/* ==========================================================================
   amsudesigns.com — styles.css
   First slice: fonts, tokens, type scale, grid, header + Work flyout.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Fonts — Geist, self-hosted woff2 subsets.
   Subset to U+0020–007E plus U+00E9 (é). Note: "basic Latin" alone
   (U+0000–007F) does NOT include é — see the flags in the handoff notes.
   Subsetting command (glyphhanger or pyftsubset), run once per file:
     pyftsubset Geist-Regular.ttf --unicodes="U+0020-007E,U+00E9" \
       --flavor=woff2 --output-file=Geist-Regular.woff2
   -------------------------------------------------------------------------- */

@font-face {
  font-family: "Geist";
  src: url("fonts/Geist-Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0020-007E, U+00E9;
}

@font-face {
  font-family: "Geist";
  src: url("fonts/Geist-Medium.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0020-007E, U+00E9;
}

@font-face {
  font-family: "Geist Mono";
  src: url("fonts/GeistMono-Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0020-007E, U+00E9;
}

/* --------------------------------------------------------------------------
   2. Tokens
   -------------------------------------------------------------------------- */

:root {
  /* Color */
  --paper:   #F5F6F2;
  --ink:     #26242B;
  --ink-60:  rgba(38, 36, 43, 0.60);
  --ink-40:  rgba(38, 36, 43, 0.40);
  --ink-12:  rgba(38, 36, 43, 0.12);
  --ink-06:  rgba(38, 36, 43, 0.06);

  --accent-guardian:    #75D18C;
  --accent-shortlist:   #885EFA;
  --accent-urbangreens: #A9718C;
  --accent-underarmour: #1A1A1A; /* UA is black/white; accent is near-ink */
  --accent-copilot:     #2563EB; /* Browser Copilot blue */

  /* Text-safe accent variants — same hue family, dark enough for small
     text on paper (all measure 4.5:1 or better). Guardian's is sampled
     from the work-section mock. */
  --accent-guardian-text:    #246B36;
  --accent-shortlist-text:   #6A3FD8;
  --accent-urbangreens-text: #8A4E6B;
  --accent-copilot-text:     #1D4ED8; /* hover/darker blue, 6.2:1 on paper */

  /* Work section */
  --work-gray: #D4D6DD;
  --work-rail-w: 360px;
  --work-card-w: clamp(300px, 30vw, 412px); /* shrinks at tighter pinned-layout
    widths instead of staying a fixed 412px — keeps its right edge clear of
    the preview video (see .work.is-pinned .work-panel's padding-left below,
    which is computed from this same value so the two can never collide) */
  --work-card-gap: 32px; /* minimum gray buffer kept between the card's right
    edge and the video's left edge at every width */
  --work-bottom-gap: clamp(8px, calc(73vh - 508px), 96px); /* the pinned
    frame's bottom margin, shared by the info card and the preview video
    below so their bottom edges always align. Shrinks on short screens — rather than
    staying a flat 96px — to give the growing, bottom-anchored card (now
    three entries tall) more headroom against the top-anchored rail tabs */

  /* Type */
  --font-sans: Geist, "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-mono: "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace;

  /* Spacing scale — 8px base */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 48px;
  --space-8: 64px;
  --space-9: 96px;
  --space-10: 128px;
  --space-11: 160px;

  /* Layout — phone values first, overridden per breakpoint */
  --margin: 20px;
  --gutter: 16px;
  --cols: 4;
  --container-max: none;
  --section-pad: 64px;

  /* Motion */
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --dur-ui: 200ms;
  --dur-reveal: 400ms;
}

@media (min-width: 768px) {
  :root {
    --margin: 40px;
    --gutter: 20px;
    --cols: 8;
    --section-pad: 96px;
  }
}

@media (min-width: 1025px) {
  :root {
    --margin: 48px;
    --gutter: 20px;
    --cols: 12;
    --section-pad: 128px;
  }
}

@media (min-width: 1280px) {
  :root {
    --margin: 80px;
    --gutter: 24px;
    --container-max: 1440px;
  }
}

/* --------------------------------------------------------------------------
   3. Base
   -------------------------------------------------------------------------- */

*,
*::before,
*::after {
  box-sizing: border-box;
  border-radius: 0; /* radius is 0 everywhere — enforced globally */
}

html {
  scroll-behavior: smooth;
  scroll-padding-top: 24px; /* no header bar — small breathing space above anchor targets */
  overflow-x: clip; /* body's overflow-x:clip below doesn't contain position:fixed
    elements (.work-flyout on phone, .work-cursor) since they escape to the
    viewport/initial containing block rather than body's box — clip here too
    so nothing fixed can ever open up a horizontal scrollbar. (.nav-bar used to
    be in that list too, but it's position:sticky/absolute now — see 7.6 —
    so it's contained by body's own box like any normal in-flow element.) */
}

button,
input,
select,
textarea {
  font: inherit; /* buttons otherwise ignore the page font */
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: 15px;
  line-height: 24px;
  -webkit-font-smoothing: antialiased;
  overflow-x: clip; /* no horizontal scroll at any width */
}

body.is-locked {
  overflow: hidden; /* while the menu overlay is open */
}

img,
video {
  max-width: 100%;
  display: block;
}

/* Focus — 2px ink outline, 2px offset, square. Never removed. */
:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

/* Links — never blue, never a weight change on hover */
a {
  color: var(--ink);
  text-decoration: none;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

.skip-link {
  position: absolute;
  top: 8px;
  left: 8px;
  z-index: 100;
  padding: 12px 16px;
  background: var(--paper);
  outline-offset: 0;
  transform: translateY(-200%);
}
.skip-link:focus-visible {
  transform: none;
}

/* --------------------------------------------------------------------------
   4. Type scale — utility classes
   Phone values first; tablet and desktop override.
   -------------------------------------------------------------------------- */

.type-statement {
  font-size: 27px;
  line-height: 32px;
  font-weight: 500;
  letter-spacing: -0.02em;
}
.type-section-head {
  font-size: 20px;
  line-height: 26px;
  font-weight: 500;
  letter-spacing: -0.015em;
}
.type-project-title {
  font-size: 18px;
  line-height: 24px;
  font-weight: 500;
  letter-spacing: -0.01em;
}
.type-body-lg {
  font-size: 16px;
  line-height: 26px;
  font-weight: 400;
}
.type-body {
  font-size: 15px;
  line-height: 24px;
  font-weight: 400;
  max-width: 62ch; /* body measure caps at 62 characters */
}
.type-nav {
  font-size: 16px;
  line-height: 20px;
  font-weight: 400;
}
.type-caption {
  font-size: 13px;
  line-height: 20px;
  font-weight: 400;
  color: var(--ink-60);
}
.type-meta {
  font-family: var(--font-mono);
  font-size: 11px;
  line-height: 14px;
  font-weight: 400;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

@media (min-width: 768px) {
  .type-statement    { font-size: 34px; line-height: 40px; }
  .type-section-head { font-size: 22px; line-height: 28px; }
  .type-project-title{ font-size: 20px; line-height: 26px; }
  .type-nav          { font-size: 14px; line-height: 18px; }
}

@media (min-width: 1280px) {
  .type-statement    { font-size: 44px; line-height: 52px; }
  .type-section-head { font-size: 24px; line-height: 30px; }
  .type-project-title{ font-size: 24px; line-height: 30px; }
  .type-nav          { font-size: 13px; line-height: 16px; }
}

/* --------------------------------------------------------------------------
   5. Grid
   .container centers content inside the page margins.
   .grid lays out the column grid inside a container.
   -------------------------------------------------------------------------- */

.container {
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: var(--margin);
}

.grid {
  display: grid;
  grid-template-columns: repeat(var(--cols), minmax(0, 1fr));
  column-gap: var(--gutter);
}

.section {
  padding-block: var(--section-pad);
}
/* --------------------------------------------------------------------------
   6. Nav — text links, top right, no boxes.
   Directional: hides upward on scroll down, returns on scroll up
   (200ms, never within the first 200px, pinned while the dropdown
   is open, never hidden under reduced motion).
   -------------------------------------------------------------------------- */

:root {
  --nav-resume:  var(--ink);
  --nav-contact: #F7E9C2;
  --nav-about:   #D9F7EA;
  --nav-work:    #F4DAF5;
}

/* Nav text + logo are flat --ink, no background on the bar itself — plain
   text straight over whatever's behind it.

   .nav-bar-stick (wrapping the real <nav> in the HTML) is what's pinned to
   the viewport, not .nav-bar itself — left over from an earlier attempt at
   a mix-blend-mode: difference version of this (which needed a Safari
   workaround), but position:sticky + an absolutely-positioned inner bar
   behaves identically to position:fixed, so there's no reason to change
   it back. */
.nav-bar-stick {
  position: sticky;
  top: 0;
  height: 0;
  z-index: 50;
}
.nav-bar {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 32px;
  padding: 16px var(--margin) 0;
  transform: translateY(0);
  transition: transform var(--dur-ui) var(--ease);
}

.nav-bar.is-hidden {
  transform: translateY(-150%);
}

.nav-item--work {
  position: relative;
}

.nav-link {
  position: relative;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0;
  background: none;
  border: 0;
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 20px;
  font-weight: 400;
  color: var(--ink);
  cursor: pointer;
  transition: transform 200ms var(--ease);
}

@media (min-width: 768px)  { .nav-link { font-size: 14px; line-height: 18px; } }
@media (min-width: 1280px) { .nav-link { font-size: 13px; line-height: 16px; } }
@media (max-width: 767px)  { .nav-link { font-size: 13px; line-height: 16px; } }

/* Hover — the word nudges right, echoing the magnet lean elsewhere. */
.nav-link:hover,
.nav-link:focus-visible {
  transform: translateX(4px);
}

@media (prefers-reduced-motion: reduce) {
  .nav-link { transition: none; }
  .nav-link:hover,
  .nav-link:focus-visible { transform: none; }
}

.nav-mark {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  margin-right: auto; /* pushes the mark to the left corner; links stay right */
  font-family: var(--font-sans);
  font-size: 24px;
  line-height: 1;
  font-weight: 500;
  letter-spacing: -0.02em;
  color: var(--ink);
}

/* "as." expands to "amelija s." on hover/focus — each letter of "melija "
   sits in its own span, collapsed to nothing (max-width: 0, overflow:
   hidden) until script.js adds .is-visible, one letter at a time, with a
   small stagger between them (see the matching JS block). The "a" and "s."
   around it are plain text, so they just get pushed right as the letters
   grow in — no JS positioning needed for those. max-width is deliberately
   larger than any single character needs: the span still only takes up
   its content's actual width, this just guarantees nothing clips. */
.nav-mark-letter {
  display: inline-block;
  overflow: hidden;
  max-width: 0;
  opacity: 0;
  vertical-align: bottom;
  transition: max-width 200ms var(--ease), opacity 150ms var(--ease);
}
.nav-mark-letter.is-visible {
  max-width: 32px;
  opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
  .nav-mark-letter {
    transition: none;
  }
}



.nav-link.is-current,
.work-trigger[aria-expanded="true"] {
  font-weight: 500;
  color: var(--ink);
}

/* --------------------------------------------------------------------------
   7. Work dropdown — C-02, unchanged inside; drops below the Work item
   -------------------------------------------------------------------------- */

:root {
  --flyout-w: 320px;
}

.work-trigger {
  white-space: nowrap;
}

.work-flyout {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  width: min(var(--flyout-w), calc(100vw - 2 * var(--margin)));
  background: var(--paper);
  border: 1px solid var(--ink-12);
}

@media (max-width: 767px) {
  .work-flyout {
    position: fixed;
    top: 64px; /* clears .nav-bar (16px pad + 44px row = 60px) + a small gap */
    left: var(--margin);
    right: var(--margin);
    width: auto;
  }
}

.flyout-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* Rows are flush — no margins, no separators. The breathing space lives
   inside each link so a hover fill covers its full section with no gaps. */
.flyout-link {
  display: grid;
  grid-template-columns: 136px 1fr; /* fixed name column so every desc starts at the same x */
  align-items: center;
  min-height: 44px;
  padding: 10px 16px; /* fill runs edge to edge, rows touch */
  transition: background-color var(--dur-ui) var(--ease);
}

.flyout-name {
  white-space: nowrap;
  font-size: 16px;
  line-height: 24px;
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--ink); /* stays ink on the accent fill */
}

.flyout-desc {
  font-family: var(--font-mono);
  font-size: 11px;
  line-height: 14px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-60);
}

/* Hovering a name fills its row with that project's accent; name stays ink */
.flyout-row[data-accent="guardian"]    .flyout-link:hover,
.flyout-row[data-accent="guardian"]    .flyout-link:focus-visible { background: var(--accent-guardian); }
.flyout-row[data-accent="shortlist"]   .flyout-link:hover,
.flyout-row[data-accent="shortlist"]   .flyout-link:focus-visible { background: var(--accent-shortlist); }
.flyout-row[data-accent="urbangreens"] .flyout-link:hover,
.flyout-row[data-accent="urbangreens"] .flyout-link:focus-visible { background: var(--accent-urbangreens); }
.flyout-row[data-accent="underarmour"] .flyout-link:hover,
.flyout-row[data-accent="underarmour"] .flyout-link:focus-visible { background: var(--accent-underarmour); }
.flyout-row[data-accent="copilot"]     .flyout-link:hover,
.flyout-row[data-accent="copilot"]     .flyout-link:focus-visible { background: var(--accent-copilot); }

/* The dark fills flip their name to white on hover (matching the
   selected section rail buttons); guardian stays ink */
.flyout-row[data-accent="shortlist"]   .flyout-link:hover .flyout-name,
.flyout-row[data-accent="shortlist"]   .flyout-link:focus-visible .flyout-name,
.flyout-row[data-accent="urbangreens"] .flyout-link:hover .flyout-name,
.flyout-row[data-accent="urbangreens"] .flyout-link:focus-visible .flyout-name,
.flyout-row[data-accent="underarmour"] .flyout-link:hover .flyout-name,
.flyout-row[data-accent="underarmour"] .flyout-link:focus-visible .flyout-name,
.flyout-row[data-accent="copilot"]     .flyout-link:hover .flyout-name,
.flyout-row[data-accent="copilot"]     .flyout-link:focus-visible .flyout-name {
  color: var(--paper);
}

/* Under Armour's "In progress" desc flips white on hover too, matching its name */
.flyout-row[data-accent="underarmour"] .flyout-link:hover .flyout-desc,
.flyout-row[data-accent="underarmour"] .flyout-link:focus-visible .flyout-desc {
  color: var(--paper);
}

.flyout-row--experiments .flyout-link:hover {
  background: var(--ink-06);
}

/* Open animation — names fade and rise 8px, staggered 40ms */
.work-flyout:not([hidden]):not(.is-closing) .flyout-row {
  animation: flyout-rise 220ms var(--ease) both;
}
.work-flyout:not([hidden]):not(.is-closing) .flyout-row:nth-child(1) { animation-delay: 0ms; }
.work-flyout:not([hidden]):not(.is-closing) .flyout-row:nth-child(2) { animation-delay: 40ms; }
.work-flyout:not([hidden]):not(.is-closing) .flyout-row:nth-child(3) { animation-delay: 80ms; }
.work-flyout:not([hidden]):not(.is-closing) .flyout-row:nth-child(4) { animation-delay: 120ms; }
.work-flyout:not([hidden]):not(.is-closing) .flyout-row:nth-child(5) { animation-delay: 160ms; }
.work-flyout:not([hidden]):not(.is-closing) .flyout-row:nth-child(6) { animation-delay: 200ms; }

@keyframes flyout-rise {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Close animation — the reverse: rows fade out and sink 8px, with the
   stagger mirrored so the last row leaves first */
.work-flyout.is-closing .flyout-row {
  animation: flyout-sink 220ms var(--ease) both;
}
.work-flyout.is-closing .flyout-row:nth-child(6) { animation-delay: 0ms; }
.work-flyout.is-closing .flyout-row:nth-child(5) { animation-delay: 40ms; }
.work-flyout.is-closing .flyout-row:nth-child(4) { animation-delay: 80ms; }
.work-flyout.is-closing .flyout-row:nth-child(3) { animation-delay: 120ms; }
.work-flyout.is-closing .flyout-row:nth-child(2) { animation-delay: 160ms; }
.work-flyout.is-closing .flyout-row:nth-child(1) { animation-delay: 200ms; }

@keyframes flyout-sink {
  from { opacity: 1; transform: translateY(0); }
  to   { opacity: 0; transform: translateY(8px); }
}

/* --------------------------------------------------------------------------
   7.5 Hero — scattered project images with magnet hover + tracking lines
   Connector line colors come straight from each project's own accent
   tokens (see initMagnetConstellation's "connections" config in script.js)
   rather than a fixed palette defined here.
   -------------------------------------------------------------------------- */

.hero {
  position: relative;
  min-height: 100vh;
  margin-bottom: 160px; /* air between the hero scatter and the work section */
}

.hero-greeting {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 3; /* above the connector lines */
  margin: 0;
  font-size: 16px;
  line-height: 22px;
  font-weight: 400;
  text-align: center;
  white-space: nowrap;
}

/* Two copies of the greeting text, one per breakpoint — the laptop mock
   reads as 2 lines, the phone mock as 4 (its own, shorter line breaks).
   Default/desktop: only the 2-line copy shows. */
.hero-greeting-mobile { display: none; }

.hero-stage {
  position: absolute;
  inset: 0;
}

.hero-lines {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 1;
}

/* initMagnetConstellation tags each connector line's <g> with
   mc-line--desktop or mc-line--mobile when the call site opts in (the hero's
   two layouts need different lines; About-skills' lines are untagged and
   always show). Default to desktop since that's the wider breakpoint. Has
   to track the hero's own stacked-layout breakpoint below (1024px, not
   767px) — otherwise tablet widths render the stacked tiles with the
   desktop set of 4 crossing lines still showing on top of the mobile set's
   3, since neither toggle has fired yet, which reads as a tangle of too
   many lines. */
.mc-line--mobile { display: none; }
@media (max-width: 1024px) {
  .mc-line--desktop { display: none; }
  .mc-line--mobile { display: inline; }
}

.hero-item {
  position: absolute;
  display: block;
  z-index: 2;
}

.hero-item-inner {
  display: block;
  position: relative; /* anchors the overlay to this box, so it rides along with the magnet transform below instead of the image floating free of it */
  width: 100%;
  height: auto;
  overflow: hidden; /* clips the hover zoom below to the tile's own frame, same as .work-preview */
  box-shadow: 0 4px 4px rgba(10, 9, 13, 0.10); /* same as the work card */
  transition: transform 0.5s ease-in-out, box-shadow 250ms var(--ease); /* inactive spring-back (per Magnet) */
  will-change: transform;
}

.hero-item-media {
  display: block;
  width: 100%;
  height: auto;
  object-fit: cover; /* no-op for the plain photos (box already matches their ratio); crops the Guardian video (see #hero-guardian below) into the same frame */
  transition: transform 400ms var(--ease); /* hover zoom, see below — same timing as .work-preview-media */
}

/* The Guardian tile's photo is now a looping video shot at 4:3 (see
   guardian-layer-thumb.mp4), not the original image's ~1.7:1 crop. Pin the
   tile back to that original ratio so the scattered hero layout (tuned
   around these proportions) and the magnet connector-line math don't
   shift, and object-fit above crops the video to fill it. */
#hero-guardian .hero-item-media {
  aspect-ratio: 357 / 210;
  height: auto;
}

.hero-item.is-magnet-active .hero-item-inner {
  transition: transform 0.3s ease-out, box-shadow 250ms var(--ease); /* active chase (per Magnet) */
}

/* Hover/focus — a quiet lift: the media zooms in slightly (clipped by
   .hero-item-inner's overflow: hidden above) and the shadow deepens a
   little, the same treatment the Work cards use. Replaces the old
   accent-color wash + name/label overlay, which read as too busy now
   that the tile itself has video playing in it. Skipped on the static
   Under Armour tile, which has no .hero-item-media. */
.hero-item:hover .hero-item-media,
.hero-item:focus-visible .hero-item-media,
.hero-item.is-touch-active .hero-item-media {
  transform: scale(1.04);
}
.hero-item:hover .hero-item-inner,
.hero-item:focus-visible .hero-item-inner,
.hero-item.is-touch-active .hero-item-inner {
  box-shadow: 0 10px 20px rgba(10, 9, 13, 0.18);
}

/* Positions and sizes from the mock (percent of the stage) */
/* Four corners: the connector lines close into a square around the greeting.
   Guardian sits a bit lower and Copilot a bit higher than their neighbors —
   deliberately uneven so the scatter doesn't read as a tidy grid. */
#hero-guardian    { left: 13.0%; top: 21.0%; width: 23.6%; } /* top-left */
#hero-underarmour { left: 65.5%; top: 19.5%; width: 20.5%; } /* top-right */
#hero-shortlist   { left: 15.5%; top: 58.5%; width: 24.0%; } /* bottom-left */
#hero-copilot     { left: 63.0%; top: 57.0%; width: 22.0%; } /* bottom-right */

/* Phone + tablet: a left-aligned heading followed by the four tiles stacked
   in a simple staircase (alternating left/right), each joined to the next by
   one connector line, replacing the laptop's scattered-corners-around-
   centered-text layout. Runs through 1024px, same threshold as the Work
   section's stacked layout (see .work-block above) — the desktop scatter's
   tile positions are hand-tuned percentages of the stage (see the four
   corner rules above) that only work with room for the tiles to spread out
   without colliding with each other or the centered greeting text, which an
   iPad-width viewport doesn't have. Visual order is Guardian, Shortlist,
   Copilot, Under Armour — Under Armour last since it's the in-progress one —
   but the DOM order (Guardian, Under Armour, Shortlist, Copilot) stays put so
   the desktop constellation's indices are untouched; .hero-stage becomes a
   column flex container and each tile gets an `order` to resequence it
   visually. .hero-lines stays absolute/inset:0 so it keeps stretching to
   cover whatever height that flow produces, and initMagnetConstellation's
   getBoundingClientRect()-based line-drawing doesn't care either way.
   Percentages below are read off the phone mock, now against the same
   edge-to-edge content column every other section uses (.hero gets
   padding-inline: var(--margin) directly, the same --margin token as the
   nav/About/Work grids), not a fixed 480px cap — that cap kept the whole
   column phone-width and centered even on an iPad-wide viewport, which
   read as "not left-aligned" (dead space on both sides) and squeezed the
   tiles' percentage widths against an artificially narrow box, so neighbors
   sat close together instead of spreading out with the wider viewport. */
@media (max-width: 1024px) {
  .hero {
    min-height: 0;
    max-width: var(--container-max); /* "none" below 1025px (see :root above) — full width until the desktop container cap kicks in */
    margin: 0 auto 48px;
    padding-top: 158px; /* clears the fixed nav (~60px tall) with generous breathing room —
                            padding, not margin: .nav-bar-stick right above this is an empty
                            0-height box (see 7.6), so a margin-top here would collapse straight
                            through it and push the sticky nav itself down the page instead of
                            creating space below it. Padding can't collapse, so it can't do that. */
    padding-inline: var(--margin); /* same edge inset as .container (see 3. Base) — left-aligns the greeting and gives .hero-stage's percentage-based tile positions a wider box to spread across */
  }
  .hero-greeting {
    position: static;
    top: auto;
    left: auto;
    transform: none;
    width: auto;
    max-width: none;
    margin: 0;
    font-size: 15px;
    line-height: 21px;
    text-align: left;
    white-space: normal;
  }
  /* The laptop's 2-line copy, not the phone's old 4-line one — it matches
     the mock's heading exactly and reads better left-aligned. */
  .hero-greeting-desktop { display: block; }
  .hero-greeting-mobile { display: none; }

  .hero-stage {
    position: relative;
    inset: auto;
    display: flex;
    flex-direction: column;
    margin-top: 40px; /* gap below the heading */
  }
  .hero-item {
    position: relative;
    display: block;
    margin-top: 40px; /* gap above each tile; overridden per-tile below. Flex items don't collapse margins, so this is a clean gap even though .hero-guardian (first in visual order) also gets one — reset to 0 there. */
  }
  #hero-guardian {
    top: auto; left: auto; width: 66%; margin-left: 5%; margin-top: 0; order: 1;
  }
  #hero-shortlist {
    top: auto; left: auto; width: 57%; margin-left: 38%; order: 2;
  }
  #hero-copilot {
    top: auto; left: auto; width: 52%; margin-left: 12%; margin-top: 32px; order: 3;
  }
  #hero-underarmour {
    top: auto; left: auto; width: 42%; margin-left: 45%; order: 4;
  }
}

