// Personal Site — Quin Li's personal landing page.
// Structure: top bar → hero → about → apps (3-card row) → experience → contact.
// Visual language matches Launchpad (cream paper, Geist/Fraunces/JetBrains Mono, red accent).

const psStyles = {
  root: {
    position: "relative",
    width: "100%",
    minHeight: "100%",
    background: "#f5f3ee",
    color: "#141414",
    fontFamily: "'Geist', 'Inter', sans-serif",
    overflow: "hidden"
  },
  sectionPad: { padding: "0 64px" },
  sectionPadMobile: { padding: "0 20px" }
};

function useIsMobile(breakpoint = 768) {
  const [m, setM] = React.useState(
    typeof window !== "undefined" ? window.innerWidth < breakpoint : false
  );
  React.useEffect(() => {
    const onResize = () => setM(window.innerWidth < breakpoint);
    window.addEventListener("resize", onResize);
    return () => window.removeEventListener("resize", onResize);
  }, [breakpoint]);
  return m;
}

// Experience timeline — high-level work and education, sourced from CV.
const PS_WORK = [
  {
    period: "Oct 2025 — Now",
    role: "Geotechnical Engineer · AI & Digital Specialist",
    org: "EIC Activities (CIMIC Group)"
  },
  {
    period: "Jan 2025 — Oct 2025",
    role: "Graduate Geotechnical Engineer",
    org: "EIC Activities (CIMIC Group)"
  },
  {
    period: "May 2023 — Jan 2025",
    role: "Structural Engineer",
    org: "Klopfer Dobos Engineering"
  },
  {
    period: "2020 — 2024",
    role: "Sessional Lecturer & Academic Tutor",
    org: "Victoria University · University of Melbourne"
  }
];

const PS_TALKS = [
  {
    kind: "Keynote",
    date: "Nov 2025",
    title: "AI for the Construction Industry — Opportunities and Challenges",
    summary: "Invited keynote at IEGRC 2025, hosted by the Department of Infrastructure Engineering at the University of Melbourne — \u201CBuilding More with Less.\u201D",
    url: "https://www.linkedin.com/feed/update/urn:li:activity:7389919073314381824/"
  },
  {
    kind: "Build log",
    date: "Sep 2025",
    title: "From 30+ hours to 30 minutes",
    summary: "The story behind the Geotechnical Rapid Data Analysis app — how a wall of Excel tabs and PDFs became one shared workspace at EIC Activities.",
    url: "https://www.linkedin.com/feed/update/urn:li:activity:7363566094877405184/",
    metric: "171 reactions · 5 reposts"
  },
  {
    kind: "Launch",
    date: "Apr 2026",
    title: "BoreholeAI is live",
    summary: "AI-powered borehole log digitalisation — from PDF to structured data.",
    url: "https://www.linkedin.com/feed/update/urn:li:activity:7445281380277772288/"
  },
  {
    kind: "Milestone",
    date: "May 2025",
    title: "PhD, Infrastructure Engineering — University of Melbourne",
    summary: "A 3.5-year journey under Prof. Lihai Zhang. \u201CA mindset for tackling the unknown and approaching challenges from first principles.\u201D",
    url: "https://www.linkedin.com/feed/update/urn:li:activity:7258030368258174977/",
    metric: "70 reactions"
  }
];

const PS_EDUCATION = [
  {
    period: "2021 — 2024",
    role: "PhD – Engineering and IT",
    org: "University of Melbourne"
  },
  {
    period: "2019 — 2020",
    role: "Master of Engineering (Structures)",
    org: "University of Melbourne"
  },
  {
    period: "2017 — 2018",
    role: "Bachelor of Science",
    org: "University of Melbourne"
  }
];

const PS_APPS = [
  { name: "BoreholeAI" },
  { name: "Geotechnical Rapid Data Analysis" },
  { name: "Geophysics Analysis" }
];

const PS_PROJECTS = [
  "Logan & Gold Coast Faster Rail",
  "Gateway Motorway and Bruce Highway upgrades"
];

