/* ============================================
   MADE BY WACHO — global stylesheet
   Base style kit (see /styleguide/): ink ground, paper for everything
   that reads. Norca Rough (display) · Martian Mono (labels
   700 / meta 400 at 87.5% width) · Inter (prose).
   Tight spacing · squared corners · two-tone paper-on-ink (citron is
   retired — see the token note below).

   Layout: sticky-rail split — nav, then a left rail (hero, expertise)
   beside a scrolling right column (one showcase band per tool, the
   Spark service card, about/closing band), ticker footer. Every tool
   card is mode-ink; the Spark card (mode-yellow, now a paper ground)
   is the page's one light band. Every component paints with
   --bg/--fg/--body so it inherits its band's mode.
   ============================================ */

/* ---- Fonts ----
   Norca Rough is served locally from /fonts.
   Martian Mono (variable: wght + wdth) and Inter come from Google
   Fonts (see index.html). */

@font-face {
  font-family: "Norca Rough";
  src: url("fonts/NorcaRough.woff2") format("woff2"),
       url("fonts/NorcaRough.otf") format("opentype");
  font-weight: 400;
  font-display: swap;
}

:root {
  --ink: #30302a;    /* warm olive-black */
  --paper: #f7f4c9;  /* butter ground (Huru) — the signature paper */
  --sage: #d5e2d3;   /* support: calm green (Huru) — not wired into layout yet */
  --golden: #fbc50a; /* support: micro-punch accent (Huru) — not wired in yet */
  /* Paper hierarchy — three steps of the same butter, so tone carries rank
     without bringing back a second hue. Contrast on --ink-deep: tier 1 is
     13.6:1 (display + CTAs), tier 2 is 9.4:1 (prose), tier 3 is 6.4:1
     (labels, meta, markers, shell chrome). All three clear AA for body text,
     so nothing here is "dim" — they just stop competing. */
  --paper-2: #cfcda9;
  --paper-3: #abaa8d;
  /* Citron is retired: the site is two-tone. These names are kept because
     every component references them; they map onto the ramp above. */
  --yellow: var(--paper);
  --yellow-dim: var(--paper-3);
  --body-dim: var(--paper-2);
  --ink-deep: #26261f;  /* flow-card ground (below page ink) */
  --rule: #3f3f33;       /* hairline dividers on ink (nav border, ticker border) */
  --font-display: "Norca Rough", "Arial Black", "Archivo Black", sans-serif;
  --font-ui: "Martian Mono", ui-monospace, "Courier New", monospace;
  --font-body: "Inter", system-ui, -apple-system, "Segoe UI", Helvetica, sans-serif;
  --gutter: 20px;
  --col: min(92vw, 1720px);
  --sbw: 0px; /* real scrollbar width, measured in script.js — see .bleed */
  --nav-h: 52px; /* remeasured in script.js; the rail sticks below it */
  --cursor: url("assets/cursor.svg") 3 1, auto;
}

/* ---- Mode system (from /styleguide/) ----
   --bg paints the band, --fg the display tier, --body the prose, --meta the
   quiet data tier (markers, meta rows, small labels). Nested panels can
   re-declare a mode and stay internally correct. */