/* --------------------------------------------------------------------------
   7.6 Homepage load-in — the four project tiles pop in first (staggered),
   then the nav (logo + menu), then the greeting text, last. Scoped to
   body.has-hero (set in the HTML, so the hidden start-state is present at
   first paint — no flash of the fully-visible page first) and gated on
   .is-entered, which script.js adds two frames after the window's `load`
   event (not immediately after parse) so the sequence starts once the hero
   photos are actually decoded and ready to paint — otherwise the fade can
   finish before a slow-loading image ever appears, which reads as an
   abrupt pop instead of a fade. Durations/delays are intentionally
   unhurried (~1.7s start-to-finish) for the same reason: slow enough that
   it reads clearly as a fade-in rather than something you can blink
   through. Skipped entirely under reduced motion (elements just stay
   visible), and <noscript> in the HTML forces everything visible if JS
   never runs.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  body.has-hero .hero-item {
    opacity: 0;
    transform: scale(0.92);
    transition: opacity 750ms var(--ease), transform 750ms var(--ease);
  }
  body.has-hero.is-entered .hero-item {
    opacity: 1;
    transform: scale(1);
  }
  body.has-hero #hero-guardian    { transition-delay: 0ms; }
  body.has-hero #hero-underarmour { transition-delay: 140ms; }
  body.has-hero #hero-shortlist   { transition-delay: 280ms; }
  body.has-hero #hero-copilot     { transition-delay: 420ms; }

  /* No entrance fade on .nav-bar itself (unlike the hero items/greeting
     below) — it just appears at full opacity as soon as .is-entered lands;
     still slides off on scroll via .is-hidden below, which only touches
     transform. */

  /* The greeting no longer fades in as one block — script.js wraps every
     character of .hero-greeting-desktop/.hero-greeting-mobile in its own
     .hero-greeting-letter span (same technique as .nav-mark-letter on the
     logo) and sets a staggered transition-delay on each, so the line
     cascades in left to right. .hero-greeting itself just needs to stay
     visible so its letters have something to fade in on top of. */
  body.has-hero .hero-greeting-letter {
    display: inline-block;
    opacity: 0;
    transform: translateY(4px);
    transition: opacity 180ms var(--ease), transform 180ms var(--ease);
  }
  body.has-hero.is-entered .hero-greeting-letter {
    opacity: 1;
    transform: none;
  }
}