function PersonalSite({ tweaks }) {
  const accent = getAccent(tweaks.accent);
  const ref = React.useRef(null);

  const bg = (() => {
    if (tweaks.background === "plain") return "#f5f3ee";
    if (tweaks.background === "data") {
      return `#f5f3ee url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='600' height='200' viewBox='0 0 600 200'><g fill='none' stroke='%23141414' stroke-opacity='0.05' stroke-width='0.6'><path d='M0,100 Q40,60 80,100 T160,100 T240,100 T320,100 T400,100 T480,100 T560,100 T640,100' /><path d='M0,120 Q40,90 80,120 T160,120 T240,120 T320,120 T400,120 T480,120 T560,120' /><path d='M0,80 Q40,50 80,80 T160,80 T240,80 T320,80 T400,80 T480,80 T560,80' /></g></svg>")`;
    }
    return `#f5f3ee url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='22' height='22'><circle cx='1' cy='1' r='1' fill='%23141414' fill-opacity='0.06'/></svg>")`;
  })();

  return (
    <div ref={ref} style={{ ...psStyles.root, background: bg }}>
      <PSTopBar accent={accent} />
      <PSHero accent={accent} />
      <PSAbout accent={accent} />
      <PSAppsSection accent={accent} cardVariant={tweaks.cardVariant} />
      <PSTalksWriting accent={accent} />
      <PSExperience accent={accent} />
      <PSFooter />
    </div>
  );
}

/* ─────────────────────────────────────────────────────────── Top bar */
function PSTopBar({ accent }) {
  const isMobile = useIsMobile();
  const nav = [
    ["About", "#about"],
    ["Apps", "#apps"],
    ["Experience", "#experience"]
  ];
  return (
    <header style={{ padding: isMobile ? "16px 20px" : "22px 64px", display: "flex", alignItems: "center", justifyContent: "space-between", borderBottom: "1px solid rgba(20,20,20,0.06)" }}>
      <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
        <svg width="22" height="22" viewBox="0 0 22 22" style={{ display: "block" }}>
          <rect x="0" y="0" width="22" height="22" fill={accent.color} rx="4" />
          <path d="M5 6h12M5 11h8M5 16h12" stroke="#fff" strokeWidth="1.6" />
        </svg>
        <span style={{ fontSize: 13, fontWeight: 700, letterSpacing: "0.18em", color: "#141414" }}>QUIN&nbsp;LI,&nbsp;PhD</span>
      </div>
      <nav style={{ display: "flex", alignItems: "center", gap: isMobile ? 14 : 28 }}>
        {!isMobile && nav.map(([label, href]) => (
          <a key={label} href={href} style={{ fontSize: 13, color: "#3a3a3a", textDecoration: "none", fontWeight: 500 }}>{label}</a>
        ))}
        <a href={"mailto:" + HUB_AUTHOR.email}
          style={{
            padding: isMobile ? "7px 12px" : "7px 14px", borderRadius: 8,
            background: "#141414", color: "#fff",
            fontSize: 12, fontWeight: 600, letterSpacing: "0.02em",
            textDecoration: "none"
          }}>
          Get in touch
        </a>
      </nav>
    </header>
  );
}