.mode-ink    { --bg: var(--ink);    --fg: var(--paper); --body: var(--paper-2); --meta: var(--paper-3); }
.mode-yellow { --bg: var(--yellow); --fg: var(--ink);   --body: var(--ink);     --meta: #626152; }
.mode-paper  { --bg: var(--paper);  --fg: var(--ink);   --body: var(--ink);     --meta: #626152; }
.mode-ink, .mode-yellow, .mode-paper { background: var(--bg); color: var(--body); }

/* Flow cards (tool showcases + the about panel) sit on a ground one shade
   below the page ink. The tier ramp comes from .mode-ink above, so this only
   needs to move the ground. */
.split__flow .band.mode-ink {
  --bg: var(--ink-deep);
}

html,
button,
a {
  cursor: var(--cursor);
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  border-radius: 0;
}

::selection {
  background: var(--yellow);
  color: var(--ink);
}

.mode-yellow ::selection,
.mode-paper ::selection {
  background: var(--ink);
  color: var(--yellow);
}

html {
  background: var(--ink);
  scroll-behavior: smooth;
}

body {
  background: var(--ink);
  color: var(--yellow);
  font-family: var(--font-body);
  /* Martian Mono runs 13.3% wider than a standard mono; 87.5% width
     restores parity. Inherited, and inert on Inter/Norca (no wdth axis). */
  font-stretch: 87.5%;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
  line-height: 1.6;
}

/* Uppercase is a display/label treatment, not a page-wide rule — prose
   stays sentence case (base kit rule, see /styleguide/). */
.masthead,
.nav,
.marker,
.btn,
.expertise-head,
.showcase__meta,
.showcase__name,
.about__roles,
.about__meta,
.section-head {
  text-transform: uppercase;
}

/* ---- Layout primitives ---- */

.wrap {
  width: 100%;
  padding-inline: var(--gutter);
  margin-inline: auto;
}

@media (min-width: 768px) {
  .wrap {
    width: var(--col);
    padding-inline: 0;
  }
}

.band {
  padding-block: clamp(52px, 8vw, 108px);
}

.band + .band {
  border-top: 1px solid var(--fg);
}

/* Flow cards (the right column) drop the stroke — they read as filled panels
   on the deeper ground, separated by a gap that shows the page ink through. */
.split__flow .band + .band {
  border-top: none;
}
.split__flow > * + *,
#showcases > * + * {
  margin-top: clamp(16px, 2vw, 28px);
}
/* Flow-card padding applies at every width the deep card ground shows at
   (from 768px, see .wrap above) — not just >=992px — or the card edge lines
   up flush with the text edge between 768-991px. */
@media (min-width: 768px) {
  .split__flow .band {
    --pad: clamp(32px, 3.4vw, 56px);
    padding: var(--pad);
  }
}

/* ---- Split layout ----
   At >=992px (the reference's breakpoint) the page becomes a sticky left
   rail — identity, pitch, CTAs, clients, expertise — beside a right column
   that scrolls through the work. Below that the split collapses to a plain
   stack; DOM order is already the mobile reading order, so nothing moves. */

.split__flow {
  /* Sized queries below ask about THIS column's width, not the viewport —
     the column is ~55% of the page in the split and 100% outside it, so a
     viewport query would get the showcase's breakpoint wrong in one of the
     two layouts. */
  container-type: inline-size;
}

/* The outer .split.wrap owns the page column and gutter at every size;
   inner wrappers just fill their track. Without this a nested .wrap
   re-applies min(88vw,1600px) and overflows its own column. */
.split .wrap,
.split .rail-wrap {
  width: 100%;
  padding-inline: 0;
}

@media (min-width: 992px) {
  .split {
    display: grid;
    grid-template-columns: minmax(260px, 26%) 1fr;
    gap: clamp(28px, 3.5vw, 64px);
    align-items: start;
  }

  .split__rail {
    position: sticky;
    top: calc(var(--nav-h) + 20px);
    /* Never taller than the space under the nav; the rail scrolls
       internally on short viewports instead of being cut off. */
    max-height: calc(100vh - var(--nav-h) - 40px);
    overflow-y: auto;
    scrollbar-width: thin;
  }

  .split__rail .band {
    padding-block: clamp(22px, 2.4vw, 34px);
  }

  .split__rail .band:first-child {
    padding-top: 0;
  }
}

/* Full-width breakout from a centered column. 100vw includes the
   scrollbar in Chrome/Firefox, so a plain left:50%/-50vw undershoots by
   half the scrollbar width — invisible on ink-on-ink, a visible seam on
   any paper/yellow band. --sbw (measured in script.js on load, once real
   content can force a scrollbar) corrects it. */
.bleed {
  width: calc(100vw - var(--sbw));
  position: relative;
  left: 50%;
  margin-left: calc(-50vw + (var(--sbw) / 2));
}

/* ---- Shared type tiers ---- */

.marker {
  font-family: var(--font-ui);
  font-weight: 700;
  font-size: 11px;
  letter-spacing: 0.16em;
  color: var(--meta);
  margin-bottom: 18px;
}

.section-head {
  font-family: var(--font-display);
  font-size: clamp(28px, 4.6vw, 56px);
  line-height: 1.02;
  letter-spacing: -0.01em;
  color: var(--fg);
  margin-bottom: 28px;
}

.lede {
  font-size: clamp(15px, 1.5vw, 17px);
  color: var(--body);
  max-width: 60ch;
}

/* ---- Buttons ----
   Plain tracked-out text links (Figma node 150:1453 — "buttons simple, not
   complex"): no box, no fill. Full-bright citron is fine here — a CTA is a
   small, deliberate accent, not a loud block repeated down the page. Hover
   underlines instead of flipping a fill. */

.btn {
  display: inline-block;
  font-family: var(--font-ui);
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.3em;
  text-decoration: none;
  text-transform: uppercase;
  color: var(--yellow);
  transition: opacity 160ms ease;
}

.btn:hover,
.btn:focus-visible {
  text-decoration: underline;
  text-underline-offset: 0.25em;
  outline: none;
}

.btn--ghost {
  color: var(--yellow);
}

/* Sidebar Spark CTA: a quiet lead-in line ("On-brand by default.") over
   the bright link text + arrow, same icon grammar as the ICONS registry in
   script.js (1.6 stroke, square caps/miter, currentColor). */
.btn--spark {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  text-decoration: none;
}
.btn--spark:hover,
.btn--spark:focus-visible {
  text-decoration: none; /* override .btn's whole-link underline — only .btn__go underlines */
}
.btn__quiet {
  display: block;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 12px;
  letter-spacing: 0.01em;
  text-transform: none;
  color: var(--meta);
}
.btn__go {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.btn--spark:hover .btn__go,
.btn--spark:focus-visible .btn__go {
  text-decoration: underline;
  text-underline-offset: 0.25em;
}
.btn__arrow {
  width: 16px;
  height: 16px;
  flex: 0 0 auto;
  transition: transform 160ms ease;
}
.btn--spark:hover .btn__arrow,
.btn--spark:focus-visible .btn__arrow {
  transform: translateX(4px);
}

/* Exception: the Spark card is the page's one deliberately loud moment
   (its annotation: "contrast here IS too much — we want to be drawing
   attention"), so its CTA keeps the filled block the rest of the page dropped. */
.showcase--service .btn {
  padding: 16px 26px;
  border: 2px solid var(--fg);
  background: var(--fg);
  color: var(--bg);
  letter-spacing: 0.1em;
  text-decoration: none;
  transition: background-color 160ms ease, color 160ms ease;
}
.showcase--service .btn:hover,
.showcase--service .btn:focus-visible {
  background: transparent;
  color: var(--fg);
  text-decoration: none;
}

/* ---- Sticky nav ---- */

.nav {
  position: sticky;
  top: 0;
  z-index: 800;
  background: var(--ink);
  border-bottom: 1px solid var(--rule);
}

.nav__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding-block: 12px;
}

.nav__wordmark {
  font-family: var(--font-display);
  font-size: clamp(15px, 1.6vw, 20px);
  letter-spacing: -0.02em;
  line-height: 1;
  color: var(--yellow-dim);
  text-decoration: none;
  white-space: nowrap;
}

.nav__links {
  display: flex;
  align-items: center;
  gap: clamp(12px, 2vw, 28px);
}

.nav__link {
  font-family: var(--font-ui);
  font-weight: 700;
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--yellow-dim);
  padding: 4px 2px;
}