/* --------------------------------------------------------------------------
   8. Work selector — C-05
   Default: four stacked blocks (no-JS, reduced-motion, and phone).
   .is-pinned (added by script.js on wider screens): a tall section with a
   sticky full-height frame; scrolling through four segments drives the
   selection. Native scroll only — nothing is hijacked.
   -------------------------------------------------------------------------- */

.work {
  --segment: 90vh;
  padding-block: var(--section-pad);
  border-top: 1px solid var(--ink-12);    /* hairline at the top of the gray */
  border-bottom: 1px solid var(--ink-12); /* hairline at the bottom of the gray */
}

.work-eyebrow {
  margin: 0 0 64px;
}

/* ---- Stacked blocks (default) ---- */

.work-block {
  margin-top: 64px;
}
.work-block:first-child {
  margin-top: 0;
}

.work-block-title {
  margin: 0 0 16px;
}

/* Project type + problem live in one paper card — same border + shadow
   language as .cs-card in the case studies — instead of running together
   as plain text. .work-detail-card just groups each label/value pair so
   the 16px gap lands between the two groups, not between every line; it
   carries no styling of its own (the card look lives on this box, not the
   groups inside it — the enhanced .work.is-pinned layout below already
   wraps the same markup in its own overlay card). */
.work-block-details {
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 24px 24px 28px;
  background: var(--paper);
  border: 1px solid var(--ink-40);
  box-shadow: 0 4px 4px rgba(10, 9, 13, 0.10);
}

