  /* ───────────────────────── Tokens ─────────────────────────
     Shared colors, fonts, base resets, type roles, and nav are
     in shared.css (loaded before this file). Only the homepage's
     own layout tokens and overrides live here. ───────────────────────────────────── */
  :root {
    --rail: 8.3333%;
    --gutter: clamp(20px, var(--rail), 120px);
    --content-max: 1400px;
    --nav-h: 96px;
  }

  input:focus-visible { outline-offset: -2px; }
  #about:not([data-mode="personal"]) :focus-visible, #contact :focus-visible, #menuPanel :focus-visible { outline-color: var(--dark-muted-85); }

  @keyframes ticker { from { transform: translateX(0); } to { transform: translateX(-50%); } }
  @keyframes fade-up { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }

  /* ───────────────────────── Frame ─────────────────────────
     Full-bleed: the frame itself spans the whole viewport (so
     section backgrounds — the dark About block, the accent
     Contact block — reach the true edges with no seam). Each
     section's own content row is capped at --content-max and
     centered instead; see the `.container-*` rules below. */
  .frame {
    position: relative;
    width: 100%;
    color: var(--ink);
    overflow: hidden;
  }
  /* .site-nav is intentionally excluded here - it spans the full frame
     width edge-to-edge, using its own --gutter padding (shared.css) to
     inset the brand/links instead of a centered max-width. */
  #hero, .about-rail, .about-content-grid, .about-footers,
  .work-inner, .skills-grid, .contact-content {
    max-width: var(--content-max);
    margin-left: auto;
    margin-right: auto;
    box-sizing: border-box;
  }

  /* ───────────────────────── Nav ─────────────────────────
     .site-nav / .nav-brand / .skip-link live in shared.css. */
  .nav-links { display: flex; align-items: center; gap: clamp(12px,1.6vw,22px); flex: none; }
  .nav-links a { text-decoration: none; color: var(--ink); display: flex; align-items: center; gap: 8px; min-height: 44px; }
  .nav-links a .dia { width: 6px; height: 6px; flex: none; background: var(--accent); transform: rotate(45deg); }
  .nav-links a:hover { color: var(--accent); text-decoration: underline; }

  .burger {
    display: none; position: relative; z-index: 120; width: 44px; height: 44px; margin-right: -10px;
    flex: none; align-items: center; justify-content: center; cursor: pointer; background: none; border: none; padding: 0;
  }
  .burger-icon { position: relative; display: block; width: 20px; height: 12px; }
  .burger-icon span {
    position: absolute; left: 0; width: 20px; height: 1px; background: var(--ink);
    transition: transform .35s var(--ease), background .3s;
  }
  .burger-icon span:first-child { top: 0; }
  .burger-icon span:last-child { bottom: 0; }
  .burger[aria-expanded="true"] .burger-icon span:first-child { transform: translateY(5px) rotate(45deg); background: var(--dark-ink); }
  .burger[aria-expanded="true"] .burger-icon span:last-child { transform: translateY(-6px) rotate(-45deg); background: var(--dark-ink); }

  /* Mobile menu panel */
  #menuPanel {
    position: fixed; inset: 0; z-index: 110; background: var(--dark-bg); color: var(--dark-ink);
    display: flex; flex-direction: column; height: 100vh;
    /* svh (small viewport height) is fixed to the viewport with the mobile
       browser's address/toolbar chrome fully shown, unlike plain vh, which
       some mobile browsers size larger than the actually visible area.
       body scroll is locked while this panel is open (see setMenu in the
       page script), so if the panel itself were taller than the true
       visible viewport, its own overflow:auto couldn't be reached to
       scroll down to it - content past the fold (like the LinkedIn button)
       would be genuinely stuck off-screen. A firm height here, rather than
       min-height, keeps the panel exactly one screen tall so that never
       happens; overflow:auto below is a fallback for content that's
       taller than even a correctly-measured screen. */
    height: 100svh;
    overflow: auto; opacity: 0; pointer-events: none; clip-path: inset(0 0 100% 0);
    transition: clip-path .55s var(--ease), opacity .3s ease;
  }
  #menuPanel.is-open { opacity: 1; pointer-events: auto; clip-path: inset(0 0 0 0); }
  .menu-ring {
    position: absolute; left: 50%; top: -60px; width: 260px; height: 260px;
    border: 1px solid rgba(111,111,255,.28); transform: translateX(-50%) rotate(45deg); pointer-events: none;
  }
  .menu-head {
    position: relative; height: 64px; padding: 0 24px; display: flex; align-items: center; justify-content: space-between;
    box-shadow: inset 0 -1px 0 var(--dark-muted-16);
  }
  .menu-head .nav-brand span { color: var(--dark-muted-58); }
  .menu-close {
    flex: none; width: 44px; height: 44px; margin-right: -10px; display: flex; align-items: center; justify-content: center;
    background: none; border: none; padding: 0; cursor: pointer; color: var(--dark-ink);
  }
  .menu-close:hover { color: var(--accent-dark); }
  .menu-index { padding: 32px 24px 20px; color: var(--dark-muted-58); }
  .menu-nav { box-shadow: inset 0 1px 0 var(--dark-muted-14); }
  .menu-item {
    position: relative; display: flex; align-items: center; gap: 18px; min-height: 72px; padding: 0 24px;
    text-decoration: none; color: var(--dark-ink); box-shadow: inset 0 -1px 0 var(--dark-muted-14);
    transition: background .2s, color .2s;
  }
  .menu-item .label { flex: 1; }
  .menu-item .node { width: 9px; height: 9px; flex: none; transform: rotate(45deg); border: 1px solid var(--dark-muted-35); transition: background .2s, border-color .2s; }
  .menu-item.is-current { color: var(--accent-dark); }
  .menu-item.is-current .node { background: var(--accent-dark); border: none; }
  .menu-item:hover { background: rgba(244,243,239,.06); color: var(--accent-dark); }
  .menu-item:hover .node { border-color: var(--accent-dark); }
  .menu-extra {
    position: relative; flex: none; margin: auto 24px 32px 24px; display: flex; align-items: center; justify-content: space-between; gap: 14px;
    padding-top: 20px; box-shadow: inset 0 1px 0 var(--dark-muted-20); color: var(--dark-muted-85);
  }
  .menu-extra a { text-decoration: none; color: var(--dark-muted-85); display: flex; justify-content: space-between; min-height: 44px; align-items: center; transition: color .2s; }
  .menu-extra a:hover { color: var(--accent-dark); text-decoration: underline; }
  /* Same pill treatment as the footer's LinkedIn button (.contact-btn) -
     a light button reads the same way against this dark menu panel as it
     does against the blue #contact section. Redeclared rather than
     sharing the class since .menu-extra a's own color/justify-content
     would otherwise win on specificity. */
  .menu-extra a.menu-cta {
    justify-content: center; gap: 8px; width: fit-content; height: 48px; padding: 0 24px;
    background: var(--bg); color: var(--ink); border: 1px solid var(--bg); text-decoration: none;
    transition: background .2s, color .2s, border-color .2s;
  }
  .menu-extra a.menu-cta:hover { background: transparent; color: var(--bg); border-color: var(--bg); text-decoration: none; }
  #menuPanel[aria-hidden="true"] { display: flex; }

  @media (max-width: 767px) {
    :root { --nav-h: 64px; }
    .nav-links { display: none; }
    .burger { display: flex; }
    .site-nav { height: 64px; }
  }

  /* ───────────────────────── Section shells ───────────────────────── */
  [data-grid-section] { position: relative; overflow: hidden; }

  .grid-layer {
    position: absolute; inset: 0; pointer-events: none;
    background-size: var(--rail) 96px;
  }

  /* ───────────────────────── Hero ───────────────────────── */
  #hero {
    padding: 64px var(--gutter) 96px;
    min-height: calc(100vh - var(--nav-h));
    /* svh (small viewport height) is fixed to the viewport size with the
       mobile browser's address/toolbar chrome fully shown, unlike plain vh,
       which mobile browsers recompute (and grow) once that chrome collapses
       on scroll. With plain vh, clicking a nav link to an anchor below the
       hero would land correctly, then the hero would grow moments later as
       the chrome collapsed, pushing that anchor down and making the page
       look like it "bounced" back up toward the hero. svh never changes
       mid-scroll, so there's nothing to correct after landing. Older
       browsers without svh support just ignore this line and keep the
       plain-vh rule above. */
    min-height: calc(100svh - var(--nav-h));
    display: flex;
    flex-direction: column;
    justify-content: center;
    box-sizing: border-box;
  }
  .hero-grid {
    background-image:
      linear-gradient(to right, var(--ink-09) 0 1px, transparent 1px),
      linear-gradient(to bottom, rgba(22,22,26,.075) 0 1px, transparent 1px);
    mask-image: radial-gradient(ellipse 48% 55% at 50% 50%, #000 0%, rgba(0,0,0,.35) 55%, transparent 88%);
    -webkit-mask-image: radial-gradient(ellipse 48% 55% at 50% 50%, #000 0%, rgba(0,0,0,.35) 55%, transparent 88%);
  }
  /* Cursor-follow spotlight: brightens the grid in accent blue under the
     pointer. Opacity and mask-image are driven by the [data-glow] script
     below; the mask here is just the resting/initial state. */
  .hero-glow {
    opacity: 0;
    background-image:
      linear-gradient(to right, rgba(43,43,240,.5) 0 1px, transparent 1px),
      linear-gradient(to bottom, rgba(43,43,240,.35) 0 1px, transparent 1px);
  }
  @media (max-width: 479px) {
    .hero-glow { display: none; }
  }
  .hero-node {
    position: absolute; left: 50%; top: 50%; border: 1px solid rgba(43,43,240,.5);
    transform: translate(-50%,-50%) rotate(45deg); pointer-events: none;
    animation: hero-node-in 1.2s var(--ease) both;
  }
  .hero-node.n1 { width: min(384px,62vw); height: min(384px,62vw); animation-delay: .15s; }
  .hero-node.n2 { width: min(192px,31vw); height: min(192px,31vw); border-color: rgba(43,43,240,.25); animation-delay: .3s; }
  @keyframes hero-node-in { from { opacity: 0; transform: translate(-50%,-50%) rotate(45deg) scale(.9); } to { opacity: 1; transform: translate(-50%,-50%) rotate(45deg) scale(1); } }
  #hero .t-title { max-width: 853px; }
  #hero .t-bodylead { margin-top: 48px; max-width: 640px; color: var(--ink-62); }

  /* Word rotator - overflow:hidden clips the wipe animation to exactly one
     line's height, sat flush on the headline's own baseline (none of the
     rotator's words have descenders, so "bottom" lines up with the rest of
     the line without extra padding to compensate for one). */
  .hero-rotator { display: inline-block; overflow: hidden; vertical-align: bottom; height: 1.05em; }
  .hero-rotator-word { display: inline-block; color: var(--accent); will-change: transform, opacity; }

  /* ───────────────────────── About ───────────────────────── */
  #about {
    --a-bg: var(--dark-bg);
    --a-ink: var(--dark-ink);
    --a-accent: var(--accent-dark);
    --a-frame: rgba(111,111,255,.55);
    --a-line: var(--dark-muted-16);
    --a-line-soft: var(--dark-muted-10);
    --a-rail: var(--dark-muted-20);
    --a-band: #1c1c22;
    --a-muted: var(--dark-muted-58);
    --a-line2: var(--dark-muted-30);
    background: var(--a-bg); color: var(--a-ink);
    box-shadow: inset 0 -1px 0 var(--a-rail);
    transition: background .5s, color .5s, box-shadow .5s;
  }
  #about[data-mode="personal"] {
    --a-bg: #ecebe4;
    --a-ink: var(--ink);
    --a-accent: var(--accent);
    --a-frame: rgba(43,43,240,.55);
    --a-line: var(--ink-14);
    --a-line-soft: rgba(22,22,26,.10);
    --a-rail: var(--ink-18);
    --a-band: #ecebe4;
    --a-muted: var(--ink-68);
    --a-line2: rgba(22,22,26,.25);
  }
  .interests-band {
    position: relative; width: 100%; height: 96px; margin-top: 96px; display: flex; align-items: center; overflow: hidden;
    background: var(--a-band); box-shadow: inset 0 1px 0 var(--a-line), inset 0 -1px 0 var(--a-line); transition: background .5s, box-shadow .5s;
  }
  .interests-label {
    flex: none; height: 100%; display: flex; align-items: center; gap: 14px;
    padding: 0 clamp(14px,2vw,28px) 0 var(--gutter);
    box-shadow: inset -1px 0 0 var(--a-line); color: var(--a-muted); transition: color .5s, box-shadow .5s;
  }
  .tick-btn {
    flex: none; width: 26px; height: 26px; display: flex; align-items: center; justify-content: center;
    padding: 0; background: none; border: 1px solid var(--dark-muted-35); color: inherit; cursor: pointer;
  }
  #about[data-mode="personal"] .tick-btn { border-color: rgba(22,22,26,.35); }
  .tick-icon-pause { display: flex; gap: 3px; align-items: center; }
  .tick-icon-pause span { width: 2px; height: 9px; background: currentColor; }
  .tick-icon-play { width: 0; height: 0; border-left: 7px solid currentColor; border-top: 5px solid transparent; border-bottom: 5px solid transparent; margin-left: 2px; }

  .ticker-viewport { position: relative; flex: 1; min-width: 0; height: 100%; overflow: hidden; }
  .ticker-track {
    position: absolute; left: 0; top: 0; height: 100%; width: max-content; display: flex; align-items: center; gap: 28px;
    padding-left: 20px; white-space: nowrap; color: var(--a-muted);
    animation: ticker 38s linear infinite; transition: opacity .4s;
  }
  .ticker-track[data-track="pro"] { opacity: 1; }
  .ticker-track[data-track="personal"] { opacity: 0; color: var(--a-accent); }
  #about[data-mode="personal"] .ticker-track[data-track="pro"] { opacity: 0; }
  #about[data-mode="personal"] .ticker-track[data-track="personal"] { opacity: 1; }
  .ticker-track .dia { color: var(--a-accent); }
  .ticker-track.is-paused { animation-play-state: paused; }

  .about-rail {
    position: relative; height: 96px; padding: 0 var(--gutter); display: flex; align-items: center;
    justify-content: space-between;
  }
  /* The divider is a separately-sized pseudo-element rather than a
     box-shadow on .about-rail itself: box-shadow always follows an
     element's own border-box, so it can't be narrowed by padding alone.
     .work-head itself has no divider - the line under "Work" is
     actually the first .work-row's top border, which bleeds 24px past
     its gutter-padded column on each side (see .work-row's negative
     margin below). Matching that same (gutter - 24px) inset, rather than
     a plain gutter inset, is what makes the two lines the same width. */
  .about-rail::after {
    content: ''; position: absolute; left: calc(var(--gutter) - 24px); right: calc(var(--gutter) - 24px); bottom: 0;
    height: 1px; background: var(--a-rail); transition: background .5s;
  }
  .about-rail h2 { color: var(--a-accent); transition: color .5s; }

  .mode-switch {
    position: relative; height: 34px; flex: none; display: flex; align-items: center;
    background: var(--a-bg); border: 1px solid var(--a-line2); transition: background .5s, border-color .5s;
  }
  .mode-thumb {
    position: absolute; top: -1px; left: -1px; height: 34px; background: var(--a-accent);
    transition: transform .4s var(--ease), width .4s var(--ease), background .5s;
  }
  .mode-switch button {
    position: relative; height: 34px; padding: 0 clamp(10px,1.4vw,16px); background: none; border: none;
    cursor: pointer; color: var(--a-muted);
  }
  .mode-switch button[aria-pressed="true"] { color: var(--a-bg); }

  .about-content-grid {
    position: relative; padding: 96px var(--gutter) 0; display: grid; grid-template-columns: repeat(10,1fr); gap: 0 32px;
  }
  .about-copy { grid-column: 1 / 7; padding-right: clamp(0px,4vw,64px); position: relative; display: grid; }
  .about-copy p { grid-area: 1 / 1; margin: 0; color: var(--a-ink); transition: opacity .4s; }
  .about-copy p[data-copy="pro"] { opacity: 1; }
  .about-copy p[data-copy="personal"] { opacity: 0; }
  #about[data-mode="personal"] .about-copy p[data-copy="pro"] { opacity: 0; }
  #about[data-mode="personal"] .about-copy p[data-copy="personal"] { opacity: 1; }

  .about-portrait { grid-column: 7 / 11; position: relative; height: 336px; display: flex; align-items: flex-start; justify-content: center; }
  .portrait-frame {
    position: absolute; width: 230px; height: 230px; border: 1px solid var(--a-frame); transform: rotate(45deg); top: 24px;
    transition: transform .6s var(--ease), width .6s var(--ease), height .6s var(--ease), border-color .5s;
    will-change: transform, width, height;
  }
  .portrait-clip {
    position: relative; margin-top: 40px; width: 200px; height: 200px; transform: rotate(45deg); overflow: hidden;
    background: var(--accent); transition: transform .6s var(--ease), width .6s var(--ease), height .6s var(--ease);
    will-change: transform, width, height;
  }
  .portrait-img { position: absolute; inset: 0; transform: rotate(-45deg) scale(1.45); transition: opacity .4s, transform .6s var(--ease); display: grid; }
  .portrait-img img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
  .portrait-img[data-img="pro"] { opacity: 1; }
  .portrait-img[data-img="personal"] { opacity: 0; }
  #about[data-mode="personal"] .portrait-img[data-img="pro"] { opacity: 0; }
  #about[data-mode="personal"] .portrait-img[data-img="personal"] { opacity: 1; }
  #about[data-mode="personal"] .portrait-frame { transform: rotate(0deg) translateY(-39px); width: 350px; height: 270px; }
  #about[data-mode="personal"] .portrait-clip { transform: rotate(0deg) translateY(-40px); width: 320px; height: 240px; }
  #about[data-mode="personal"] .portrait-img { transform: rotate(0deg) scale(1); }

  .placeholder {
    position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; text-align: center;
    background: repeating-linear-gradient(135deg, rgba(120,120,140,.10) 0 10px, rgba(120,120,140,.04) 10px 20px);
    border: 1px dashed rgba(120,120,140,.45);
    color: rgba(120,120,140,.9);
    font: 500 11px/1.4 var(--font-mono); letter-spacing: .1em; text-transform: uppercase;
    padding: 8px;
  }

  .about-footers { position: relative; margin-top: 48px; padding: 0 var(--gutter); display: grid; }
  .footer-grid {
    grid-area: 1 / 1; box-shadow: inset 0 1px 0 var(--a-line2); padding-top: 28px;
    display: grid; grid-template-columns: 2fr 3fr 3fr 2fr; gap: 0 24px; transition: opacity .4s, box-shadow .5s;
  }
  .footer-grid[data-foot="pro"] { opacity: 1; }
  .footer-grid[data-foot="personal"] { opacity: 0; }
  #about[data-mode="personal"] .footer-grid[data-foot="pro"] { opacity: 0; }
  #about[data-mode="personal"] .footer-grid[data-foot="personal"] { opacity: 1; }
  .footer-cell { padding-right: 24px; }
  .footer-cell.divider { box-shadow: inset 1px 0 0 var(--a-line); padding-left: 24px; }
  .footer-cell .node-row { display: flex; align-items: center; gap: 10px; }
  .footer-cell .node { width: 9px; height: 9px; flex: none; border: 1px solid var(--dark-muted-35); transform: rotate(45deg); }
  #about[data-mode="personal"] .footer-cell .node { border-color: rgba(22,22,26,.35); }
  .footer-cell .node.filled { background: var(--a-accent); border: none; }
  .footer-cell .hair { flex: 1; height: 1px; background: var(--a-line); }
  .footer-cell .hair.dotted { background: repeating-linear-gradient(to right, var(--a-rail) 0 2px, transparent 2px 6px); }
  .footer-cell .foot-label { margin-top: 16px; color: var(--a-muted); }
  .footer-cell .foot-label.accent { color: var(--a-accent); }
  .footer-cell .foot-value { margin-top: 8px; }

  @media (max-width: 1023px) {
    .about-content-grid { grid-template-columns: 1fr; }
    .about-copy { grid-column: auto; padding-right: 0; }
    /* Stacked layout: margin-top is the copy-to-image gap (bumped up from
       the side-by-side layout's implicit 0, since the two no longer share
       a row to align against), and padding-bottom brings the space below
       the image up to the same 96px as .about-content-grid's own top
       padding above the copy - the footer's own 48px margin-top makes up
       the rest. */
    .about-portrait { grid-column: auto; grid-row: auto; margin-top: 64px; height: auto; padding-bottom: 48px; }
  }
  @media (max-width: 767px) {
    /* The ticker's marquee reads as a distinct, separately-scrolling row
       once the timeline above it stacks into a single narrow column - at
       that width it competes with the stacked timeline rather than
       reading as a closing band under it, so it's dropped instead. Its
       own margin-top was this section's only bottom spacing, so that gap
       is added back directly on #about now that there's nothing below
       the timeline to supply it. */
    .interests-band { display: none; }
    #about { padding-bottom: 64px; }

    /* Stacked timeline: each cell becomes a 2-column row (node rail,
       then label/value), with the node-row rotated to run vertically
       instead of horizontally. The node-row spans both the label and
       value rows so its hairline has the full cell height to fill,
       reading as one continuous rail down the left edge rather than a
       short stub per cell like the horizontal layout's. */
    .footer-grid { grid-template-columns: 1fr; gap: 0; }
    .footer-cell.divider { box-shadow: none; padding-left: 0; }

    /* Only the Professional grid's cells have a node-row (the timeline
       diamond); the Personal grid's cells are plain label/value pairs
       with no rail, so this layout is scoped to cells that actually
       have one instead of every .footer-cell. */
    .footer-grid[data-foot="pro"] .footer-cell { display: grid; grid-template-columns: 9px 1fr; grid-template-rows: auto auto; column-gap: 16px; padding-right: 0; padding-bottom: 24px; }
    .footer-grid[data-foot="pro"] .footer-cell:last-child { padding-bottom: 0; }
    .footer-grid[data-foot="pro"] .footer-cell .node-row { grid-column: 1; grid-row: 1 / 3; flex-direction: column; align-items: center; gap: 6px; }
    .footer-grid[data-foot="pro"] .footer-cell .node { margin-top: 3px; }
    .footer-grid[data-foot="pro"] .footer-cell .hair { flex: 1; width: 1px; height: auto; min-height: 24px; }
    .footer-grid[data-foot="pro"] .footer-cell .hair.dotted { background: repeating-linear-gradient(to bottom, var(--a-rail) 0 2px, transparent 2px 6px); }
    .footer-grid[data-foot="pro"] .footer-cell .foot-label { grid-column: 2; grid-row: 1; margin-top: 0; }
    .footer-grid[data-foot="pro"] .footer-cell .foot-value { grid-column: 2; grid-row: 2; }
  }

  /* ───────────────────────── Work ───────────────────────── */
  #work { padding-bottom: 48px; }
  .work-inner { position: relative; padding: 0 var(--gutter); }
  .work-head { height: 96px; display: flex; align-items: center; justify-content: space-between; gap: 16px; color: var(--ink-68); }
  .work-head h2 { color: var(--accent); }

  .work-row {
    cursor: pointer; min-height: 200px; display: grid; grid-template-columns: 106px minmax(0,747px) minmax(200px,320px);
    box-shadow: inset 0 1px 0 var(--ink-18); align-items: start; padding-top: 48px; padding-bottom: 16px;
    margin: 0 -24px; padding-left: 24px; padding-right: 24px; justify-content: space-between;
    text-decoration: none; color: inherit; transition: background .3s ease;
  }
  a.work-row { cursor: pointer; }
  a.work-row:hover, .work-row[data-lock-row]:hover { background: rgba(43,43,240,.06); }
  a.work-row:hover h3, .work-row[data-lock-row]:hover h3 { color: var(--accent); }
  a.work-row:hover .row-desc, .work-row[data-lock-row]:hover .row-desc { color: var(--ink-68); }
  .work-row.is-static { cursor: default; }
  .row-meta { display: flex; flex-direction: column; gap: 20px; }
  .row-meta-top { display: flex; flex-direction: column; gap: 10px; }
  .row-meta-top .year { color: var(--ink-68); }
  .row-meta-top .client { color: var(--accent); }
  .row-lock-icon {
    position: relative; width: 27px; height: 27px; flex: none; border: 1px solid var(--accent); background: var(--bg); transform: rotate(45deg);
  }
  .row-lock-icon svg { position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%) rotate(-45deg); }

  .row-body { padding-left: 32px; min-width: 0; }
  .row-title-line { display: flex; flex-wrap: wrap; align-items: center; gap: 16px; }
  .row-title-line h3 { overflow-wrap: anywhere; }
  .prism-badge {
    display: inline-flex; align-items: center; gap: 8px; height: 28px; padding: 0 12px;
    background: var(--ink); border: 1px solid var(--ink); color: var(--dark-muted-85);
    font: 500 11px/1 var(--font-mono); letter-spacing: .14em; text-transform: uppercase;
  }
  .prism-dot { width: 8px; height: 8px; flex: none; border-radius: 50%; background: var(--prism); background-size: 200% 100%; animation: prism 6s linear infinite; }
  .row-desc { margin-top: 16px; color: var(--ink-60); max-width: 68ch; }
  .row-cta { margin-top: 24px; display: flex; align-items: center; gap: 12px; min-height: 44px; color: var(--accent); }
  .row-cta button.lock-cta {
    min-height: 24px; padding: 0; background: none; border: none; color: inherit; cursor: pointer; text-align: left;
  }

  /* Thumbnail: the case study's own hero image, cropped to its top like
     the hero itself. Locked rows start on a tiny (24px-wide) public copy
     of the protected hero, scaled up and blurred so only its colours read;
     the real image is swapped in once the password has been entered (see
     setLockThumb() in index.html), never served from a public URL. */
  .row-thumb {
    position: relative; overflow: hidden; aspect-ratio: 16/9; align-self: start; margin-left: 48px; margin-bottom: 32px;
    background: var(--ink-18);
  }
  /* Drawn as an overlay rather than a border/inset shadow on .row-thumb
     itself, which the image would paint over. */
  .row-thumb::after {
    content: ""; position: absolute; inset: 0; pointer-events: none;
    box-shadow: inset 0 0 0 1px var(--ink-18);
  }
  .row-thumb img {
    display: block; width: 100%; height: 100%; object-fit: cover; object-position: top;
    transition: transform .6s var(--ease), filter .6s var(--ease);
  }
  .row-thumb.is-locked img { filter: blur(14px) saturate(1.1); transform: scale(1.25); }
  /* Padlock over the blurred placeholder, in the homepage's diamond-lock
     shape. multiply blending makes it a deeper shade of whatever colours
     sit beneath it, so it reads as part of the image rather than a flat
     overlay. Fades out when setLockThumb() drops .is-locked. */
  .thumb-lock {
    position: absolute; left: 50%; top: 50%; z-index: 1; width: 27px; height: 27px;
    display: flex; align-items: center; justify-content: center;
    border: 1px solid currentColor; transform: translate(-50%,-50%) rotate(45deg);
    color: rgba(22,22,26,.32); mix-blend-mode: multiply; pointer-events: none;
    transition: opacity .6s var(--ease);
  }
  .thumb-lock svg { transform: rotate(-45deg); }
  .row-thumb:not(.is-locked) .thumb-lock { opacity: 0; }
  a.work-row:hover .row-thumb img, .work-row.is-unlocked:hover .row-thumb img { transform: scale(1.03); }

  .lock-panel { height: 0; overflow: hidden; transition: height .45s var(--ease); }
  .lock-form { padding-top: 8px; display: flex; align-items: stretch; max-width: 520px; flex-wrap: wrap; }
  .lock-form input {
    flex: 1 1 200px; min-width: 0; height: 48px; padding: 0 16px; border: 1px solid rgba(22,22,26,.55); border-right: none;
    background: transparent; color: var(--ink); font: 500 16px/1.35 var(--font-body);
  }
  .lock-form button {
    flex: none; height: 48px; padding: 0 20px; border: 1px solid var(--accent); background: var(--accent); color: var(--bg);
    cursor: pointer; font: 500 11px/1 var(--font-mono); letter-spacing: .16em; text-transform: uppercase;
  }
  .lock-form button:hover { background: var(--ink); border-color: var(--ink); }
  .lock-msg { margin-top: 10px; padding-bottom: 24px; color: var(--ink-68); min-height: 1.4em; }
  .lock-msg.is-error { color: #c0362c; }
  .lock-msg.is-success { color: var(--accent); }

  @media (max-width: 1023px) {
    .work-row { grid-template-columns: 72px minmax(0,1fr) minmax(160px,220px); }
    .row-body { padding-left: 20px; }
    .row-thumb { margin-left: 24px; }
  }
  @media (max-width: 767px) {
    /* padding-left/right stay at 24px here (not 0) to offset .work-row's
       own -24px bleed margin above - dropping them left the row's content
       flush with the bled box edge instead of aligned with .work-head. */
    .work-row { grid-template-columns: minmax(0,1fr); min-height: 0; padding: 24px 24px 28px; row-gap: 16px; }
    .row-body, .row-meta { padding-left: 0; min-width: 0; }
    .row-meta { flex-direction: row; align-items: center; gap: 14px; }
    .row-meta-top { flex-direction: row; gap: 10px; }
    .row-thumb { order: -1; margin: 0; }
  }

  /* ── Side quests: 3-across cards under the case-study rows ── */
  .quests { margin-top: 48px; }
  .quests-head { box-shadow: inset 0 1px 0 var(--ink-18); }
  /* The right-hand label is long enough to wrap on phones - keep the
     heading on one line and let the label wrap flush right instead. */
  .quests-head h2 { white-space: nowrap; }
  .quests-head span { text-align: right; }
  .quest-grid { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); gap: 0 32px; margin: 0 -16px; }
  .quest-card {
    display: flex; flex-direction: column; padding: 16px 16px 8px; text-decoration: none; color: inherit;
    transition: background .3s ease;
  }
  .quest-card:hover { background: rgba(43,43,240,.06); }
  .quest-card:hover h3 { color: var(--accent); }
  .quest-card:hover .quest-desc { color: var(--ink-68); }
  .quest-card h3 { margin-top: 24px; overflow-wrap: anywhere; }
  .quest-thumb { position: relative; overflow: hidden; aspect-ratio: 4/3; background: var(--ink-09); }
  /* Overlay border, same as .row-thumb, so an image can't paint over it. */
  .quest-thumb::after { content: ""; position: absolute; inset: 0; pointer-events: none; box-shadow: inset 0 0 0 1px var(--ink-18); }
  .quest-thumb img { display: block; width: 100%; height: 100%; object-fit: cover; transition: transform .6s var(--ease); }
  .quest-card:hover .quest-thumb img { transform: scale(1.03); }
  .quest-placeholder {
    position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
    background: repeating-linear-gradient(135deg, rgba(120,120,140,.10) 0 10px, rgba(120,120,140,.04) 10px 20px);
    color: var(--ink-68); font: 500 11px/1 var(--font-mono); letter-spacing: .1em; text-transform: uppercase;
  }
  .quest-desc { margin-top: 12px; color: var(--ink-60); }
  .quest-card .row-cta { margin-top: auto; padding-top: 16px; }
  @media (max-width: 1023px) { .quest-grid { gap: 0 16px; } }
  @media (max-width: 767px) {
    .quest-grid { grid-template-columns: minmax(0,1fr); gap: 16px; margin: 0 -24px; }
    .quest-card { padding: 16px 24px 8px; }
  }

  .brings-divider {
    position: relative; overflow: hidden; display: flex; align-items: center; justify-content: center; height: 96px;
  }
  .brings-divider .line { position: absolute; left: 0; right: 0; top: 48px; height: 1px; background: linear-gradient(to right, transparent, rgba(22,22,26,.35) 30%, rgba(22,22,26,.35) 70%, transparent); }
  .brings-divider .chip { position: relative; display: flex; align-items: center; gap: clamp(12px,2vw,18px); background: var(--bg); padding: 0 clamp(20px,3vw,32px); color: var(--ink-68); }
  .brings-divider .dot { width: 10px; height: 10px; flex: none; background: var(--accent); transform: rotate(45deg); }

  /* ───────────────────────── Skills ─────────────────────────
     Tag-field layout: each discipline is a wrapping row of chips rather
     than a vertical bulleted list, so the column doesn't read as a footer
     nav list at a glance. */
  #skills { padding-bottom: 96px; }
  .skills-grid {
    position: relative; padding: 96px var(--gutter) 0; display: grid; grid-template-columns: 2fr 8fr; gap: 0 32px;
  }
  .skills-grid h2 { color: var(--accent); }
  .skills-intro-note { margin-top: 14px; color: var(--ink-68); }
  .skill-groups { display: flex; flex-direction: column; gap: 36px; }
  .skill-group h3 { margin-bottom: 18px; }
  .skill-tags { display: flex; flex-wrap: wrap; gap: 10px; }
  .skill-tags span {
    padding: 10px 14px; background: rgba(22,22,26,.05);
    color: rgba(22,22,26,.78); font: 400 14px/1 var(--font-body);
  }

  @media (max-width: 767px) { .skills-grid { grid-template-columns: 1fr; gap: 32px; } }

  /* ───────────────────────── Contact (footer) ───────────────────────── */
  #contact { background: var(--accent); color: var(--bg); }
  .contact-grid-layer {
    background-image: linear-gradient(to right, var(--dark-muted-30) 0 1px, transparent 1px), linear-gradient(to bottom, rgba(244,243,239,.22) 0 1px, transparent 1px);
    mask-image: radial-gradient(ellipse 55% 80% at 25% 50%, #000 0%, transparent 85%);
    -webkit-mask-image: radial-gradient(ellipse 55% 80% at 25% 50%, #000 0%, transparent 85%);
  }
  .contact-content { position: relative; min-height: 320px; padding: 96px var(--gutter); display: grid; grid-template-columns: repeat(10,1fr); gap: 32px; align-items: center; }
  .contact-copy { grid-column: 1 / 7; }
  .contact-links {
    grid-column: 8 / 11; height: 192px; display: flex; flex-direction: column; justify-content: center; align-items: flex-end; gap: 16px;
    padding-left: 20px;
  }
  .contact-btn {
    width: fit-content; height: 48px; padding: 0 24px; display: inline-flex; align-items: center; gap: 8px;
    background: var(--bg); color: var(--ink); border: 1px solid var(--bg); text-decoration: none;
    transition: background .2s, color .2s, border-color .2s;
  }
  .contact-btn:hover { background: transparent; color: var(--bg); border-color: var(--bg); }
  .contact-credit { position: absolute; left: 0; right: 0; bottom: 24px; text-align: center; color: var(--dark-muted-85); }
  @media (max-width: 1023px) {
    .contact-content { grid-template-columns: minmax(0,1fr); gap: 40px; align-items: start; }
    .contact-copy, .contact-links { grid-column: auto; }
    .contact-links { height: auto; border-left: none; border-top: 1px solid rgba(244,243,239,.35); padding-left: 0; padding-top: 24px; align-items: flex-start; }
    .contact-credit { left: var(--gutter); right: auto; text-align: left; }
  }

  @media (max-width: 479px) {
    .grid-layer, .hero-node { display: none !important; }
  }