/* ─────────────────────────────────────────────────────────── Hero */
function PSHero({ accent }) {
  const isMobile = useIsMobile();
  const pad = isMobile ? psStyles.sectionPadMobile : psStyles.sectionPad;
  const meta = (
    <div style={{
      display: "flex", flexDirection: "column", gap: 22,
      fontFamily: "'JetBrains Mono', monospace",
      ...(isMobile
        ? { marginTop: 36, position: "relative" }
        : { position: "absolute", top: 96, right: 64, width: 220 })
    }}>
      <div>
        <div style={{ fontSize: 10, fontWeight: 700, color: "#9a9a9a", letterSpacing: "0.18em", textTransform: "uppercase", marginBottom: 8 }}>Based</div>
        <div style={{ fontSize: 13, color: "#141414", fontWeight: 500 }}>Melbourne, Australia</div>
      </div>
      <div>
        <div style={{ fontSize: 10, fontWeight: 700, color: "#9a9a9a", letterSpacing: "0.18em", textTransform: "uppercase", marginBottom: 8 }}>Currently</div>
        <div style={{ fontSize: 13, color: "#141414", fontWeight: 500, lineHeight: 1.4 }}>Geotechnical engineer &amp; AI/digital specialist at EIC Activities.</div>
      </div>
      <div>
        <div style={{ fontSize: 10, fontWeight: 700, color: "#9a9a9a", letterSpacing: "0.18em", textTransform: "uppercase", marginBottom: 8 }}>Status</div>
        <div style={{ display: "inline-flex", alignItems: "center", gap: 8 }}>
          <span style={{ display: "inline-block", width: 7, height: 7, borderRadius: 999, background: accent.color }} />
          <span style={{ fontSize: 12, color: accent.color, fontWeight: 600, letterSpacing: "0.08em", textTransform: "uppercase" }}>Working the problem</span>
        </div>
      </div>
    </div>
  );

  return (
    <section style={{ ...pad, paddingTop: isMobile ? 48 : 96, paddingBottom: isMobile ? 56 : 88, position: "relative" }}>
      <div style={{ maxWidth: 1100 }}>
        <h1 style={{
          margin: 0,
          fontSize: isMobile ? "clamp(40px, 11vw, 56px)" : 92,
          lineHeight: 1.02,
          letterSpacing: "-0.035em",
          fontWeight: 500,
          color: "#141414",
          fontFamily: "'Fraunces', 'Geist', serif",
          textWrap: "balance"
        }}>
          I'm Quin.<br />
          Geotechnical engineer,<br />
          end to end.
        </h1>
        {isMobile && meta}
        <p style={{ margin: "36px 0 0 0", fontSize: isMobile ? 16 : 19, lineHeight: 1.55, color: "#3a3a3a", maxWidth: 680, fontWeight: 400 }}>
          Site investigation, ground modelling, and the in-house data
          analysis applications that join them up. The apps below are some
          of those tools, used by fellow engineers and me day to day.
        </p>
      </div>

      {!isMobile && meta}
    </section>
  );
}