.work-detail-label {
  margin: 0 0 4px;
  color: var(--ink-60);
}

/* Per-project accent on the PROJECT TYPE / PROBLEM labels — dark
   text-safe variants, per the mock (all clear 4.5:1 on paper) */
.work-block[data-accent="guardian"]    .work-detail-label { color: var(--accent-guardian-text); }
.work-block[data-accent="shortlist"]   .work-detail-label { color: var(--accent-shortlist-text); }
.work-block[data-accent="urbangreens"] .work-detail-label { color: var(--accent-urbangreens-text); }
.work-block[data-accent="underarmour"] .work-detail-label { color: var(--accent-underarmour); }
.work-block[data-accent="copilot"]     .work-detail-label { color: var(--accent-copilot-text); }

.work-detail-value {
  margin: 0;
  font-size: 15px;
  line-height: 24px;
}

.work-preview {
  display: block;
  margin-top: 24px;
}

.work-preview {
  overflow: hidden; /* clip the zoom so it scales within the frame */
  box-shadow: 0 4px 4px rgba(10, 9, 13, 0.10); /* same baseline as the hero tiles */
  transition: box-shadow 250ms var(--ease);
}
.work-preview-media {
  display: block;
  width: 100%;              /* needed for the Guardian card's <video> — a replaced element,
                                unlike the plain <span> tiles, doesn't stretch to fill width on its own */
  height: auto;             /* the Under Armour card's <img> carries width/height="1600"/"900"
                                HTML attributes (for its intrinsic-size layout hint); those map to a
                                low-priority presentational height that this rule must still beat
                                explicitly — aspect-ratio alone doesn't override an attribute-set
                                height, so without this the image rendered at a literal 900px tall
                                instead of scaling with its 740px-wide card, dwarfing the other three
                                video-based previews which have no such attributes */
  aspect-ratio: 16 / 9;     /* same ratio at every width, matching the Under Armour reveal card */
  background: var(--ink-06);
  background-size: cover;   /* real mockups fill the frame */
  background-position: center;
  object-fit: cover;        /* same "fill the frame" behavior, for the Guardian card's <video> */
  object-position: center;
  transition: transform 400ms var(--ease);
}
.work-preview:hover .work-preview-media,
.work-preview:focus-visible .work-preview-media,
.work-preview.is-touch-active .work-preview-media {
  transform: scale(1.04);
}
/* Hover/focus — same quiet lift as the hero tiles: the shadow deepens to
   match (see .hero-item-inner in the hero section above). */