/* Nav chrome sits at tier 3; hover/focus steps up to full paper instead of
   the old filled-block invert. */
.nav__link:hover,
.nav__link:focus-visible {
  background: none;
  color: var(--yellow);
  outline: none;
}

/* The two in-page anchors are noise on a small screen; the email CTA
   is the one that matters, so it survives. */
@media (max-width: 640px) {
  .nav__link--anchor {
    display: none;
  }
}

/* Language toggle: a plain button wearing .nav__link's typography, with its
   own active/dim states per option so the current language stays legible
   without waiting on hover. */
.nav__lang {
  background: none;
  border: 0;
  cursor: pointer;
}

.nav__lang [data-lang-opt] {
  color: var(--yellow-dim);
}

.nav__lang [data-lang-opt].is-active {
  color: var(--yellow);
}

.nav__lang:hover [data-lang-opt],
.nav__lang:focus-visible [data-lang-opt] {
  color: var(--yellow);
}

/* ---- Hero ---- */

.hero {
  padding-top: clamp(28px, 5vw, 64px);
  padding-bottom: clamp(40px, 6vw, 84px);
}

/* Masthead: fitText() in script.js scales this to span its own width
   edge-to-edge, whatever font actually loaded. */
.masthead {
  width: 100%;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 14.55vw; /* refined at runtime by fitText() */
  letter-spacing: -0.05em;
  line-height: 0.82;
  text-align: center;
  white-space: nowrap;
  text-box: trim-both cap alphabetic;
  user-select: none;
  margin-inline: auto;
  /* Tier 2, not tier 1: at ~14vw this is the largest surface on the page, so
     size already carries it. Full paper here is what made everything read as
     equally loud. */
  color: var(--paper-2);
}