/* ─────────────────────────────────────────────────────────── About */
function PSAbout({ accent }) {
  const isMobile = useIsMobile();
  const pad = isMobile ? psStyles.sectionPadMobile : psStyles.sectionPad;
  return (
    <section id="about" style={{ ...pad, paddingTop: isMobile ? 48 : 64, paddingBottom: isMobile ? 56 : 96, borderTop: "1px solid rgba(20,20,20,0.06)" }}>
      <PSSectionHeader eyebrow="About" title="About" accent={accent} />
      <div style={{ display: "grid", gridTemplateColumns: isMobile ? "1fr" : "1fr 1.6fr", gap: isMobile ? 40 : 80, marginTop: 36, alignItems: "start" }}>
        <div>
          <p style={{ margin: 0, fontSize: 16, lineHeight: 1.7, color: "#3a3a3a" }}>
            I'm a geotechnical engineer who also enjoys what technology brings
            to the work. My background runs through structural engineering,
            geotechnics and computational research — a PhD at the University
            of Melbourne and industry experience pulled them together. Most
            days are core geotechnical work — calculations, modelling,
            ground interpretation — shoulder-to-shoulder with the rest of
            the team.
          </p>
          <p style={{ margin: "20px 0 0 0", fontSize: 16, lineHeight: 1.7, color: "#3a3a3a" }}>
            In the gaps I work on the digital side: digital engineering and
            AI applied to geotechnical data. The part I enjoy most is both
            halves — building AI solutions and seeing them deployed in real
            construction engineering work, not stuck in a notebook. A lot
            of geotechnical reporting still gets bogged down in manual data
            handling, formatting and repetition before interpretation can
            begin; that's where most of the apps below started. Some are
            built by me, others I contributed to, all shaped by feedback
            from engineers in the team.
          </p>
        </div>

        <div>
          <div style={{ display: "grid", gridTemplateColumns: isMobile ? "1fr" : "1fr 1fr", gap: isMobile ? 32 : 56 }}>
            <div>
              <div style={{ fontSize: 11, fontWeight: 700, color: "#9a9a9a", letterSpacing: "0.18em", textTransform: "uppercase", marginBottom: 18, fontFamily: "'JetBrains Mono', monospace" }}>
                Developments
              </div>
              <ul style={{ listStyle: "none", padding: 0, margin: 0, display: "flex", flexDirection: "column", gap: 12 }}>
                {PS_APPS.map((f, i) => (
                  <li key={i} style={{ display: "flex", alignItems: "flex-start", gap: 12, fontSize: 15, color: "#141414", lineHeight: 1.5 }}>
                    <span style={{
                      flex: "0 0 auto",
                      marginTop: 7,
                      width: 6, height: 6, borderRadius: 999,
                      background: accent.color
                    }} />
                    <span>
                      {f.name}
                      {f.betaSup && (
                        <sup style={{ fontSize: "0.6em", fontWeight: 600, color: accent.color, marginLeft: 2, fontFamily: "'JetBrains Mono', monospace", letterSpacing: "0.05em" }}>(beta)</sup>
                      )}
                    </span>
                  </li>
                ))}
              </ul>
            </div>

            <div>
              <div style={{ fontSize: 11, fontWeight: 700, color: "#9a9a9a", letterSpacing: "0.18em", textTransform: "uppercase", marginBottom: 18, fontFamily: "'JetBrains Mono', monospace" }}>
                Recent projects
              </div>
              <ul style={{ listStyle: "none", padding: 0, margin: 0, display: "flex", flexDirection: "column", gap: 12 }}>
                {PS_PROJECTS.map((p, i) => (
                  <li key={i} style={{ display: "flex", alignItems: "flex-start", gap: 12, fontSize: 15, color: "#141414", lineHeight: 1.5 }}>
                    <span style={{
                      flex: "0 0 auto",
                      marginTop: 7,
                      width: 6, height: 6, borderRadius: 1,
                      background: "#141414"
                    }} />
                    <span>{p}</span>
                  </li>
                ))}
              </ul>
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

/* ─────────────────────────────────────────────────────────── Apps section */
function PSAppsSection({ accent, cardVariant }) {
  const isMobile = useIsMobile();
  const pad = isMobile ? psStyles.sectionPadMobile : psStyles.sectionPad;
  return (
    <section id="apps" style={{ ...pad, paddingTop: isMobile ? 48 : 64, paddingBottom: isMobile ? 56 : 96, borderTop: "1px solid rgba(20,20,20,0.06)", background: "rgba(255,255,255,0.35)" }}>
      <PSSectionHeader
        eyebrow="Apps"
        title="Apps"
        accent={accent}
        meta={
          <span style={{ display: "inline-flex", alignItems: "center", gap: 10, fontSize: 11, color: "#6b6b6b", fontFamily: "'JetBrains Mono', monospace", letterSpacing: "0.08em", textTransform: "uppercase" }}>
            {HUB_APPS.filter((a) => a.statusKind === "live").length} of {HUB_APPS.length} live
          </span>
        }
      />
      <p style={{ margin: "16px 0 36px 0", fontSize: 15, lineHeight: 1.6, color: "#5a5a5a", maxWidth: 640 }}>
        Production tools used by fellow engineers and me day to day — each one chips away at
        something that used to take hours. Click through to launch.
      </p>
      <div style={{ display: "grid", gridTemplateColumns: isMobile ? "1fr" : "repeat(3, 1fr)", gap: isMobile ? 14 : 18 }}>
        {HUB_APPS.map((app, i) => (
          <PSAppCard key={app.id} app={app} accent={accent} index={i} />
        ))}
      </div>
    </section>
  );
}

function PSAppCard({ app, accent, index }) {
  const [hover, setHover] = React.useState(false);
  return (
    <a
      href={app.url || "#"}
      target={app.external ? "_blank" : undefined}
      rel={app.external ? "noreferrer" : undefined}
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      style={{
        position: "relative",
        display: "flex", flexDirection: "column",
        background: "#fff",
        border: "1px solid rgba(20,20,20,0.08)",
        borderRadius: 14,
        textDecoration: "none", color: "inherit",
        overflow: "hidden",
        boxShadow: hover ? "0 1px 2px rgba(20,20,20,0.04), 0 18px 36px -22px rgba(20,20,20,0.32)" : "0 1px 2px rgba(20,20,20,0.03)",
        transform: hover ? "translateY(-2px)" : "none",
        transition: "all 220ms cubic-bezier(.4,0,.2,1)"
      }}>
      {/* Preview — reuses the rich SVG previews from the Launchpad */}
      <div style={{ position: "relative" }}>
        {window.AppPreview && <window.AppPreview app={app} accent={accent} hover={hover} big={false} />}
        <span style={{
          position: "absolute", top: 12, left: 14,
          fontSize: 10, fontWeight: 700, color: "#9a9a9a",
          letterSpacing: "0.18em", textTransform: "uppercase",
          fontFamily: "'JetBrains Mono', monospace",
          background: "rgba(255,255,255,0.85)",
          padding: "3px 7px", borderRadius: 3,
          border: "1px solid rgba(20,20,20,0.06)"
        }}>{app.code}</span>
      </div>

      <div style={{ padding: "18px 20px 20px 20px", display: "flex", flexDirection: "column", gap: 8, flex: 1 }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 12 }}>
          <h3 style={{ margin: 0, fontSize: 18, fontWeight: 600, letterSpacing: "-0.015em", color: "#141414", lineHeight: 1.2 }}>
            {app.name}
            {app.betaSup && (
              <sup style={{ fontSize: "0.55em", fontWeight: 600, color: "#9a9a9a", marginLeft: 3, fontFamily: "'JetBrains Mono', monospace", letterSpacing: "0.05em", verticalAlign: "super" }}>(beta)</sup>
            )}
          </h3>
          <span style={{ color: "#0f9460", flexShrink: 0 }}>
            <StatusPing kind={app.statusKind} label={app.status} />
          </span>
        </div>
        <p style={{ margin: 0, fontSize: 13.5, color: "#4a4a4a", lineHeight: 1.5 }}>{app.tagline}</p>
        <p style={{ margin: "4px 0 0 0", fontSize: 11, color: "#9a9a9a", fontFamily: "'JetBrains Mono', monospace" }}>
          {app.stack}
        </p>
        <div style={{ flex: 1 }} />
        <div style={{ marginTop: 12, display: "flex", justifyContent: "space-between", alignItems: "center", paddingTop: 12, borderTop: "1px solid rgba(20,20,20,0.06)" }}>
          <span style={{ fontSize: 11, color: "#9a9a9a", fontFamily: "'JetBrains Mono', monospace" }}>{app.version}</span>
          <span style={{
            display: "inline-flex", alignItems: "center", gap: 6,
            padding: "6px 12px",
            fontSize: 12, fontWeight: 600,
            color: hover ? "#fff" : "#141414",
            background: hover ? accent.color : "rgba(20,20,20,0.06)",
            borderRadius: 999,
            transition: "all 200ms"
          }}>
            Launch
            <span style={{ display: "inline-block", transform: hover ? "translateX(2px)" : "none", transition: "transform 200ms" }}>→</span>
          </span>
        </div>
      </div>
    </a>
  );
}

/* ─────────────────────────────────────────────────────────── Talks & Writing */
function PSTalksWriting({ accent }) {
  const isMobile = useIsMobile();
  const pad = isMobile ? psStyles.sectionPadMobile : psStyles.sectionPad;
  return (
    <section id="talks" style={{ ...pad, paddingTop: isMobile ? 56 : 72, paddingBottom: isMobile ? 56 : 96, borderTop: "1px solid rgba(20,20,20,0.06)" }}>
      <PSSectionHeader
        eyebrow="Selected Talks & Writing"
        title="Selected Talks & Writing"
        accent={accent}
        meta={
          <span style={{ fontSize: 11, color: "#9a9a9a", fontFamily: "'JetBrains Mono', monospace", letterSpacing: "0.08em", textTransform: "uppercase" }}>
            via LinkedIn
          </span>
        }
      />
      <p style={{ margin: "16px 0 36px 0", fontSize: 15, lineHeight: 1.6, color: "#5a5a5a", maxWidth: 640 }}>
        Selected posts and talks — keynotes, build notes, and milestones from the apps and the PhD.
      </p>
      <div style={{ display: "grid", gridTemplateColumns: isMobile ? "1fr" : "1fr 1fr", gap: isMobile ? 12 : 16 }}>
        {PS_TALKS.map((item, i) => (
          <PSTalkCard key={i} item={item} accent={accent} />
        ))}
      </div>
    </section>
  );
}

function PSTalkCard({ item, accent }) {
  const [hover, setHover] = React.useState(false);
  return (
    <a
      href={item.url}
      target="_blank"
      rel="noreferrer"
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      style={{
        display: "flex", flexDirection: "column", gap: 8,
        padding: "22px 24px",
        background: "#fff",
        border: "1px solid rgba(20,20,20,0.08)",
        borderRadius: 14,
        textDecoration: "none", color: "inherit",
        transform: hover ? "translateY(-2px)" : "none",
        borderColor: hover ? "rgba(20,20,20,0.2)" : "rgba(20,20,20,0.08)",
        boxShadow: hover ? "0 1px 2px rgba(20,20,20,0.04), 0 18px 36px -24px rgba(20,20,20,0.28)" : "none",
        transition: "all 200ms cubic-bezier(.4,0,.2,1)"
      }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
        <span style={{ fontSize: 10, fontWeight: 700, color: "#9a9a9a", letterSpacing: "0.16em", textTransform: "uppercase", fontFamily: "'JetBrains Mono', monospace" }}>
          {item.kind}
        </span>
        <span style={{ fontSize: 11, color: "#9a9a9a", fontFamily: "'JetBrains Mono', monospace" }}>
          {item.date}
        </span>
      </div>
      <h3 style={{ margin: 0, fontSize: 18, fontWeight: 600, lineHeight: 1.25, letterSpacing: "-0.015em", color: hover ? accent.color : "#141414", transition: "color 200ms" }}>
        {item.title}
      </h3>
      <p style={{ margin: 0, fontSize: 13.5, lineHeight: 1.55, color: "#3a3a3a" }}>
        {item.summary}
      </p>
      <div style={{ marginTop: 4, display: "flex", justifyContent: "space-between", alignItems: "center" }}>
        <span style={{ fontSize: 11, color: "#9a9a9a", fontFamily: "'JetBrains Mono', monospace" }}>
          {item.metric || "\u00A0"}
        </span>
        <span style={{ fontSize: 12, fontWeight: 600, color: "#141414", display: "inline-flex", alignItems: "center", gap: 4, transform: hover ? "translateX(2px)" : "none", transition: "transform 200ms" }}>
          Read on LinkedIn →
        </span>
      </div>
    </a>
  );
}

/* ─────────────────────────────────────────────────────────── Experience */
function PSExperience({ accent }) {
  const isMobile = useIsMobile();
  const pad = isMobile ? psStyles.sectionPadMobile : psStyles.sectionPad;
  const rowStyle = isMobile
    ? { display: "flex", flexDirection: "column", gap: 6, padding: "16px 0", borderTop: "1px solid rgba(20,20,20,0.08)" }
    : { display: "grid", gridTemplateColumns: "180px 1fr 280px", gap: 40, padding: "20px 0", borderTop: "1px solid rgba(20,20,20,0.08)", alignItems: "center" };
  return (
    <section id="experience" style={{ ...pad, paddingTop: isMobile ? 56 : 72, paddingBottom: isMobile ? 56 : 96, borderTop: "1px solid rgba(20,20,20,0.06)" }}>
      <PSSectionHeader eyebrow="Background" title="Background" accent={accent} />
      <p style={{ margin: "16px 0 44px 0", fontSize: 15, lineHeight: 1.6, color: "#5a5a5a", maxWidth: 640 }}>
        Work and education.
      </p>

      <div style={{ fontSize: 11, fontWeight: 700, color: "#9a9a9a", letterSpacing: "0.18em", textTransform: "uppercase", marginBottom: 8, fontFamily: "'JetBrains Mono', monospace" }}>
        Work
      </div>
      <div style={{ display: "flex", flexDirection: "column", marginBottom: 44 }}>
        {PS_WORK.map((e, i) => (
          <div key={i} style={rowStyle}>
            <div style={{ fontSize: 13, color: "#6b6b6b", fontFamily: "'JetBrains Mono', monospace", letterSpacing: "0.02em" }}>
              {e.period}
            </div>
            <h4 style={{ margin: 0, fontSize: isMobile ? 16 : 18, fontWeight: 600, color: "#141414", letterSpacing: "-0.01em" }}>
              {e.role}
            </h4>
            <div style={{ fontSize: 14, color: "#141414", fontWeight: 500 }}>
              {e.org}
            </div>
          </div>
        ))}
        <div style={{ borderTop: "1px solid rgba(20,20,20,0.08)" }} />
      </div>

      <div style={{ fontSize: 11, fontWeight: 700, color: "#9a9a9a", letterSpacing: "0.18em", textTransform: "uppercase", marginBottom: 8, fontFamily: "'JetBrains Mono', monospace" }}>
        Education
      </div>
      <div style={{ display: "flex", flexDirection: "column" }}>
        {PS_EDUCATION.map((e, i) => (
          <div key={i} style={rowStyle}>
            <div style={{ fontSize: 13, color: "#6b6b6b", fontFamily: "'JetBrains Mono', monospace", letterSpacing: "0.02em" }}>
              {e.period}
            </div>
            <h4 style={{ margin: 0, fontSize: isMobile ? 16 : 18, fontWeight: 600, color: "#141414", letterSpacing: "-0.01em" }}>
              {e.role}
            </h4>
            <div style={{ fontSize: 14, color: "#141414", fontWeight: 500 }}>
              {e.org}
            </div>
          </div>
        ))}
        <div style={{ borderTop: "1px solid rgba(20,20,20,0.08)" }} />
      </div>
    </section>
  );
}

/* ─────────────────────────────────────────────────────────── Contact */
function PSContact({ accent }) {
  return (
    <section id="contact" style={{ ...psStyles.sectionPad, paddingTop: 80, paddingBottom: 96, borderTop: "1px solid rgba(20,20,20,0.06)", background: "#141414", color: "#f5f3ee" }}>
      <div style={{ display: "grid", gridTemplateColumns: "1.4fr 1fr", gap: 64, alignItems: "end" }}>
        <div>
          <div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 24 }}>
            <span style={{ width: 22, height: 1, background: accent.color }} />
            <span style={{ fontSize: 11, fontWeight: 700, color: accent.color, letterSpacing: "0.22em", textTransform: "uppercase", fontFamily: "'JetBrains Mono', monospace" }}>
              Contact
            </span>
          </div>
          <h2 style={{ margin: 0, fontSize: 64, lineHeight: 1.02, letterSpacing: "-0.03em", fontWeight: 600, color: "#fff", fontFamily: "'Fraunces', 'Geist', serif" }}>
            Got a hard ground-data problem?
          </h2>
          <p style={{ margin: "24px 0 0 0", fontSize: 17, lineHeight: 1.6, color: "rgba(245,243,238,0.7)", maxWidth: 560 }}>
            Site investigations, ground modelling, AI on engineering data. Happy to chat.
            Quickest route is email; LinkedIn works too.
          </p>
        </div>

        <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
          <a href={"mailto:" + HUB_AUTHOR.email} style={{
            display: "flex", alignItems: "center", justifyContent: "space-between",
            padding: "20px 24px",
            background: accent.color, color: "#fff",
            borderRadius: 12, textDecoration: "none",
            fontSize: 16, fontWeight: 600
          }}>
            <span style={{ display: "flex", flexDirection: "column", gap: 2 }}>
              <span style={{ fontSize: 11, fontWeight: 700, letterSpacing: "0.18em", textTransform: "uppercase", opacity: 0.8, fontFamily: "'JetBrains Mono', monospace" }}>Email</span>
              <span>{HUB_AUTHOR.email}</span>
            </span>
            <span style={{ fontSize: 22 }}>→</span>
          </a>
          <a href={HUB_AUTHOR.linkedin} target="_blank" rel="noreferrer" style={{
            display: "flex", alignItems: "center", justifyContent: "space-between",
            padding: "20px 24px",
            background: "rgba(255,255,255,0.06)", color: "#fff",
            border: "1px solid rgba(255,255,255,0.12)",
            borderRadius: 12, textDecoration: "none",
            fontSize: 16, fontWeight: 600
          }}>
            <span style={{ display: "flex", flexDirection: "column", gap: 2 }}>
              <span style={{ fontSize: 11, fontWeight: 700, letterSpacing: "0.18em", textTransform: "uppercase", opacity: 0.6, fontFamily: "'JetBrains Mono', monospace" }}>LinkedIn</span>
              <span>linkedin.com/in/quin-li</span>
            </span>
            <span style={{ fontSize: 22 }}>→</span>
          </a>
        </div>
      </div>
    </section>
  );
}