.work-preview:hover,
.work-preview:focus-visible,
.work-preview.is-touch-active {
  box-shadow: 0 10px 20px rgba(10, 9, 13, 0.18);
}

/* Phone-only "View project" button, sitting right under each heading. On
   larger screens the whole preview image is already the click target,
   with .work-cursor's hover label doing this same job — phones get an
   explicit button instead since there's no hover state to reveal it.
   Hidden by default; the phone media query below turns it on. Colors
   mirror .work-cursor's per-project treatment: light accents (Guardian's
   green) keep dark ink type, darker/more saturated ones (Shortlist's
   purple, Copilot's blue) flip to paper-white type. */
.work-mobile-cta {
  display: none;
}
.work-mobile-cta[data-accent="guardian"]  { background: var(--accent-guardian);  color: var(--ink); }
.work-mobile-cta[data-accent="shortlist"] { background: var(--accent-shortlist); color: var(--paper); }
.work-mobile-cta[data-accent="copilot"]   { background: var(--accent-copilot);   color: var(--paper); }

/* Phone + tablet: this is the default stacked layout (.work never gets
   .is-pinned below 1025px — see script.js; the pinned overlay card is
   sized for the desktop rail and overlaps too much of the preview image
   at narrower widths), title → image → cards instead of title → cards →
   image, so the project photo reads first. */
@media (max-width: 1024px) {
  .work-block {
    display: flex;
    flex-direction: column;
  }
  .work-mobile-cta {
    display: inline-flex;
    align-items: center;
    min-height: 40px;
    width: fit-content;
    padding: 0 18px;
    margin: 8px 0 0;
    font-size: 14px;
    line-height: 18px;
    font-weight: 500;
  }
  .work-panel {
    order: 1; /* .work-preview's own margin-top:24px still applies — same gap, now below the title instead */
  }
  .work-block-details {
    order: 2;
    margin-top: 24px; /* same gap, now sits below the image instead */
  }
}

/* Real project preview images */
.work-preview-media--guardian   { background-image: url("images/work-guardian.jpg"); }

.work-preview-media--shortlist  { background-image: url("images/work-shortlist.jpg"); }
.work-preview-media--urbangreens{ background-image: url("images/work-urbangreens.jpg"); }
.work-preview-media--copilot    { background-image: url("images/work-browsercopilot.jpg"); }


/* ---- Rail buttons (exist only when enhanced) ---- */

.work-tab {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: flex-start; /* labels flush left in the equal-width boxes */
  height: 36px;
  padding: 0 16px;
  background: transparent;
  border: 0; /* per the mock: unselected items are plain labels, no box */
  font-family: var(--font-sans);
  font-size: 13px;
  line-height: 16px;
  font-weight: 400;
  color: var(--ink);
  cursor: pointer;
}

/* The visible box is 36px; this keeps the touch/click area at 44px */
.work-tab::before {
  content: "";
  position: absolute;
  inset: -4px 0;
}

.work-tab[aria-selected="true"] {
  font-weight: 500;
}
.work-tab[aria-selected="true"][data-accent="guardian"]    { background: var(--accent-guardian); }
.work-tab[aria-selected="true"][data-accent="shortlist"]   { background: var(--accent-shortlist); }
.work-tab[aria-selected="true"][data-accent="urbangreens"] { background: var(--accent-urbangreens); }
.work-tab[aria-selected="true"][data-accent="underarmour"] { background: var(--accent-underarmour); }
.work-tab[aria-selected="true"][data-accent="copilot"]     { background: var(--accent-copilot); } /* dark fill, white label */

/* The dark fills carry white labels; the light fills keep ink */
.work-tab[aria-selected="true"][data-accent="shortlist"],
.work-tab[aria-selected="true"][data-accent="urbangreens"],
.work-tab[aria-selected="true"][data-accent="underarmour"],
.work-tab[aria-selected="true"][data-accent="copilot"] {
  color: var(--paper);
}

/* Pressed state — brief inset while the click registers, for every tab */
.work-tab:active {
  transform: translateY(1px);
}

/* Cursor-following "View project" label — appears over the preview when
   enhanced, filled with the active project's accent, ink type. Hidden on
   touch and under reduced motion (handled in JS). */