.hero__sub {
  font-size: clamp(15px, 1.7vw, 19px);
  color: var(--body);
  max-width: 54ch;
  margin-top: clamp(20px, 3vw, 32px);
}

/* ---- Section label ----
   The small mono label above the expertise grid ("My expertise"). */

.expertise-head {
  font-family: var(--font-ui);
  font-weight: 700;
  font-size: 11px;
  letter-spacing: 0.16em;
  color: var(--meta);
  margin-bottom: 20px;
}

/* ---- Expertise grid ---- */

/* A plain two-column list, not a boxed grid — it lives in the narrow
   rail, where framing every cell would crowd it. */
.expertise__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px 18px;
}

.expertise__item {
  display: flex;
  align-items: center;
  gap: 11px;
  /* Bullets read in Inter, not the mono label face — softer and set apart
     from the "My expertise" heading above. */
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 14px;
  letter-spacing: 0.005em;
  line-height: 1.25;
  color: var(--body);
}

/* Icon slot — holds an inline stroke SVG that paints with currentColor. */
.expertise__mark {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  color: var(--meta);
}

.expertise__mark svg {
  width: 100%;
  height: 100%;
  display: block;
}

/* Two stacked CTA groups below the expertise list: the Spark link (its own
   quiet line + go link count as one group) and Email me as a second, plainer
   group underneath it. The gap here reads as the space between groups; the
   tighter 6px gap inside .btn--spark reads as within one group. */
.rail-ctas {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: clamp(20px, 3vw, 28px);
  margin-top: clamp(28px, 4vw, 40px);
}

/* ---- Tool showcase bands ----
   One band per tool. Grounds alternate (set per-band in the markup);
   the media side alternates via :nth-child so consecutive bands mirror. */

/* The card clips its own overhang — the full-bleed media and the diagonal
   "FREE" tape both extend to (or past) the edges. */
.showcase {
  position: relative;
  overflow: hidden;
}

.showcase__inner {
  display: grid;
  gap: clamp(24px, 4vw, 56px);
  align-items: center;
}

/* Keyed to the flow column's own width, so the two-up layout only appears
   when this column can actually carry it — the same rule works inside the
   split's narrower column and on the full-width mobile stack. */
@container (min-width: 660px) {
  .showcase__inner {
    grid-template-columns: 1fr 1fr;
    /* Give every card real vertical presence so the media reads large and the
       cards cover a bigger share of the column. */
    min-height: clamp(360px, 44vh, 540px);
  }

  /* Flipped showcases put the media first — mirrored layout down the page.
     Keyed to a class (set by tool index in JS), not nth-of-type, so inserting
     the service card between tools never re-mirrors them. */
  .showcase--flip .showcase__media {
    order: -1;
  }

  /* Media fills its half full-height (cover-crops the gif) and bleeds flush
     to the card's own edge on its side — no border, no inset; the text keeps
     its padding. --pad is the split card's padding, so the negative-margin
     bleed is a no-op on the full-width (non-split) layout where it's 0. */
  .showcase__media {
    align-self: stretch;
    /* Grow the figure past its track by the card's padding so it reaches the
       card edge; justify-self aims that overflow at the outer side. Explicit
       width (not stretch) because Chrome won't expand a stretch-sized grid
       item with an inline negative margin — only the block axis. margin-block
       bleeds it to the top and bottom edges (block stretch does honor that). */
    width: calc(100% + var(--pad, 0px));
    margin-block: calc(-1 * var(--pad, 0px));
    justify-self: start;   /* default: media on the right → grow right */
  }
  .showcase--flip .showcase__media {  /* media on the left → grow left */
    justify-self: end;
  }
  .showcase__media img,
  .showcase__media video {
    height: 100%;
    object-fit: cover; /* fill the panel edge-to-edge; crop rather than distort */
  }

  /* Stretch the text column full-height so the marker can drop to the bottom
     margin (margin-top:auto), far from the name/meta/description cluster. */
  .showcase__inner > .showcase__text {
    align-self: stretch;
  }
}

