@font-face {
  font-family: "Inter Display Regular";
  src: url("../../public/fonts/InterDisplay-Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
  background: #ffffff;
  color: #000000;
  /* Suppress the grey/blue tap-highlight rectangle iOS Safari and
     Android Chrome flash on every tappable element. We handle our
     own visual feedback (badge bounce, row hover, link color). */
  -webkit-tap-highlight-color: transparent;
}

body {
  font-family: "Inter Display Regular", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;
  font-size: 14px;
  line-height: 1.4em;
  letter-spacing: -0.01em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

p {
  margin: 0;
}

a {
  color: inherit;
  text-decoration: none;
}

button {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
}

ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

img {
  display: block;
}

/* Outer page wrapper — matches Framer's framer-1jfwxz6 (gap 134px, padding 100px). */
.page {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 134px;
  padding: 100px;
  width: 100%;
  position: relative; /* anchor for the absolute lang-switch */
}

/* ---------- Language switcher (top-right) ---------- */
.lang-switch {
  position: absolute;
  top: 100px;
  right: 100px;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-size: 13px;
  /* Same line-height as the body (14px × 1.4 = 19.6px) so the button's text
     baseline lines up with the intro paragraph's first line, regardless of
     the smaller font size. */
  line-height: 19.6px;
  /* Resting grey matches the "Past" experiences text. Hover transitions to
     black using the same asymmetric timing as .link-past / .link-footer. */
  color: #c2c2c2;
  cursor: pointer;
  z-index: 10;
  /* Hidden for pre-public-ship — the underlying i18n system still
     works, just no UI affordance. Change back to `inline-block` to
     bring the button back. */
  display: none;
  transition: color 0.22s cubic-bezier(0.2, 0.8, 0.2, 1);
  /* Suppress the iOS Safari grey tap-highlight rectangle on touch. */
  -webkit-tap-highlight-color: transparent;
}

/* Hover/focus only on devices that actually support hover. On touch,
   tapping the button just commits the next language without any preview. */
@media (hover: hover) {
  .lang-switch:hover,
  .lang-switch:focus-visible {
    color: #000000;
    transition: color 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
  }
}

.lang-window {
  display: inline-block;
  position: relative;
  width: 1.6em;
  height: 19.6px; /* same as line-height to keep the slide window flush */
  overflow: hidden;
  vertical-align: top;
  text-align: left;
}

.lang-label {
  position: absolute;
  top: 0;
  left: 0;
  display: block;
  white-space: nowrap;
  /* WAAPI sets transform/opacity per-label during the slide swap; no CSS
     transition here so taps feel immediate when JS animates. */
  will-change: transform, opacity;
}

/* Body-scoped override active during a language change. The animation is
   asymmetric:
   • Disappear (is-visible removed) — opacity fades immediately for every
     word at once (no stagger), and the vertical motion uses a smoother,
     slightly longer ease-in-out so it feels like a calm settle rather than
     a punchy drop.
   • Appear (is-visible added) — the regular staggered cascade plays so
     each word fades in left-to-right. */
.is-lang-switching .reveal.has-words .word,
.is-lang-switching .intro.reveal.has-words .word {
  /* Both opacity and the vertical settle start at the same instant (no delay,
     no stagger). The transform is much shorter than the opacity so you feel
     a tiny dip downward at the start of the fade — visible only briefly. */
  transition: opacity 0.25s cubic-bezier(0.22, 0.61, 0.36, 1) 0ms,
              transform 0.22s cubic-bezier(0.45, 0, 0.55, 1) 0ms;
}

.is-lang-switching .reveal.has-words.is-visible .word {
  transition: opacity 0.3s cubic-bezier(0.22, 0.61, 0.36, 1) calc(var(--i, 0) * 20ms),
              transform 0.3s cubic-bezier(0.16, 1, 0.3, 1) calc(var(--i, 0) * 20ms);
}

/* The intro keeps a slightly tighter stagger on appear, in line with the
   shorter timing it has on first paint. */
.is-lang-switching .intro.reveal.has-words.is-visible .word {
  transition: opacity 0.3s cubic-bezier(0.22, 0.61, 0.36, 1) calc(var(--i, 0) * 16ms),
              transform 0.3s cubic-bezier(0.16, 1, 0.3, 1) calc(var(--i, 0) * 16ms);
}

/* Intro paragraph — 545px column centered horizontally. */
.intro {
  width: 545px;
  max-width: 100%;
  text-align: left;
}

/* Whole Body / Contact column. */
.whole-body,
.contact {
  display: flex;
  flex-direction: column;
  width: 545px;
  max-width: 100%;
  gap: 15px;
}

/* Section spacing follows the live site exactly: the .page flex `gap` is the
   only thing controlling vertical rhythm (no margin-top overrides). */

.label {
  width: 100%;
  text-align: left;
}

.label.past {
  color: #c2c2c2;
  /* Opaque background so the close-animation FLIP doesn't reveal the
     fading job rows BEHIND this label. Without it, "Past" visually
     overlaps the still-fading "Senior Industrial Designer" row as it
     slides up past it. Matches the page bg so it's invisible at rest. */
  background: #ffffff;
  position: relative;
  z-index: 1;
}

/* Each row: two 50/50 cells. */
.row {
  display: flex;
  width: 100%;
  align-items: flex-start;
  gap: 0;
}

/* Rows that host the floating Now Hiring badge anchor it via position:relative.
   Scoped to .has-now-hiring so other rows are unaffected. */
.row.has-now-hiring {
  position: relative;
}

.row.past .cell-left,
.row.past .cell-right {
  color: #c2c2c2;
}

/* Same reasoning as .label.past: opaque background so the FLIP-sliding
   past rows don't reveal still-fading job rows beneath them on close. */
.row.past {
  background: #ffffff;
  position: relative;
  z-index: 1;
}

.cell-left {
  flex: 1 1 50%;
  text-align: left;
}

.cell-right {
  flex: 1 1 50%;
  text-align: right;
  display: flex;
  flex-direction: row;
  justify-content: flex-end;
  align-items: baseline;
  gap: 3px;
}

.cell-right-stack {
  flex-direction: column;
  align-items: flex-end;
  gap: 0;
}

.cell-right-stack p {
  text-align: right;
}

.tag {
  font-size: 10px;
  line-height: 1.4em;
  letter-spacing: 0;
  display: inline-block;
}

/* White-on-white kept faithful to source: "Founding Member" / "Founding" / closing CTA. */
.is-hidden-text,
.is-hidden-text .tag {
  color: #ffffff;
}

/* Past company link preset (Unison/Cove/Multi/New Tendency).
   Asymmetric hover: faster on hover-in, ~30% longer on hover-out so the
   color lingers a touch as the cursor leaves. */
.link-past {
  color: #c2c2c2;
  transition: color 0.22s cubic-bezier(0.2, 0.8, 0.2, 1);
}

.link-past:hover,
.link-past:focus-visible {
  color: #1a1a1a;
  transition: color 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
}

/* Footer social link preset (also used for the email mailto link).
   Same asymmetric timing as .link-past. */
.link-footer {
  color: #000000;
  transition: color 0.22s cubic-bezier(0.2, 0.8, 0.2, 1);
}

.link-footer:hover,
.link-footer:focus-visible {
  color: #dedede;
  transition: color 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
}

/* Closing CTA invisible-by-design. */
.closing {
  width: 545px;
  max-width: 100%;
  text-align: left;
}

/* ---------- Carousel ----------
   Full-bleed: the carousel breaks out of .page's 100px side padding so cards
   can scroll edge-to-edge. The list uses internal padding equal to the page
   padding so the first card sits flush with the rest of the centered content. */
.carousel {
  position: relative;
  width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
  padding-bottom: 100px;
  margin-top: -34px;
  margin-bottom: -34px;
}

.carousel-list {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  gap: 45px;
  /* First card sits 100px from the left and the last card sits 100px from
     the right — symmetric margins so the last card never reaches center. */
  padding-left: 100px;
  padding-right: 100px;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  /* Promote to its own compositor layer; lets the browser GPU-composite the
     horizontal scroll without repainting the rest of the page. */
  will-change: scroll-position;
  /* contain: paint isolates paints to this element so children don't trigger
     repaints up the tree. */
  contain: paint;
}

.carousel-list::-webkit-scrollbar {
  display: none;
  width: 0;
  height: 0;
}

/* Bounce is now driven by the Web Animations API (script.js) so it can run
   on the compositor thread, off the main thread. CSS keyframes for it are
   intentionally not defined here. */

/* Each card: outer width scales with the --card-scale variable. Desktop is
   1 → 600px (matches live). Mobile sets a smaller scale so the same per-
   card image-wraps shrink proportionally and fit inside the viewport. */
.card {
  flex: 0 0 auto;
  width: calc(600px * var(--card-scale, 1));
  scroll-snap-align: center;
  scroll-snap-stop: always;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  cursor: pointer;
  /* Paint isolation per card — cheap to add, helps the compositor batch.
     Earlier I also had `content-visibility: auto`; that turned out to make
     click-driven scroll laggy because every scrollLeft write during the
     animation forced the browser to re-evaluate visibility on all 17 cards.
     Removed. */
  contain: layout paint;
  /* Smooth appear-on-scroll animation. The 240ms delay is the load-bearing
     part: it gives the card time to scroll meaningfully into view before
     starting to fade, so a fast swipe doesn't burn the animation off-screen. */
  opacity: 0;
  transform: translate3d(0, 9px, 0);
  transition: opacity 1.2s cubic-bezier(0.16, 1, 0.3, 1) 240ms,
              transform 1.2s cubic-bezier(0.16, 1, 0.3, 1) 240ms;
}

.card.is-shown {
  opacity: 1;
  transform: translate3d(0, 0, 0);
}

/* Once a card has finished its entrance, drop the translate3d so the
   browser can release the GPU layer it forces. Otherwise all 17 cards
   stay promoted as separate compositor layers and the scroll animation
   pays per-frame compositing cost on every one of them, tanking FPS
   on click-driven scrolls. JS adds .is-rested after the entrance
   animation completes (or via ensureAllCardsShown on first click). */
.card.is-shown.is-rested {
  transform: none;
  will-change: auto;
}

@media (prefers-reduced-motion: reduce) {
  .card,
  .card.is-shown {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* Card-media is the 600×540 image area (scales down on mobile via the
   --card-scale variable). Per-card image-wraps use calc() so their sizes
   and positions scale together. Dimensions match the live Framer site.
   clip-path clips vertical overflow (cards 10, 13, 17 are intentionally
   taller than 540) so images don't bleed over the caption below. */
.card-media {
  position: relative;
  width: calc(600px * var(--card-scale, 1));
  height: calc(540px * var(--card-scale, 1));
  align-self: stretch;
  clip-path: inset(0 -200px);
}

.card-image {
  position: absolute;
  top: 0;
  left: 0;
}

.card-image img {
  width: 100%;
  height: 100%;
  display: block;
}

/* Per-card image-box dimensions — original (desktop) values verified via
   Playwright. Each property goes through calc(N * --card-scale) so mobile
   shrinks the entire composition proportionally without affecting desktop. */
.card-1  .card-image { width: calc(720px * var(--card-scale,1)); height: calc(540px * var(--card-scale,1)); left: calc(-60px * var(--card-scale,1)); top: calc(0px * var(--card-scale,1)); }
.card-1  .card-image img { object-fit: contain; }

.card-2  .card-image { width: calc(235px * var(--card-scale,1)); height: calc(215px * var(--card-scale,1)); left: calc(183px * var(--card-scale,1)); top: calc(163px * var(--card-scale,1)); }
.card-2  .card-image img { object-fit: cover; }

.card-3  .card-image { width: calc(291px * var(--card-scale,1)); height: calc(291px * var(--card-scale,1)); left: calc(155px * var(--card-scale,1)); top: calc(125px * var(--card-scale,1)); }
.card-3  .card-image img { object-fit: cover; }

.card-4  .card-image { width: calc(630px * var(--card-scale,1)); height: calc(540px * var(--card-scale,1)); left: calc(-15px * var(--card-scale,1)); top: calc(-5px * var(--card-scale,1)); }
.card-4  .card-image img { object-fit: contain; }

.card-5  .card-image { width: calc(320px * var(--card-scale,1)); height: calc(320px * var(--card-scale,1)); left: calc(140px * var(--card-scale,1)); top: calc(110px * var(--card-scale,1)); }
.card-5  .card-image img { object-fit: cover; }

.card-6  .card-image { width: calc(344px * var(--card-scale,1)); height: calc(344px * var(--card-scale,1)); left: calc(128px * var(--card-scale,1)); top: calc(98px * var(--card-scale,1)); }
.card-6  .card-image img { object-fit: cover; }

.card-7  .card-image { width: calc(480px * var(--card-scale,1)); height: calc(540px * var(--card-scale,1)); left: calc(60px * var(--card-scale,1)); top: calc(0px * var(--card-scale,1)); }
.card-7  .card-image img { object-fit: contain; }

.card-8  .card-image { width: calc(600px * var(--card-scale,1)); height: calc(540px * var(--card-scale,1)); left: calc(0px * var(--card-scale,1)); top: calc(0px * var(--card-scale,1)); }
.card-8  .card-image img { object-fit: contain; }

.card-9  .card-image { width: calc(600px * var(--card-scale,1)); height: calc(540px * var(--card-scale,1)); left: calc(0px * var(--card-scale,1)); top: calc(0px * var(--card-scale,1)); }
.card-9  .card-image img { object-fit: contain; }

.card-10 .card-image { width: calc(834px * var(--card-scale,1)); height: calc(594px * var(--card-scale,1)); left: calc(-117px * var(--card-scale,1)); top: calc(-27px * var(--card-scale,1)); }
.card-10 .card-image img { object-fit: contain; }

.card-11 .card-image { width: calc(440px * var(--card-scale,1)); height: calc(440px * var(--card-scale,1)); left: calc(80px * var(--card-scale,1)); top: calc(50px * var(--card-scale,1)); }
.card-11 .card-image img { object-fit: cover; }

.card-12 .card-image { width: calc(440px * var(--card-scale,1)); height: calc(440px * var(--card-scale,1)); left: calc(80px * var(--card-scale,1)); top: calc(50px * var(--card-scale,1)); }
.card-12 .card-image img { object-fit: cover; }

.card-13 .card-image { width: calc(691px * var(--card-scale,1)); height: calc(691px * var(--card-scale,1)); left: calc(-46px * var(--card-scale,1)); top: calc(-76px * var(--card-scale,1)); }
.card-13 .card-image img { object-fit: cover; }

.card-14 .card-image { width: calc(430px * var(--card-scale,1)); height: calc(410px * var(--card-scale,1)); left: calc(85px * var(--card-scale,1)); top: calc(65px * var(--card-scale,1)); }
.card-14 .card-image img { object-fit: cover; }

.card-15 .card-image { width: calc(490px * var(--card-scale,1)); height: calc(490px * var(--card-scale,1)); left: calc(55px * var(--card-scale,1)); top: calc(25px * var(--card-scale,1)); }
.card-15 .card-image img { object-fit: cover; }

.card-16 .card-image { width: calc(271px * var(--card-scale,1)); height: calc(271px * var(--card-scale,1)); left: calc(165px * var(--card-scale,1)); top: calc(135px * var(--card-scale,1)); }
.card-16 .card-image img { object-fit: cover; }

.card-17 .card-image { width: calc(490px * var(--card-scale,1)); height: calc(580px * var(--card-scale,1)); left: calc(55px * var(--card-scale,1)); top: calc(-20px * var(--card-scale,1)); }
.card-17 .card-image img { object-fit: contain; }

/* Cove 2021 / 2022 bottle renders (cards 8 and 9) have very faint
   horizontal lines at the top/bottom edges of the image at every
   breakpoint — sub-pixel anti-aliasing where contain-scaled image edges
   meet the white page background. Clipping 2px inside the image hides
   the artifact without visibly changing the bottle. */
.card-8 .card-image img,
.card-9 .card-image img {
  clip-path: inset(2px 0);
}

/* Caption block — narrower than the body text column so title and year sit
   close together on desktop (matches the live Framer site's 325px). The
   mobile breakpoint overrides this with 88vw so the caption spans the
   centered content column on phones. */
.card-info {
  width: 325px;
  max-width: 100%;
  display: flex;
  flex-direction: column;
  gap: 10px;
  color: #dedede;
}

.card-info-row {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
  width: 100%;
}

.card-title {
  text-align: left;
}

.card-year {
  text-align: right;
}

.card-meta {
  display: flex;
  flex-direction: column;
}

/* Prev/next buttons in DOM for a11y, visually hidden on the live site. */
.carousel-btn {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---------- Reveal animations ---------- */

/* Default (no JS or before script runs): fade the whole element in. */
.reveal {
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 0.6s ease-out, transform 0.6s ease-out;
  will-change: opacity, transform;
}

.reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
}

/* JS-enhanced: once words are wrapped, the parent stays visible and each
   word inside handles its own staggered reveal. */
.reveal.has-words {
  opacity: 1;
  transform: none;
  transition: none;
}

.reveal.has-words .word {
  display: inline-block;
  opacity: 0;
  /* translate3d forces a compositor layer so the lift is GPU-driven, which
     eliminates the sub-pixel anti-aliasing jitter you can sometimes see when
     many text nodes animate translateY simultaneously. */
  transform: translate3d(0, 6px, 0);
  /* var(--row-delay) cascades the reveal lines top-to-bottom — JS sets it
     based on each reveal's document order so the second line starts shortly
     after the first begins. */
  transition: opacity 1.4s cubic-bezier(0.22, 0.61, 0.36, 1) calc(var(--row-delay, 0ms) + var(--i, 0) * 55ms),
              transform 0.7s cubic-bezier(0.16, 1, 0.3, 1) calc(var(--row-delay, 0ms) + var(--i, 0) * 55ms);
  /* Words are pure visuals — skip hit-testing. Reduces cost of mousemove and
     scroll over big text blocks. */
  pointer-events: none;
}

/* Intro paragraph: snappier than the rest of the page (the first sentence
   shouldn't keep the visitor waiting) and a shallower lift. */
.intro.reveal.has-words .word {
  transform: translate3d(0, 3px, 0);
  transition: opacity 0.45s cubic-bezier(0.22, 0.61, 0.36, 1) calc(var(--row-delay, 0ms) + var(--i, 0) * 22ms),
              transform 0.3s cubic-bezier(0.16, 1, 0.3, 1) calc(var(--row-delay, 0ms) + var(--i, 0) * 22ms);
}

.reveal.has-words.is-visible .word {
  opacity: 1;
  transform: translate3d(0, 0, 0);
}

/* When Motion.dev has loaded and is driving the reveal/lang-switch animations,
   suppress the CSS transitions so the two systems don't double-animate. The
   `body.motion-on` class is added by script.js after the import succeeds; if
   the import fails, the class is never added and the CSS rules above still
   provide a working reveal as a fallback. */
body.motion-on .reveal.has-words .word,
body.motion-on .reveal.has-words.is-visible .word,
body.motion-on .is-lang-switching .reveal.has-words .word,
body.motion-on .is-lang-switching .reveal.has-words.is-visible .word,
body.motion-on .reveal,
body.motion-on .reveal.is-visible,
body.motion-on .card,
body.motion-on .card.is-shown {
  transition: none;
}

/* Hide the lang button at page load when Motion is driving its entrance.
   The class is added synchronously by script.js, so this fires before first
   paint. Motion sets inline opacity (which has higher specificity) once its
   animation begins. Without this rule, the button would render fully visible
   in the brief window before Motion's first frame. */
body.motion-on .lang-switch {
  opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
  .reveal,
  .reveal.is-visible,
  .reveal.has-words .word,
  .reveal.has-words.is-visible .word {
    opacity: 1;
    transform: none;
    transition: none;
  }

  .link-past,
  .link-footer {
    transition: none;
  }
}

/* ---------- Responsive breakpoints (matching live site) ---------- */

/* All non-mobile (≥584px): pull the contact section up so the visible
   gap between the carousel and the contact text is ~25% smaller. The
   .page flex `gap: 134px` between siblings (carousel → closing → contact)
   plus the ~20px height of the invisible .closing easter egg adds up to
   ~288px of empty space; -72px shrinks that to ~216px. Mobile has its
   own `margin-top: -116px` override at the bottom of this stylesheet. */
@media (min-width: 584px) {
  .contact {
    margin-top: -72px;
  }
}

/* Tablet large: 810–1279px — same as desktop in live (gap:134, padding:100,
   width:100%); we leave the page rule alone here so it inherits. */
@media (min-width: 810px) and (max-width: 1279px) {
  .lang-switch {
    top: 100px;
    right: 100px;
  }
}

/* Tablet large: 810–1279px — symmetric narrower side padding. */
@media (min-width: 810px) and (max-width: 1279px) {
  .carousel-list {
    padding-left: 60px;
    padding-right: 60px;
  }
}

/* Tablet small: 584–809px — match the live site (Framer .framer-1jfwxz6 mobile
   rule): page padding 75 top / 50 bottom / 0 sides, content column at 88% of
   viewport. Cards stay at desktop 600×540 and scroll horizontally inside the
   full-bleed carousel, so the image-wrap dimensions never need scaling. */
@media (min-width: 584px) and (max-width: 809px) {
  .page {
    padding: 75px 0 50px;
    /* gap inherits 134px from the desktop rule, matching live. */
    width: 88%;
    margin: 0 auto;
  }

  .intro,
  .whole-body,
  .contact,
  .closing {
    width: 100%;
    max-width: 545px;
  }

  .lang-switch {
    top: 75px;
    right: 6%;
  }

  .carousel-list {
    /* Land card 1 about 24px from the viewport's left edge so the second
       card peeks during initial swipe. */
    padding-left: 24px;
    padding-right: 24px;
    gap: 45px;
  }

  .carousel {
    padding-bottom: 60px;
  }
}

/* Mobile: ≤583px — same pattern, tighter vertical rhythm. */
@media (max-width: 583px) {
  .page {
    padding: 75px 0 50px;
    width: 88%;
    margin: 0 auto;
  }

  .lang-switch {
    top: 75px;
    right: 6%;
    /* Breathing room from the intro paragraph on its left. */
    padding-left: 16px;
  }

  /* Reserve space on the intro's right so it visually shortens to make
     room for the lang-switch button at top-right. min-height locks the
     paragraph at 2 lines so switching to FR/CN doesn't reflow the page
     and shift the carousel below it. text-wrap: balance keeps the two
     lines roughly equal length across all languages. */
  .intro {
    width: 100%;
    padding-right: 32px;
    box-sizing: border-box;
    min-height: 2.8em;
    text-wrap: balance;
  }
  /* CN wraps by Han comma rather than by space, so the same 32px reserve
     and `text-wrap: balance` force 3 lines (balance prefers three roughly-
     equal lines over two uneven ones). Tighten the reserve and switch to
     greedy wrapping for CN so the paragraph fits in 2 lines. */
  body.lang-cn .intro {
    padding-right: 16px;
    text-wrap: wrap;
  }

  .whole-body,
  .contact,
  .closing {
    width: 100%;
  }

  /* Body font stays at the global 14px on mobile across every section.
     The work-history rows need a couple of extra pixels to keep the
     longest pair ("Impossible, Inc — San Francisco, CA" / "Founding
     Member Founding") on one line at 14px. The flex container's
     `align-items: center` would otherwise fix .whole-body at parent's
     content width (346px on iPhone 17 Pro); `min-width` overrides that
     and the negative margins keep it visually centered. */
  .whole-body {
    min-width: calc(100% + 32px);
    margin-left: -16px;
    margin-right: -16px;
  }
  .whole-body .label {
    padding-left: 16px;
    padding-right: 16px;
  }
  .whole-body .row {
    padding-left: 16px;
    padding-right: 16px;
    align-items: baseline;
  }
  .whole-body .row .cell-left {
    flex: 1 1 auto;
    white-space: nowrap;
  }
  .whole-body .row .cell-right {
    flex: 0 0 auto;
    margin-left: auto;
    white-space: nowrap;
  }
  .whole-body .row .cell-right.cell-right-stack {
    white-space: normal;
  }

  /* Tighter gap between the image area and the spec text on mobile
     (30% smaller than the desktop 10px). */
  .card {
    width: 550px;
    gap: 7px;
  }

  /* Mobile uses bespoke per-card image dimensions that match the live
     Framer site (extracted via Playwright at 393×852). The desktop
     `--card-scale` uniform multiplier doesn't apply: Framer ships
     hand-tuned mobile values where every card has its own width/height
     and aspect ratio. Outer card frame is 550×522: width matches live
     (550), height shrunk from live's 588 by 66px to remove ~30% of the
     empty space below each image (per request). All images still fit
     vertically — verified per-card. */
  .card-media {
    width: 550px;
    height: 522px;
  }

  /* Per-card overrides — width/height/left/top match live mobile exactly,
     except cards 2, 5, 10, 16 which are bumped ~7% (per request) to give
     small images more presence on iPhone-class viewports. */
  .card-1  .card-image { width: 500px; height: 500px; left: 25px;  top: -5px; }
  .card-2  .card-image { width: 220px; height: 200px; left: 165px; top: 144px; }
  .card-3  .card-image { width: 260px; height: 260px; left: 145px; top: 115px; }
  .card-4  .card-image { width: 500px; height: 490px; left: 25px;  top: 0; }
  .card-5  .card-image { width: 300px; height: 300px; left: 125px; top: 95px; }
  .card-6  .card-image { width: 310px; height: 310px; left: 120px; top: 90px; }
  .card-7  .card-image { width: 410px; height: 461px; left: 70px;  top: 14px; }
  .card-8  .card-image { width: 460px; height: 460px; left: 45px;  top: 15px; }
  .card-9  .card-image { width: 460px; height: 460px; left: 45px;  top: 15px; }
  .card-10 .card-image { width: 655px; height: 467px; left: -53px; top: 12px; }
  .card-11 .card-image { width: 400px; height: 400px; left: 75px;  top: 45px; }
  .card-12 .card-image { width: 400px; height: 400px; left: 75px;  top: 45px; }
  .card-13 .card-image { width: 450px; height: 450px; left: 50px;  top: 20px; }
  .card-14 .card-image { width: 400px; height: 381px; left: 75px;  top: 54px; }
  .card-15 .card-image { width: 435px; height: 435px; left: 58px;  top: 28px; }
  .card-16 .card-image { width: 226px; height: 226px; left: 162px; top: 132px; }
  .card-17 .card-image { width: 400px; height: 418px; left: 75px;  top: 36px; }

  /* Carousel padding: card 1 starts flush left so its right edge crops
     on viewports narrower than 550px. JS auto-centers card 1 after the
     first vertical scroll. Horizontal gap between cards keeps the same
     value as before this rewrite (31.5px ≈ 45px × 0.7) per user request. */
  .carousel-list {
    padding-left: 0;
    padding-right: 24px;
    gap: 31.5px;
    scroll-snap-type: none;
  }

  /* card-info is 88vw so its left/right edges line up with the centered
     .whole-body column (which is 88% of viewport on mobile). When a card
     is centered, the card-title sits exactly under "Multi"/"Cove" etc.
     and the year sits under "Designer Founding". */
  .card-info {
    width: 88vw;
    max-width: 545px;
  }

  .carousel {
    padding-bottom: 40px;
  }

  /* Reduce the visible empty space between the bottom of the carousel
     and the contact "footer". The .page flex `gap: 134px` between siblings
     (carousel → closing → contact) plus the ~20px height of the invisible
     .closing easter egg adds up to ~288px of visible whitespace. Pulling
     .contact up 116px shrinks the visible gap to ~172px — ~30% reduction
     vs. the original spec, ~15% tighter than the previous -86px setting. */
  .contact {
    margin-top: -116px;
  }

  /* Mobile .now-hiring positioning lives AFTER the base .now-hiring
     rule (at the bottom of this file) because the base rule comes
     later in source order. With equal specificity, source order
     determines the winner. */
}

/* ---------- Now Hiring floating badge ----------
   Rectangle with squircle (G3-style continuous) corners, drawn by an inline
   SVG so the curves are smoother than CSS border-radius's circular arcs.
   Tilted +10° at rest (CSS, no animation), aligns to 0° on hover via Motion.
   To remove: delete the .has-now-hiring helper above, this block, and the
   <a class="now-hiring"> element from index.html. */
.now-hiring {
  position: absolute;
  /* Sits to the RIGHT of "San Francisco, CA". Mobile override floats it above. */
  top: -5px;
  left: 225px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* Fixed dimensions matching the SVG viewBox so the squircle corners
     don't distort. */
  width: 70px;
  height: 22px;
  /* Typography — same family/letter-spacing as document body, just one
     step smaller (12px vs 14px) so the badge reads as a label. */
  font-family: "Inter Display Regular", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;
  font-size: 12px;
  line-height: 1;
  letter-spacing: -0.01em;
  -webkit-font-smoothing: antialiased;
  /* Bright orange — applies to the text directly, and to the SVG stroke
     via `currentColor` on the path. */
  color: #ff6a00;
  white-space: nowrap;
  text-decoration: none;
  z-index: 5;
  cursor: pointer;
  /* Resting tilt — +9°. Hidden initially (opacity 0, lifted 14px,
     untilted); JS fades, lifts, AND rotates it into the +9° tilt
     simultaneously. On hover, 3-keyframe WAAPI bounce: rest +9° →
     overshoot 2° past hover → settles +4°. On mouseleave, same
     duration / same easing / same overshoot magnitude — exact mirror. */
  opacity: 0;
  transform: translate3d(0, 14px, 0) rotate(-5deg);
  transform-origin: center;
}

/* The SVG fills the element and draws the squircle outline. */
.now-hiring-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* Text layer sits above the SVG. */
.now-hiring-text {
  position: relative;
  z-index: 1;
}

@media (prefers-reduced-motion: reduce) {
  .now-hiring {
    /* Skip the entrance animation entirely — show the badge at rest, tilted. */
    opacity: 1;
    transform: rotate(9deg);
  }
}

/* Mobile: badge sits just AFTER "Impossible, Inc — San Francisco,
   CA", slightly above the baseline. Anchored to the right of the
   text end so it never overlaps the row content. Text ends at
   row coord ~241 on mobile (16px padding + ~225px text), so the
   badge at left:245 leaves a 4px gap.

   Lives here (AFTER the base .now-hiring rule) so source-order
   specificity makes it actually win on mobile. */
@media (max-width: 583px) {
  .now-hiring {
    /* A few px higher (further from the role lines below) and a few
       px to the left (closer to ", CA" so the badge sits tighter to
       the end of the text). */
    top: -16px;
    left: 238px;
  }
}

/* ---------- Job listings (revealed on Now Hiring click) ----------
   Uses the same .row layout as the work-history rows but with a tighter
   internal gap so the listing reads as a SUB-list nested under the
   Impossible row, rather than as more entries in the main work history.

   Closed by default (height 0). On click, JS uses the FLIP technique:
   it snaps the height to the target value (one layout pass), then
   animates `transform: translateY` on every sibling below — pure
   compositor-friendly transforms, no per-frame layout work. That's
   what makes the open/close buttery smooth.
   `overflow: hidden` keeps the rows clipped in the resting closed
   state; JS switches to `overflow: visible` during animations so the
   row fade-in/out is visible while the height is at 0. */
.job-listings {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 5px;          /* internal row gap — tighter than .whole-body's
                        15px so the list groups visually as its own cluster */
  margin-top: -9px;  /* pulls the listing UP into the .whole-body's gap.
                        Net distance from Impossible-row bottom to the
                        first job row top is 15 - 9 = 6px when open. */
  height: 0;
  overflow: hidden;
}

/* Job rows — same flex layout as a regular row, but in the badge's
   orange to visually tie them to the trigger. Each row is an <a>
   tag that opens a mailto: with the role title as the subject.
   Each row starts hidden; JS fades + lifts each one in with a
   small stagger. */
.row.job {
  color: #ff6a00;
  opacity: 0;
  text-decoration: none;
}
.row.job .cell-left,
.row.job .cell-right {
  color: inherit;
  /* Cell-level opacity transition. Hover sets it to 0.7 (–30%) using
     the same asymmetric timing as .link-past (180ms in / 220ms out).
     Putting opacity on the cells (not the row) avoids fighting with
     the WAAPI animation that drives the row's open/close opacity
     via fill:"both" — those effects would override any CSS opacity
     set on .row.job itself, but they don't reach into the cell
     children's CSS opacity. */
  opacity: 1;
  transition: opacity 0.22s cubic-bezier(0.2, 0.8, 0.2, 1);
}

@media (hover: hover) {
  .row.job:hover .cell-left,
  .row.job:hover .cell-right,
  .row.job:focus-visible .cell-left,
  .row.job:focus-visible .cell-right {
    opacity: 0.7;
    transition: opacity 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
  }
}

/* Comp annotation — superscript asterisk after the dollar range. */
.row.job .comp-mark {
  /* Slight superscript bump without doubling line-height. */
  vertical-align: super;
  font-size: 0.72em;
  line-height: 0;
  margin-left: 1px;
}

/* ── Shared comp tooltip ───────────────────────────────────────────
   ONE tooltip per listing (not per row). Sits as the last flex
   child of .job-listings, so it occupies its own dedicated row
   slot — never overlaps with the role lines above OR the Past
   section below. The listings' natural height grows to include
   this slot when open; the close animation collapses everything
   together.

   Desktop: hover anywhere on .job-listings to reveal (one big
   hitbox covering all four rows).
   Mobile: always visible whenever the listings are open (no
   hover affordance on touch — the tooltip just lives down there
   as part of the open layout). */
.comp-tooltip {
  /* In flex flow — takes its own row slot. */
  display: block;
  /* Sits flush with the last role line. The 5px flex `gap` on
     .job-listings provides the only spacing, matching the visual
     gap between consecutive tooltip lines themselves. */
  margin-top: 0;
  font-size: 11px;
  line-height: 1.5;
  /* Light grey — matches the Past section's #c2c2c2 so the tooltip
     reads as ancillary annotation rather than primary content. */
  color: #c2c2c2;
  /* Right-aligned so the text ends flush with the comp column on
     the right edge, anchoring it visually under the comp values. */
  text-align: right;
  /* Hidden by default + slight downward offset. The hover-in state
     animates back to translate(0) for a snappy "settling in" feel. */
  opacity: 0;
  transform: translate3d(0, 6px, 0);
  pointer-events: none;
  /* Slower out so leaving the hover area doesn't feel abrupt. */
  transition: opacity 0.22s cubic-bezier(0.45, 0, 0.55, 1),
              transform 0.22s cubic-bezier(0.45, 0, 0.55, 1);
}

.comp-tooltip-line {
  display: block;
}

/* Show on hover anywhere over the listings (large hitbox). The IN
   transition is faster and uses a back-out curve with overshoot
   (y1 = 1.6) so the tooltip settles in with a snappy pop. */
@media (hover: hover) {
  .job-listings[aria-hidden="false"]:hover .comp-tooltip {
    opacity: 1;
    transform: translate3d(0, 0, 0);
    transition: opacity 0.18s cubic-bezier(0.16, 1, 0.3, 1),
                transform 0.32s cubic-bezier(0.34, 1.6, 0.64, 1);
  }
}

/* Mobile clever trick: always visible while the listings are open.
   No hover, no discovery needed — the tooltip just lives below the
   rows as part of the open layout. Same snappy back-out curve on
   the transform so it has the same character as desktop hover.
   Left-aligned on mobile (instead of right-aligned like desktop)
   so the tooltip lines line up with the role titles above
   rather than ending under the comp values. */
@media (max-width: 583px) {
  .comp-tooltip {
    text-align: left;
    /* Match the 16px horizontal padding that .whole-body .row
       applies to the role lines, so the tooltip text starts at
       exactly the same x-coordinate as the role titles above. */
    padding-left: 16px;
    padding-right: 16px;
  }
  .job-listings[aria-hidden="false"] .comp-tooltip {
    opacity: 1;
    transform: translate3d(0, 0, 0);
    transition: opacity 0.4s cubic-bezier(0.16, 1, 0.3, 1) 0.25s,
                transform 0.5s cubic-bezier(0.34, 1.6, 0.64, 1) 0.25s;
  }
}