.work-cursor {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 5;
  padding: 8px 14px;
  font-family: var(--font-sans);
  font-size: 13px;
  line-height: 16px;
  font-weight: 500;
  color: var(--ink);
  background: var(--ink-06); /* replaced per-project below */
  backdrop-filter: blur(2px); /* keeps the label legible over the preview */
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, -50%) scale(0.9);
  transition: opacity 150ms var(--ease), transform 150ms var(--ease), background 250ms var(--ease), color 250ms var(--ease);
  white-space: nowrap;
}
.work-cursor.is-visible {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
}
/* ~85% opaque accent — the preview shows through slightly */
.work-cursor[data-accent="guardian"]    { background: color-mix(in srgb, var(--accent-guardian) 85%, transparent); }
.work-cursor[data-accent="shortlist"]   { background: color-mix(in srgb, var(--accent-shortlist) 85%, transparent); color: var(--paper); }
.work-cursor[data-accent="urbangreens"] { background: color-mix(in srgb, var(--accent-urbangreens) 85%, transparent); color: var(--paper); }
.work-cursor[data-accent="underarmour"] { background: color-mix(in srgb, var(--accent-underarmour) 85%, transparent); color: var(--paper); }
.work-cursor[data-accent="copilot"]     { background: color-mix(in srgb, var(--accent-copilot) 85%, transparent); color: var(--paper); }

/* Hide the native cursor over the preview while the label stands in */
.work.is-pinned .work-preview {
  cursor: none;
}

/* ---- Enhanced: pinned frame — layout per the work-section mock:
   a paper rail on the left, the section gray behind everything else,
   the preview centered in the gray, and a paper card with the project
   information overlapping the rail edge ---- */

.work.is-pinned {
  height: calc(var(--work-count, 4) * var(--segment) + 100vh); /* --work-count set by script.js */
  padding-block: 0;
  background: var(--work-gray);
}

.work.is-pinned .work-frame {
  position: sticky;
  top: 0;
  height: 100vh;
  background: var(--work-gray);
  overflow: hidden;
}

/* The paper rail band */
.work.is-pinned .work-frame::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: var(--work-rail-w);
  background: var(--paper);
}

.work.is-pinned .work-grid {
  position: relative;
  height: 100%;
  max-width: none;
  margin: 0;
  padding: 0;
}

.work.is-pinned .work-eyebrow {
  position: absolute;
  top: 64px;
  left: var(--margin);
  margin: 0;
}

.work.is-pinned .work-rail-buttons {
  position: absolute;
  top: 200px;
  left: var(--margin);
  width: max-content; /* the widest label sets the width... */
  display: flex;
  flex-direction: column;
  align-items: stretch; /* ...and every tab stretches to it */
  gap: 16px;
}

.work.is-pinned .work-blocks,
.work.is-pinned .work-block {
  display: contents;
}

.work.is-pinned .work-block-title {
  display: none; /* the tab carries the name when enhanced */
}

/* The overlaying paper card with the project information.
   All four cards share the same flex+gap box as the base .work-block-details
   — just repositioned as an absolute overlay — and now hold three entries
   (project type, problem, impact) instead of two.
   NOTE: the drop shadow is the site's first — sanctioned by the mock.
   Width comes from --work-card-w (clamp(300px, 30vw, 412px) — see :root)
   instead of a flat 412px, so the card itself shrinks at tighter pinned
   widths rather than staying full-size and eating further into the video's
   space; .work-panel's padding-left below is computed from this same
   variable so the two can never collide regardless of viewport width.
   The card keeps its full, original padding/gap/line-spacing at every
   height — it's allowed to run taller now that it holds a third entry.
   Room for that extra height comes from --work-bottom-gap instead (see
   :root): that bottom margin shrinks on short screens, letting the card
   sit closer to the bottom edge of the frame and so closer to the rail
   tabs above it, without compressing any of the card's own text or
   spacing. */
.work.is-pinned .work-block-details {
  position: absolute;
  left: var(--margin);
  bottom: var(--work-bottom-gap);
  width: var(--work-card-w);
  max-width: calc(100vw - 2 * var(--margin));
  gap: 16px;
  padding: 24px 24px 28px;
  z-index: 3;
  opacity: 0;
  transform: translateY(8px);
  visibility: hidden;
  transition:
    opacity 200ms var(--ease) 40ms,
    transform 200ms var(--ease) 40ms,
    visibility 0s 300ms;
}

/* The preview, centered in the gray field */
.work.is-pinned .work-panel {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--work-rail-w);
  right: 0;
  display: flex;
  align-items: flex-end; /* was stretch — stretching to 100% height first, then
    letting the video's aspect-ratio drive its width, is exactly what made it
    overflow/squish at tighter widths (width capped by max-width while height
    stayed pinned to the full band, so the 16:9 ratio broke and object-fit:
    cover cropped the sides). Flex-end still leaves both dimensions to the
    auto + max-width/max-height + aspect-ratio rule on .work-preview below,
    which shrinks width *and* height together, in ratio, whichever is
    tighter — so the video never distorts or gets cropped, just gets smaller
    — but now settles to the bottom of the band instead of centering, so its
    bottom edge always lines up with the info card's bottom edge (both sit
    on var(--work-bottom-gap)) instead of leaving empty space underneath it. */
  justify-content: flex-end; /* preview sits flush to the page margin */
  /* top edge aligns with the first tab (200px) when unconstrained, bottom
     with the card (96px); padding-left is no longer a flat 48px — it's
     derived from --work-card-w + --work-card-gap so there's always at
     least a --work-card-gap buffer between the card's right edge and the
     video's left edge, at every width, instead of the two overlapping
     (the bug reported: the card sitting on top of the video). */
  padding: 200px var(--margin) var(--work-bottom-gap)
    max(24px, calc(var(--margin) + var(--work-card-w) + var(--work-card-gap) - var(--work-rail-w)));
  z-index: 1;
  opacity: 0;
  visibility: hidden;
  transition: opacity 300ms var(--ease), visibility 0s 300ms;
}

/* Sized like a replaced element (same trick as an <img> with
   width/height:auto + max-width/max-height + aspect-ratio): the browser
   picks the largest 16:9 box that fits within the available width *and*
   height, so it shrinks in both dimensions together as the pinned layout
   gets tighter, rather than one dimension capping while the other stays
   fixed and breaks the ratio. */
.work.is-pinned .work-preview {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
  aspect-ratio: 16 / 9;
  margin-top: 0;
}

.work.is-pinned .work-preview-media {
  width: 100%;
  height: 100%;
}

.work.is-pinned .work-block.is-active .work-block-details,
.work.is-pinned .work-block.is-active .work-panel {
  opacity: 1;
  transform: none;
  visibility: visible;
  transition:
    opacity 300ms var(--ease),
    transform 200ms var(--ease) 40ms,
    visibility 0s;
}

/* No tablet override here anymore — .work.is-pinned now only ever applies
   at 1025px and up (see script.js), where the base rules above (360px
   rail, 412px card) are the only ones that run. The old 768-1024px
   override (280px rail, 360px card) used to narrow the rail much faster
   than the card, so the overlay card ate ~120px into the preview image
   instead of just grazing its edge — that's the bug the user reported. */