/* Text column: a flex stack with the overline marker pinned to the bottom.
   The marker is last in the DOM (script.js) so tab/reading order matches
   what's on screen; margin-top:auto alone pushes it to the bottom. */
.showcase__text {
  display: flex;
  flex-direction: column;
}
.showcase__text .marker {
  margin-top: auto;
  margin-bottom: 0;
  padding-top: clamp(20px, 4vw, 44px);
}

.showcase__name {
  font-family: var(--font-display);
  font-size: clamp(28px, 4.2vw, 58px);
  line-height: 0.98;
  letter-spacing: -0.02em;
  color: var(--fg);
  margin-bottom: 12px;
}

.showcase__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 18px;
  font-family: var(--font-ui);
  font-size: 11px;
  letter-spacing: 0.12em;
  color: var(--meta);
  margin-bottom: 20px;
}

.showcase__description {
  font-size: clamp(15px, 1.5vw, 17px);
  color: var(--body);
  max-width: 46ch;
  margin-bottom: 24px;
}

/* ---- "FREE" tape ----
   A diagonal police-tape ribbon stuck across the card's lower corner: a
   yellow band with ink edges, scrolling the tag text on an infinite loop.
   Sits above the media (z-index) and is clipped by the card's overflow. */
.showcase__tape {
  position: absolute;
  right: var(--tape-right, -6%);
  bottom: var(--tape-bottom, 9%);
  width: var(--tape-w, 58%);
  z-index: 2;
  /* Dimmed to match the quiet cards — full-bright citron is reserved for the
     Spark card, not a stripe repeated across every dark card. */
  background: var(--yellow-dim);
  border-block: 2px solid var(--ink);
  /* Anchored to the right and pivoted there so a steeper tilt keeps the tape
     hugging the bottom-right corner instead of spanning the whole card. Angle,
     width, position, speed and direction all vary per card (set in JS). */
  transform: rotate(var(--tape-angle, -20deg));
  transform-origin: 100% 50%;
  overflow: hidden;
  pointer-events: none;
}

.showcase__tape-track {
  display: flex;
  width: max-content;
  animation: tape-scroll var(--tape-dur, 16s) linear infinite;
  animation-direction: var(--tape-dir, normal);
}

.showcase__tape-group {
  display: flex;
  flex-shrink: 0;
}

.showcase__tape-word {
  font-family: var(--font-ui);
  font-weight: 700;
  font-size: clamp(14px, 2vw, 22px);
  line-height: 1;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink);
  white-space: nowrap;
  padding: 6px 0;
  padding-right: 0.7em;
}

@keyframes tape-scroll {
  to {
    transform: translateX(-50%);
  }
}

.showcase__cta {
  display: block;
}

/* ---- Spark service card ----
   The one accent card (mode-yellow). No media block: the copy sits centered,
   and on hover/focus small artboard frames of real deliverables fly in from
   the top and sides, clipped by the card's overflow (set on .showcase). */

/* Single centered column, whatever the container width — override the two-up
   grid the tool cards use. The copy stays above the frames overlay. */
.showcase__inner--service {
  grid-template-columns: 1fr;
  justify-items: center;
  text-align: center;
}

.showcase__service-copy {
  position: relative;
  z-index: 2;
  max-width: 46ch;
}
.showcase__service-copy .showcase__meta {
  justify-content: center;
}
.showcase__service-copy .showcase__description {
  margin-inline: auto;
}

/* Frames overlay — covers the card, behind the copy, clipped by .showcase's
   overflow:hidden. pointer-events off so it never blocks the card's hover. */
.showcase__frames {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
}

/* Each frame: a small framed artboard anchored just off an edge. At rest it's
   pushed fully out (--tx/--ty) and faded; on reveal it slides to its anchor
   (translate 0) keeping its tilt (--rot). Per-frame anchor + direction + delay
   are set by :nth-child below. */