/* ─────────────────────────────────────────────────────────── Footer */
function PSFooter() {
  const isMobile = useIsMobile();
  const linkStyle = { color: "#3a3a3a", textDecoration: "none" };
  return (
    <footer style={{
      padding: isMobile ? "20px 20px" : "22px 64px",
      borderTop: "1px solid rgba(20,20,20,0.08)",
      color: "#6b6b6b",
      display: "flex",
      flexDirection: isMobile ? "column" : "row",
      justifyContent: "space-between",
      alignItems: isMobile ? "flex-start" : "center",
      gap: isMobile ? 10 : 16, flexWrap: "wrap",
      fontSize: 12, fontFamily: "'JetBrains Mono', monospace"
    }}>
      <span>© {new Date().getFullYear()} {HUB_AUTHOR.name}</span>
      <span style={{ display: "inline-flex", flexDirection: isMobile ? "column" : "row", alignItems: isMobile ? "flex-start" : "center", gap: isMobile ? 6 : 18 }}>
        <a href={"mailto:" + HUB_AUTHOR.email} style={linkStyle}>{HUB_AUTHOR.email}</a>
        <a href={HUB_AUTHOR.linkedin} target="_blank" rel="noreferrer" style={linkStyle}>linkedin.com/in/quin-li</a>
      </span>
    </footer>
  );
}

/* ─────────────────────────────────────────────────────────── Section header helper */
function PSSectionHeader({ eyebrow, title, meta, accent }) {
  // Single red Fraunces title. No mono eyebrow, no black title.
  const ti = (title || eyebrow || "").trim();
  const accentColor = (accent && accent.color) || "#d71919";
  return (
    <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 24 }}>
      <h2 style={{
        margin: 0,
        fontSize: 30,
        fontWeight: 500,
        letterSpacing: "-0.02em",
        color: accentColor,
        fontFamily: "'Fraunces', 'Geist', serif",
        lineHeight: 1.05
      }}>
        {ti}
      </h2>
      {meta}
    </div>
  );
}

window.PersonalSite = PersonalSite;