/* --------------------------------------------------------------------------
   8.5 About — C-04
   Paper band on the left holds the bio card (overlapping into the gray)
   and the pink-framed portrait; a scattered skill constellation on the
   right reuses the hero's magnet + line-tracking behavior.
   -------------------------------------------------------------------------- */

:root {
  --about-pink: #E7D3DF; /* resume button + portrait frame — the original, lighter pale pink */
  --about-line: #D9A9BC; /* skill connector lines — a darker rose, kept separate from the button/frame pink so the lines stay visible */
  --about-rail-w: 360px;
}

.about {
  position: relative;
  background: var(--paper); /* right side is now paper */
  border-bottom: 1px solid var(--ink-12);
  overflow: hidden;
}

/* Gray band on the left, same width as the work rail (swapped from paper) */
.about::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: var(--about-rail-w);
  background: var(--work-gray);
}

.about-grid {
  position: relative;
  max-width: none;
  padding: 96px var(--margin) 128px;
}

.about-heading {
  margin: 0 0 48px;
}

/* Bio card — same treatment as the work cards, overlapping onto the gray */
.about-card {
  position: relative;
  z-index: 2;
  width: 632px;
  max-width: calc(100vw - 2 * var(--margin));
  padding: 32px;
  background: var(--paper);
  border: 1px solid var(--ink-40);
  box-shadow: 0 4px 4px rgba(10, 9, 13, 0.10);
}

.about-card .type-body + .type-body {
  margin-top: 20px;
}

.about-resume {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  margin-top: 24px;
  padding: 0 20px;
  background: var(--about-pink);
  color: var(--ink);
  font-size: 15px;
  line-height: 20px;
  transition: transform 200ms var(--ease);
}
.about-resume:hover,
.about-resume:focus-visible {
  transform: translateX(4px);
}
@media (prefers-reduced-motion: reduce) {
  .about-resume { transition: none; }
  .about-resume:hover,
  .about-resume:focus-visible { transform: none; }
}

/* Pink-framed portrait, below the card on the paper band */
.about-portrait {
  position: relative;
  z-index: 2;
  width: 170px;              /* smaller portrait */
  padding: 16px;             /* pink border scaled down to match */
  background: var(--about-pink);
  /* Right edge still lines up with the card's right edge (632px wide) */
  margin-top: 56px;
  margin-left: calc(632px - 170px);
  max-width: calc(100vw - 2 * var(--margin));
}
.about-portrait {
  overflow: hidden; /* clip the zoom within the pink frame */
}
.about-portrait img {
  display: block;
  width: 100%;
  height: auto;
  transition: transform 400ms var(--ease);
}
.about-portrait:hover img,
.about-portrait:focus-within img {
  transform: scale(1.04);
}

/* Skill constellation — occupies the gray area on the right */
.about-skills {
  position: absolute;
  top: 0;
  right: 0;
  left: var(--about-rail-w);
  bottom: 0;
  z-index: 1;
}

.about-lines {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.skill {
  position: absolute;
  transform: translate(-50%, -50%); /* positioned by its center */
}

.skill-inner {
  display: inline-block;
  padding: 10px 18px;
  background: var(--paper);
  border: 1px solid var(--ink-40);
  font-family: var(--font-sans);
  font-size: 15px;
  line-height: 20px;
  color: var(--ink);
  white-space: nowrap;
  transition: transform 0.5s ease-in-out; /* magnet spring-back */
  will-change: transform;
}
.skill.is-magnet-active .skill-inner {
  transition: transform 0.3s ease-out; /* magnet chase */
}

/* Scatter positions, from the mock (percent of the skills area) */
#skill-a { left: 72.5%; top: 16.7%; }
#skill-b { left: 54.0%; top: 32.6%; }
#skill-c { left: 74.0%; top: 43.7%; }
#skill-d { left: 52.5%; top: 57.6%; }
#skill-e { left: 78.0%; top: 68.2%; }
#skill-f { left: 62.2%; top: 81.7%; }

/* Phone + tablet: skills, then body text, then the portrait, with the
   portrait flush to the left margin — runs through 1024px, same threshold
   as the hero and Work section's stacked layouts above (see their media
   queries), since the desktop scatter's hand-placed percentages and the
   bio card's fixed 632px-wide overlap onto the gray rail only work with
   the full laptop-width layout and collide at narrower widths. Skills drop
   the desktop's tall right-rail scatter for a shorter, wider constellation
   (positions + box size set below) that runs the full 1024px range, scaled
   down further under 767px for phone. */
@media (max-width: 1024px) {
  .about { overflow: visible; }
  .about::before { display: none; }
  .about-grid { padding: 64px var(--margin) 80px; }
  .about-card { width: auto; }
  .about-portrait {
    margin-left: 0; /* flush left instead of lining up under the card's right edge */
  }
}

/* Phone + tablet skill constellation — six boxes in a loose zigzag,
   connected pairwise by thin lines, per the reference mock. Narrower and
   shorter than the desktop scatter (that one's percentages assume a tall
   column down the gray rail; these are hand-placed for a wide, short box
   instead) and given its own "mobile"-tagged connection set in script.js
   (desktop's 8 connections are tagged "desktop" to match — see the
   .mc-line--desktop/--mobile toggle up in the hero section, same 1024px
   threshold so the two stay in sync). The box positions/sizing here are
   the tablet (768-1024px) version; the max-width:767px block right below
   shrinks the boxes and re-spaces them further for phone, where the same
   percentages would crowd a much narrower track. */
@media (max-width: 1024px) {
  .about-skills {
    position: relative;
    top: auto; left: auto; right: auto; bottom: auto; /* clear the desktop rule's left: var(--about-rail-w)/right: 0 — those are absolute-positioning offsets from the base (non-media) .about-skills rule, still in the cascade here since this block only touches `position`; left unset they'd apply as a relative-position offset instead, at 1024px wide enough to shove the whole box ~360px off to the right */
    display: block;
    margin: 0 0 56px;
    aspect-ratio: 1.9 / 1;
  }
  .about-lines { display: block; }
  .skill {
    position: absolute;
    transform: translate(-50%, -50%);
  }
  .about-skills .skill.reveal { transform: translate(-50%, calc(-50% + 12px)); }
  .about-skills .skill.reveal.is-revealed { transform: translate(-50%, -50%); }

  /* Positions read off the reference mock (percent of the .about-skills
     box) — a loose left-to-right zigzag rather than the desktop's tall
     right-side scatter. */
  #skill-a { left: 34%; top: 24%; } /* UI/UX Design — top-left */
  #skill-b { left: 15%; top: 55%; } /* User Research — left, lower */
  #skill-c { left: 35%; top: 82%; } /* Design Systems — bottom-left */
  #skill-d { left: 56%; top: 48%; } /* Visual Design — center */
  #skill-e { left: 79%; top: 26%; } /* Figma — top-right */
  #skill-f { left: 76%; top: 78%; } /* Vibe Coding — bottom-right */
}

/* Phone: same constellation, scaled down. Smaller box padding/type shrinks
   each box's pixel footprint since the six labels' text can't shrink the
   box below its content width otherwise, and a taller aspect-ratio (vs.
   tablet's 1.9/1) trades some of the width phones don't have for the
   vertical room to keep boxes from overlapping. Positions are also
   re-spaced (not just inherited from the 1024px block above) — pushed
   further toward the edges/corners — since the narrower track needs more
   percentage-point separation between boxes to clear their now-larger
   share of the width. */
@media (max-width: 767px) {
  .about-skills { aspect-ratio: 1.15 / 1; }
  .skill-inner {
    padding: 7px 12px;
    font-size: 12px;
    line-height: 16px;
  }
  /* Bounds checked against the narrowest supported viewport (320px, a
     280px-wide track) so no box's edge — half its own pixel width out from
     its center point — clips past the container into the page margin;
     "Design Systems" is the widest label and the tightest fit. */
  #skill-a { left: 30%; top: 14%; }
  #skill-b { left: 22%; top: 48%; }
  #skill-c { left: 34%; top: 80%; }
  #skill-d { left: 60%; top: 44%; }
  #skill-e { left: 85%; top: 14%; }
  #skill-f { left: 80%; top: 78%; }
}

/* --------------------------------------------------------------------------
   9. Contact footer — all paper. Heading, a call to action with two
   external-link buttons (LinkedIn soft blue, Resume — the same pink as the
   About section's button/frame), and read-only detail columns. Ends the
   page on plain paper.
   -------------------------------------------------------------------------- */

:root {
  --btn-linkedin: #D3D7E7; /* soft pastel blue, LinkedIn nod */
  --btn-email:    var(--about-pink); /* class name is legacy ("email"); this button links to the resume */
}

.site-footer {
  padding: 80px 0 64px; /* compact footer, no heading */
  border-top: 1px solid var(--ink-12);
}

.footer-body {
  display: flex;
  flex-wrap: wrap;
  gap: 64px 96px;
  align-items: flex-start;
}

/* Drop the detail columns so their top lines up with the buttons row.
   The CTA block above the buttons is two lines: 2 x (24px line + 20px
   margin) = 88px of lead-in. */
.footer-details {
  margin-top: 72px; /* 2 tight CTA lines (48px) + 24px gap before buttons */
}

.footer-cta-line {
  margin: 0;
  font-size: 16px;
  line-height: 24px;
  color: var(--ink);
}

.footer-actions {
  display: flex;
  gap: 16px;
  margin-top: 24px; /* gap after the tight CTA lines, before the buttons */
}

.footer-btn {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 24px;
  color: var(--ink);
  font-size: 15px;
  line-height: 20px;
  transition: transform 200ms var(--ease);
}
.footer-btn.reveal.is-revealed:hover,
.footer-btn.reveal.is-revealed:focus-visible,
.footer-btn:hover,
.footer-btn:focus-visible {
  transform: translateX(4px); /* same nudge as the nav + resume button */
}
.footer-btn--linkedin { background: var(--btn-linkedin); }
.footer-btn--email    { background: var(--btn-email); }

.footer-details {
  display: flex;
  flex-wrap: wrap;
  gap: 32px 64px;
}

.footer-detail {
  min-width: 160px;
}

.footer-label {
  font-family: var(--font-mono);
  font-size: 11px;
  line-height: 14px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-60);
}