.showcase__frame {
  position: absolute;
  /* cqw, not vw — this sizes off .split__flow's own width (see the
     container-type note above), not the viewport. */
  width: clamp(92px, 12cqw, 152px);
  aspect-ratio: 1 / 1;
  border: 1px solid var(--ink);
  background: var(--paper);
  opacity: 0;
  transform: translate(var(--tx, 0), var(--ty, 0)) rotate(var(--rot, 0deg));
  transition: transform 560ms cubic-bezier(0.2, 0.75, 0.25, 1),
              opacity 320ms ease;
  transition-delay: var(--d, 0ms);
  will-change: transform, opacity;
}
.showcase__frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Two drop in from above, two enter from the left, two from the right. */
.showcase__frame:nth-child(1) { top: -7%; left: 9%;   --tx: 0; --ty: -135%; --rot: -8deg; --d: 0ms; }
.showcase__frame:nth-child(2) { top: -7%; right: 11%; --tx: 0; --ty: -135%; --rot: 7deg;  --d: 70ms; }
.showcase__frame:nth-child(3) { top: 20%; left: -6%;  --tx: -135%; --ty: 0; --rot: -6deg; --d: 130ms; }
.showcase__frame:nth-child(4) { top: 52%; left: -4%;  --tx: -135%; --ty: 0; --rot: 5deg;  --d: 40ms; }
.showcase__frame:nth-child(5) { top: 15%; right: -6%; --tx: 135%;  --ty: 0; --rot: 6deg;  --d: 100ms; }
.showcase__frame:nth-child(6) { top: 50%; right: -4%; --tx: 135%;  --ty: 0; --rot: -5deg; --d: 160ms; }

/* Reveal: card hover, or keyboard focus reaching the CTA inside it. */
.showcase--service:hover .showcase__frame,
.showcase--service:focus-within .showcase__frame {
  opacity: 1;
  transform: translate(0, 0) rotate(var(--rot, 0deg));
}

/* Media panel. Stacked (single-column) it shows at natural height; in the
   two-up layout it stretches full-height and bleeds flush to the card edge
   (see the container query above). No frame — the card itself is the frame. */
/* No explicit width — as a grid item it stretches to fill its column;
   the two-up layout overrides width to bleed past the track (see above). */
.showcase__media {
  overflow: hidden;
}

.showcase__media img,
.showcase__media video {
  width: 100%;
  display: block;
  /* Retint the baked two-tone clips to the new palette (see #media-duotone in
     index.html): old yellow → #F7F4C9 (--paper), black → card ground. */
  filter: url(#media-duotone);
  /* No height here — it's the default (auto) for the stacked layout, and
     leaving it unset lets the two-up rule's height:100% win the cascade
     (an explicit height:auto would override it, since it's declared later). */
}

/* ---- About / closing band ----
   Media left, text right (the yellow weight sits right on this page).
   Same treatment as the tool cards: the media bleeds full-height flush to
   the card's left edge. Stacks media-first on mobile. */

.about-band {
  position: relative;
  overflow: hidden;
}

.about {
  display: flex;
  align-items: stretch;
  gap: clamp(20px, 3vw, 40px);
  width: 100%;
}

.about__media {
  flex: 0 0 auto;
  /* Widen by the card padding and pull left so the right edge holds its
     place while the left bleeds to the card border; margin-block bleeds it
     to the top and bottom. --pad is 0 off the split, so it's a no-op there. */
  width: calc(min(420px, 42%) + var(--pad, 0px));
  margin-block: calc(-1 * var(--pad, 0px));
  margin-left: calc(-1 * var(--pad, 0px));
  object-fit: cover;
  display: block;
  /* Same duotone retint as the tool clips — old yellow → #F7F4C9 (--paper), black → card ground. */
  filter: url(#media-duotone);
}

.about__info {
  display: flex;
  flex-direction: column;
  gap: 20px;
  flex: 1 1 0;
  min-width: 0;
  container-type: inline-size;
  padding-block: 4px;
}

/* Sized off the info column's own width (not the viewport), so the
   longest word ("OVERTHINKER") always fits — the column's share of the
   page varies with the layout. */
.about__roles {
  font-family: var(--font-display);
  font-size: clamp(24px, 13cqw, 60px);
  letter-spacing: 0.01em;
  line-height: 1.08;
  color: var(--fg);
}

.about__body {
  font-size: clamp(15px, 1.5vw, 17px);
  color: var(--body);
  max-width: 46ch;
}

.about__meta {
  margin-top: auto;
  display: flex;
  justify-content: space-between;
  max-width: 305px;
  transform: scaleY(-1);
  font-family: var(--font-ui);
  font-size: 12px;
  letter-spacing: 0.3em;
  color: var(--meta);
}

@container (max-width: 659px) {
  .about {
    flex-direction: column;
    gap: 20px;
  }

  .about__media {
    width: 100%;
    max-height: 340px;
    margin: 0; /* no edge-bleed when stacked */
  }
}

/* ---- Footer ticker ----
   Infinite marquee, full-bleed. JS clones the phrase enough times to
   cover the widest viewport, then duplicates the whole group once so
   a -50% translate loops seamlessly with no gap or jump. */

.ticker {
  overflow: hidden;
  width: 100%;
  user-select: none;
  background: var(--ink);
  color: var(--yellow-dim);
  border-top: 1px solid var(--rule);
  padding-block: 10px;
}

.ticker__track {
  display: flex;
  width: max-content;
  animation: ticker-scroll 26s linear infinite;
}

.ticker__group {
  display: flex;
  flex-shrink: 0;
}

.ticker__item {
  font-family: var(--font-display);
  font-size: clamp(28px, 4.2vw, 60px);
  letter-spacing: -0.02em;
  line-height: 0.9;
  white-space: nowrap;
  padding-right: 0.6em;
}

/* The Jojo phrase is a hover easter egg (see preview below). */
.ticker__item--jojo {
  cursor: var(--cursor);
}

.ticker__item--jojo:hover {
  text-decoration: underline;
  text-underline-offset: 0.1em;
}

@keyframes ticker-scroll {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

/* ---- Jojo easter-egg hover preview (Figma node 85:1309) ----
   A retro "window" (dark title bar + gif) that follows the cursor while
   hovering the ticker's Jojo phrase. Positioned in JS via transform;
   pointer-events off so it never blocks the hover it depends on. */

.jojo-pop {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 900;
  width: clamp(280px, 30vw, 400px); /* wide enough for the full filename */
  background: var(--ink);
  color: var(--yellow);
  border: 2px solid var(--yellow); /* separates the dark window from the ink page */
  box-shadow: 8px 8px 0 rgba(0, 0, 0, 0.55);
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transition: opacity 120ms ease;
  will-change: transform;
}

.jojo-pop.is-visible {
  opacity: 1;
  visibility: visible;
}

.jojo-pop__bar {
  display: flex;
  align-items: center;
  height: 30px;
  padding: 0 10px;
  overflow: hidden;
  margin-bottom: 4px;
}

.jojo-pop__title {
  font-family: var(--font-ui);
  font-size: 11px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  white-space: nowrap;
}

.jojo-pop__media {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  background: var(--ink);
}

/* ---- Click knock effect ----
   Short strokes bursting out from the click point; purely visual.
   Colors alternate yellow/ink so half the strokes read on the ink
   ground and the other half on the yellow accent fills. */

.knock {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 999;
  pointer-events: none;
}

.knock span {
  position: absolute;
  left: -1.5px;
  top: -6px;
  width: 3px;
  height: 12px;
  background: var(--yellow);
  animation: knock-line 320ms cubic-bezier(0.2, 0.6, 0.3, 1) forwards;
}

.knock span:nth-child(even) {
  background: var(--ink);
}

@keyframes knock-line {
  from {
    opacity: 1;
    transform: rotate(var(--a)) translateY(-8px) scaleY(1);
  }
  to {
    opacity: 0;
    transform: rotate(var(--a)) translateY(-22px) scaleY(0.4);
  }
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  .ticker__track,
  .showcase__tape-track {
    animation: none;
  }

  /* Frames fade in place instead of flying in from the edges. */
  .showcase__frame {
    --tx: 0;
    --ty: 0;
    transition: opacity 240ms ease;
  }

  .knock {
    display: none;
  }
}

/* ---- Small screens ---- */

@media (max-width: 640px) {
  :root {
    --gutter: 14px;
  }

  /* Break "WACHO" onto its own line; fitText() sizes the widest line */
  .masthead {
    line-height: 0.86;
  }

  .masthead__break {
    display: block;
  }
}