.footer-value {
  margin: 4px 0 0;
  font-size: 15px;
  line-height: 24px;
  color: var(--ink);
}
.footer-value a {
  color: inherit;
  text-decoration: none;
}

@media (prefers-reduced-motion: reduce) {
  .footer-btn { transition: none; }
  .footer-btn:hover,
  .footer-btn:focus-visible { transform: none; }
}

/* Phone: the CTA and the detail columns stack (.footer-body wraps), so the
   72px margin-top meant to line .footer-details up next to the buttons on
   wide screens just stacks on top of the 64px row-gap — shrink both down
   to one reasonable gap. Detail labels/values also come down a size, since
   at full size they compete with the CTA for attention. */
@media (max-width: 767px) {
  .footer-body { gap: 32px; }
  .footer-details {
    margin-top: 0;
    flex-direction: column; /* one column so location/email/website stack and line up, instead of an uneven 2-up wrap */
    gap: 16px;
  }
  .footer-label { font-size: 10px; }
  .footer-value { font-size: 12px; line-height: 18px; }
}

/* --------------------------------------------------------------------------
   10. Reduced motion
   Transforms removed, opacity only, no smooth scroll.
   The nav stack never hides — enforced here and in script.js.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  .nav-bar {
    transition: none;
  }
  .nav-bar.is-hidden {
    transform: none; /* belt and suspenders — JS also stops adding the class */
  }
  .nav-link {
    transition: none;
  }
  .work-flyout:not([hidden]):not(.is-closing) .flyout-row {
    animation: flyout-fade 220ms linear both;
    animation-delay: 0ms !important;
  }
  .work-flyout.is-closing .flyout-row {
    animation: none;
  }
  @keyframes flyout-fade {
    from { opacity: 0; }
    to   { opacity: 1; }
  }
  .flyout-link,
  .footer-row,
  .work-block-details,
  .work-panel {
    transition: none;
  }
}

/* Reveal-on-scroll — soft fade-up as elements enter, once. Matches the
   flyout's 8px rise, slightly longer. No-op under reduced motion. */
.reveal {
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 400ms var(--ease), transform 400ms var(--ease);
  will-change: opacity, transform;
}
.reveal.is-revealed {
  opacity: 1;
  transform: none;
}
@media (prefers-reduced-motion: reduce) {
  .reveal {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* Skill boxes are centered with translate(-50%,-50%); their reveal has to
   keep that while adding the fade-up, so it overrides the generic .reveal. */
.skill.reveal {
  transform: translate(-50%, calc(-50% + 12px));
}
.skill.reveal.is-revealed {
  transform: translate(-50%, -50%);
}
@media (prefers-reduced-motion: reduce) {
  .skill.reveal { transform: translate(-50%, -50%); }
}

@media (prefers-reduced-motion: reduce) {
  .work-preview-media,
  .about-portrait img {
    transition: none;
  }
  .work-preview:hover .work-preview-media,
  .about-portrait:hover img {
    transform: none;
  }
  .hero-item-media {
    transition: none;
  }
  .hero-item:hover .hero-item-media,
  .hero-item:focus-visible .hero-item-media,
  .hero-item.is-touch-active .hero-item-media {
    transform: none;
  }
}

/* 'In progress' badge — mono label, corner of the preview/hero item */
.wip-badge {
  position: absolute;
  top: 12px;
  left: 12px;
  z-index: 4;
  padding: 4px 10px;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--font-mono);
  font-size: 11px;
  line-height: 14px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* In-progress items are not clickable: no pointer, no cursor:none takeover */
.work-preview--static {
  position: relative; /* anchors .wip-badge below — used to come from .work-preview--reveal,
                          now gone along with pixel-reveal.js; every other .work-preview tile
                          doesn't need this since it has no badge to position */
  cursor: default;
}
.hero-item--static { cursor: default; }
.work.is-pinned .work-preview--static { cursor: default; }
.flyout-row--static .flyout-link { cursor: default; }
