/* ============================================================================
   PINWHEEL NAV — shared across every page.

   THE GRID
   Not a uniform 4x2. Four columns at 20% / 30% / 30% / 20%, where the two
   middle columns are offset from each other by half a button, and each
   column's horizontal seam sits half a button above or below centre --
   alternating. The result: every one of the eight tiles is a different size,
   and all four seams run tangent to an edge of the centre button.

   The button is not sitting ON the grid. It is a HOLE in it that the four
   middle tiles pinwheel around. That is the whole trick.

        +--------+--------------+------------+--------+
        |        |      2       |            |   4    |
        |   1    |         +----+---+    3   +--------+
        |        +---------|  BTN  |         |        |
        +--------+         +---+---+---------+   8    |
        |   5    |      6      |       7     |        |
        +--------+-------------+-------------+--------+

   Geometry verified against the reference at 960x993: all eight tiles match
   to the pixel.
   ========================================================================== */

:root{
  --button-size: 90px;
  --nav-tile-gap: 10px;

  /* Half-button and gap shorthands -- the grid is written in these. */
  --b2: calc(var(--button-size) / 2);
  --g:  var(--nav-tile-gap);
  --g2: calc(var(--nav-tile-gap) * 2);

  --tile-transition: .35s cubic-bezier(.4,0,.2,1);
  --nav-easing: cubic-bezier(.4,0,.2,1);
  --nav-easing-swoop: cubic-bezier(.6,0,0,1);

  /* MEASURED from the reference: the page is exactly 3x viewport height. */
  --scroll-length: 300vh;

  --overscroll-progress: 0;

  /* ---- Angela Clemons design system -------------------------------
     Lifted verbatim from the portfolio's :root. Ink scale + warm accents.  */
  --ink:    #0a0e14;     /* the black everything inverts to               */
  --ink2:   #1c2333;
  --ink3:   #2e3a50;
  --mid:    #6b7a99;
  --lite:   #a8b4cc;
  --ghost:  #f0f3f8;
  --white:  #ffffff;
  --orange: #d4622a;     /* signature accent — the dot in the A           */
  --orange-d:#bf5523;
  --orange-p:#f5e0d3;
  --rust:   #a43b27;
  --amber:  #e8a020;
  --amber-p:#fdf3e0;

  --bg: var(--ghost);
  --accent: var(--orange);
  --line: rgba(10,14,20,.22);

  /* The grid stays orange. It draws itself in orange and it STAYS orange —
     it does not hand off to grey. The lines are the brand, not scaffolding. */
  --grid-line: var(--orange);

  /* ---- TILE PALETTE — three weights per hue. See palette-study.html ----
     VIVID route: tile L 0.775, ink L 0.395, mid L 0.600.

     Every section owns a HUE, not a colour, and gets three weights of it.
     -lite is the home tile. -ink is that tile's LABEL, its ARTWORK, and the
     ground its page opens on — one token doing all three is what makes
     clicking a tile feel like one continuous move rather than a page load.
     -mid carries hover, rules and accents inside the page.

     Lightness is held constant across the eight; CHROMA IS NOT, deliberately.
     sRGB's chroma ceiling swings from ~0.07 to ~0.27 with hue AND lightness,
     so a single locked chroma is silently clipped on half the wheel — which
     is exactly what the previous "L 0.550 / C 0.152" set did to amber (-24%),
     teal (-38%), blue (-26%) and green (-5%). That clipping, not the
     lightness alone, is why that set read muted. Equal lightness is what the
     eye reads as "one set"; chroma was never carrying the cohesion.

     L 0.775 is the specific choice: there the eight hues span C 0.118-0.135,
     a 1.14x spread, so they genuinely weigh the same. Go lighter and the
     spread blows out to 1.61x at L 0.855 — greens and teals go neon beside
     pale blues and no cap can level it. Bright had to cost saturation or
     evenness; this buys evenness.

     Rust moved 33 -> 16 deg. At 11 deg from orange the two tiles were one
     colour that looked like a rendering bug. Blue and indigo opened likewise.

     TILES 1 AND 7 INVERT — deep ground, light type. Two dark tiles in eight
     is rhythm, not decoration: eight bright rectangles is a swatch library
     and it strobes. See the .tile-1,.tile-7 rule below. */
  --t1-lite:#fd986c; --t1-ink:#7c221c; --t1-mid:#c75d29;  /* Orange  44° · 4.70:1 · INVERTED · About Angie */
  --t2-lite:#fd9299; --t2-ink:#782044; --t2-mid:#c95461;  /* Rust    16° · 4.67:1 */
  /* 3 and 4 are swapped against hue order on purpose: tile 3 is the prominent
     slot right of the button, and it holds Project Coffer, whose brand is
     green. Same eight hues, same weights — only which slot owns which. */
  --t3-lite:#7bcc7f; --t3-ink:#00524a; --t3-mid:#3a9742;  /* Green  145° · 4.68:1 · Coffer */
  --t4-lite:#e5aa44; --t4-ink:#683900; --t4-mid:#a97500;  /* Amber   78° · 4.68:1 */
  --t5-lite:#1ecfd0; --t5-ink:#005156; --t5-mid:#009394;  /* Teal   196° · 4.71:1 */
  --t6-lite:#6bbefd; --t6-ink:#00487e; --t6-mid:#0087cf;  /* Blue   243° · 4.67:1 */
  --t7-lite:#b2a9fd; --t7-ink:#5b2e77; --t7-mid:#7e6ed3;  /* Indigo 288° · 4.75:1 · INVERTED */
  --t8-lite:#e695df; --t8-ink:#75214d; --t8-mid:#b05baa;  /* Plum   330° · 4.68:1 */

  /* Impact first so it's the real thing wherever it's installed; Anton is the
     closest free web equivalent and covers Linux, where Impact usually isn't
     present. Same condensed heavy grotesque either way. */
  --font-impact:  Impact, 'Anton', 'Haettenschweiler', 'Arial Narrow Bold', sans-serif;
  /* Every display face is Impact now. Syne is gone. */
  --font-display: var(--font-impact);
  --font-body:    'DM Sans', ui-sans-serif, system-ui, sans-serif;
}

@media (max-width:1024px){
  :root{ --button-size: 64px; --nav-tile-gap: 5px; }
}

/* ========================================================================
   POINTER vs TOUCH COPY
   The loop is driven by a wheel on a pointer device and by a swipe on touch,
   and "hit the tab, or press Escape" is meaningless on a phone. Both variants
   ship in the markup and CSS picks. `hover:none` is the honest test — it asks
   what the INPUT can do rather than how wide the screen is, so a touchscreen
   laptop gets the right one too.
   ====================================================================== */
.only-touch{ display:none; }
@media (hover: none){
  .only-pointer{ display:none; }
  .only-touch{ display:inline; }
}

/* ========================================================================
   TAP PREVIEW

   The tap plays the artwork at FULL speed — the same animation the hover
   shows, not a compressed one. Compressing it was an attempt to make the tap
   feel snappy, and it did the opposite of what was wanted: the whole point of
   the tap transition is that a touch device can finally SEE this artwork, and
   running it at 0.42x stagger meant it flashed past.

   The knob stays because it is useful and costs nothing, but it is 1 by
   default. nav.js measures the real duration and waits exactly that long.
   ====================================================================== */
.tile.tap-preview{ --stagger: 1; }

/* --- reset ------------------------------------------------------------- */
*{ margin:0; padding:0; box-sizing:border-box; -webkit-font-smoothing:antialiased; }
html{ scrollbar-width:none; }
::-webkit-scrollbar{ display:none; }
html,body{ overscroll-behavior:none; }
body{
  background: var(--bg); color: var(--ink);
  font-family: var(--font-body);
}
::selection{ background: var(--accent); color:#fff; }
h1,h2,h3,p{ margin:0; font-weight:500; letter-spacing:-.02em; }
/* Impact across every display face — tile titles included, so "About Angie",
   "Voice & Tone", "Typography" all sit in the same voice. */
h1,h2,h3,.tile-title{
  font-family: var(--font-display);
  font-weight:400; text-transform:uppercase; letter-spacing:-.005em;
}
img{ max-width:100%; display:block; }


/* ========================================================================
   STAGE
   ====================================================================== */
.nav-container{ position:fixed; inset:0; z-index:50; pointer-events:none; }
.nav{ position:fixed; inset:0; overflow:hidden; pointer-events:none; }

/* ---- THE IDENTITY BAND ---------------------------------------------------
   Home reserves a strip at the bottom for the identity line. The tiles are
   positioned in PERCENTAGES of .nav, so shortening .nav lifts the whole
   pinwheel by exactly this much and nothing else moves.

   It cannot jump, because this is the assembly's TARGET geometry, not a change
   applied at the end: the tiles have been easing toward a resting position
   32px higher the whole way down. The intro card is unaffected — it is centred
   off 50vw/50vh, not off .nav.

   Home only. In page mode the tiles are parked offscreen and there is nothing
   to reserve space for. */
body[data-nav-mode="home"] .nav{ bottom: var(--home-footer); }
:root{ --home-footer: 32px; }
@media (max-width:640px){ :root{ --home-footer: 52px; } }

/* The mark is the pinwheel's PIVOT, so it has to lift with it. Reserving the
   band without this leaves the button centred on the viewport while the tiles
   are centred 16px (52 on a phone) higher — which on a phone was enough to
   drop the mark straight onto the Box Buddy title. Half the reserve, because
   only the bottom edge moved.

   Measured, for the same reason the base rule is — see .menu below. The band is
   already subtracted from --nav-h, because it is subtracted from .nav itself,
   so this only has to halve what it is given. */
body[data-nav-mode="home"] .menu{
  --menu-y: calc(var(--nav-h, calc(100svh - var(--home-footer))) / 2 - var(--button-size) / 2);
}
.nav-container.open .nav{ pointer-events:auto; }

.nav-wrapper{ position:absolute; inset:0; z-index:2; }
.nav-wrapper-gridlines{ z-index:1; opacity:.4; pointer-events:none; }

/* White backdrop behind the tiles, but only once the menu is open. The
   0.2s delay on open lets the tiles start moving before the page behind
   them is covered -- so it reads as tiles arriving, not a panel appearing. */
.nav-wrapper{ transition: background-color .3s var(--nav-easing); }
.nav-container.open .nav-wrapper{
  background-color: var(--bg);
  transition: background-color .3s var(--nav-easing) .2s;
}


/* ========================================================================
   THE PINWHEEL GRID
   ====================================================================== */
.nav-tile{ position:absolute; padding:0; will-change: transform; }

/* transform-origin points at the CENTRE of the screen for every tile, so
   scaling up throws each tile's mass outward instead of bloating in place. */
.nav-tile-1{ left:var(--g);  top:var(--g);    width:calc(20% - var(--g2));        height:calc(50% + var(--b2) - var(--g));  transform-origin:100% 100%; }
.nav-tile-2{ left:20%;       top:var(--g);    width:calc(30% + var(--b2));        height:calc(50% - var(--b2) - var(--g2)); transform-origin:100% 100%; }
.nav-tile-3{ right:20%;      top:var(--g);    width:calc(30% - var(--b2) - var(--g)); height:calc(50% + var(--b2) - var(--g)); transform-origin:0 100%; }
.nav-tile-4{ right:var(--g); top:var(--g);    width:calc(20% - var(--g2));        height:calc(50% - var(--b2) - var(--g2)); transform-origin:0 100%; }
.nav-tile-5{ left:var(--g);  bottom:var(--g); width:calc(20% - var(--g2));        height:calc(50% - var(--b2) - var(--g2)); transform-origin:100% 0; }
.nav-tile-6{ left:20%;       bottom:var(--g); width:calc(30% - var(--b2) - var(--g)); height:calc(50% + var(--b2) - var(--g)); transform-origin:100% 0; }
.nav-tile-7{ right:20%;      bottom:var(--g); width:calc(30% + var(--b2));        height:calc(50% - var(--b2) - var(--g2)); transform-origin:0 0; }
.nav-tile-8{ right:var(--g); bottom:var(--g); width:calc(20% - var(--g2));        height:calc(50% + var(--b2) - var(--g)); transform-origin:0 0; }

/* Only the four outer corners of the whole grid are rounded. */
.nav-tile-1 .tile{ border-top-left-radius:8px; }
.nav-tile-4 .tile{ border-top-right-radius:8px; }
.nav-tile-5 .tile{ border-bottom-left-radius:8px; }
.nav-tile-8 .tile{ border-bottom-right-radius:8px; }

/* --- below desktop: 2 x 4, same half-button stagger -------------------
   1024px is the site's tablet boundary, shared with the column grid in
   grid.css. It used to be 991 here, which meant the home page and the inner
   pages re-formed at different widths — a design system cannot document a
   breakpoint it does not actually keep. */
@media (max-width:1024px){
  .nav-tile-1{ left:var(--g);  right:auto; top:var(--g);    bottom:auto;  width:calc(50% - var(--b2) - var(--g2)); height:calc(25% - var(--g2)); }
  .nav-tile-2{ right:var(--g); left:auto;  top:var(--g);    bottom:auto;  width:calc(50% + var(--b2) - var(--g)); height:calc(25% - var(--g2)); transform-origin:0 100%; }
  .nav-tile-3{ left:var(--g);  right:auto; top:25%;         bottom:auto;  width:calc(50% + var(--b2) - var(--g)); height:calc(25% - var(--b2) - var(--g)); transform-origin:100% 100%; }
  .nav-tile-4{ right:var(--g); left:auto;  top:25%;         bottom:auto;  width:calc(50% - var(--b2) - var(--g2)); height:calc(25% + var(--b2)); transform-origin:0 100%; }
  .nav-tile-5{ left:var(--g);  right:auto; bottom:25%;      top:auto;     width:calc(50% - var(--b2) - var(--g2)); height:calc(25% + var(--b2)); transform-origin:100% 0; }
  .nav-tile-6{ right:var(--g); left:auto;  bottom:25%;      top:auto;     width:calc(50% + var(--b2) - var(--g)); height:calc(25% - var(--b2) - var(--g)); transform-origin:0 0; }
  .nav-tile-7{ left:var(--g);  right:auto; bottom:var(--g); top:auto;     width:calc(50% + var(--b2) - var(--g)); height:calc(25% - var(--g2)); transform-origin:100% 0; }
  .nav-tile-8{ right:var(--g); left:auto;  bottom:var(--g); top:auto;     width:calc(50% - var(--b2) - var(--g2)); height:calc(25% - var(--g2)); transform-origin:0 0; }

  .nav-tile-2 .tile{ border-top-right-radius:8px; }
  .nav-tile-3 .tile{ border-top-left-radius:0; }
  .nav-tile-7 .tile{ border-bottom-left-radius:8px; }
}


/* ========================================================================
   TILES
   ====================================================================== */
.tile{
  display:flex; flex-flow:column; justify-content:space-between;
  width:100%; height:100%;
  padding:23px;
  border-radius:0;
  overflow:hidden;
  text-decoration:none;
  /* Default: bright ground, deep type. The two anchor tiles override this
     pair below to swap them. */
  --tile-bg: var(--tile-lite);
  --tile-fg: var(--tile-ink);
  background: var(--tile-bg);
  color: var(--tile-fg);
  transition: color .35s cubic-bezier(.4,0,.2,1), background-color .35s cubic-bezier(.4,0,.2,1);
  /* NAMED, because .visual-square is also a size container and sits between
     the tile and the artwork. An unnamed @container query from inside the
     art resolves to the SQUARE — which is ~219px and therefore always
     matched `max-height:300px`, silently applying the short-tile layout at
     every size. Unnamed cq UNITS still resolve to the nearest container
     (the square), which is what the offsets want. */
  container-type: size;
  container-name: tile;
}
.tile *{ pointer-events:none; }
@media (max-width:1024px){ .tile{ padding:14px; } }

/* Each tile carries its hue's three weights, then --tile-bg/--tile-fg pick
   which two are showing. Keeping all three around (not just the two in use)
   is what lets the page transition and the inner page reach for the same
   hue without re-deriving it from a tile index. */
.tile-1{ --tile-lite:var(--t1-lite); --tile-ink:var(--t1-ink); --tile-mid:var(--t1-mid); }
.tile-2{ --tile-lite:var(--t2-lite); --tile-ink:var(--t2-ink); --tile-mid:var(--t2-mid); }
.tile-3{ --tile-lite:var(--t3-lite); --tile-ink:var(--t3-ink); --tile-mid:var(--t3-mid); }
.tile-4{ --tile-lite:var(--t4-lite); --tile-ink:var(--t4-ink); --tile-mid:var(--t4-mid); }
.tile-5{ --tile-lite:var(--t5-lite); --tile-ink:var(--t5-ink); --tile-mid:var(--t5-mid); }
.tile-6{ --tile-lite:var(--t6-lite); --tile-ink:var(--t6-ink); --tile-mid:var(--t6-mid); }
.tile-7{ --tile-lite:var(--t7-lite); --tile-ink:var(--t7-ink); --tile-mid:var(--t7-mid); }
.tile-8{ --tile-lite:var(--t8-lite); --tile-ink:var(--t8-ink); --tile-mid:var(--t8-mid); }

/* THE TWO ANCHORS. Must come after the block above — same specificity, so
   source order is doing the work. Flip which tiles are listed here and the
   grid's whole rhythm changes; nothing else needs touching. */
.tile-1, .tile-7{ --tile-bg:var(--tile-ink); --tile-fg:var(--tile-lite); }

/* Was wrapped in @media (any-hover: hover). Unwrapped, and every :hover now
   also answers to .previewing — the class the tap transition sets on touch,
   where :hover never fires. One selector serves both inputs instead of the
   artwork being unreachable on a phone. */
.tile:is(:hover, .previewing){ background-color: var(--ink) !important; color:#fff !important; }


.tile-title{
  position:relative; z-index:5;
  transform-origin:0 0;
  /* Inherits --tile-fg rather than forcing white. That is the whole revision:
     the label is a deep shade of the tile's OWN hue, which is what makes each
     tile read as a self-contained world instead of eight coloured rectangles
     with the same label pasted on. Clears 4.67:1 or better on all eight. */
  color: inherit;
  /* MEASURED: the reference's exact clamp. Small screens track 2.75vw, large
     ones flatten to 1.375vw -- so the label never runs away on a wide monitor. */
  font-size: min(2.75vw - 6px, 1.375vw + 15px);
  line-height:1; font-weight:500;
}
@media (max-width:1024px){ .tile-title{ font-size:20px; } }

/* Artwork sits in the remaining space, bottom-aligned. Sized off the tile's
   smaller edge with container units so it stays square at any tile ratio --
   and these tiles have eight different ratios. */
.visual{ flex:1; display:flex; align-items:flex-end; justify-content:flex-end; min-height:0; }
/* The box sizes itself off the TILE (nearest ancestor container), then becomes
   a container itself so the artwork inside measures against the box rather
   than the tile. Without that second step, art sized in cq units is scaled to
   the tile and overflows this box on every non-square tile — and all eight
   tiles are non-square. */
.visual-square{
  position:relative;
  width:  min(46cqw, 60cqh);
  height: min(46cqw, 60cqh);
  container-type: size;
}
@media (max-height:399px), (max-width:1024px) and (max-height:480px), (max-width:249px){
  .visual{ display:none; }
}


/* ========================================================================
   GRIDLINES -- the ghost layer.
   Each line runs the full viewport and is counter-scaled on its thickness
   axis by JS, so it stays exactly 1px however far the tiles are zoomed.
   ====================================================================== */
.tile-line-wrapper{ position:absolute; inset:0; }
.tile-line{ position:absolute; background: var(--grid-line); pointer-events:none; }
/* Counter-scale reads ONE custom property the engine sets per frame, instead
   of the engine writing a transform onto all 32 lines. Same result, 32 style
   writes a frame become 1 — which is the difference between smooth and
   stuttering on a phone. */
.tile-line.nav-l, .tile-line.nav-r{ top:50%; width:1px; height:100vh; height:100svh;
  transform: translateY(-50%) scaleX(var(--inv-scale, 1)); }
.tile-line.nav-l{ left:0; }  .tile-line.nav-r{ right:0; }
.tile-line.nav-t, .tile-line.nav-b{ right:50%; height:1px; width:100vw;
  transform: translateX(50%) scaleY(var(--inv-scale, 1)); }
.tile-line.nav-t{ top:0; }   .tile-line.nav-b{ bottom:0; }
.nav-tile-gridlines .tile-line{ opacity:0; }


/* ========================================================================
   CENTRE BUTTON
   .menu is a fixed, button-sized box pinned to dead centre; the button is
   flex-centred inside it, so any size JS gives it grows symmetrically about
   the exact point the four middle tiles pinwheel around.
   ====================================================================== */
/* Every menu position is expressed as an offset from the top-left, through
   two custom properties. Because vars are substituted before `transform`
   interpolates, the states still tween — but each one can be written as an
   absolute destination instead of a stack of nested percentages. */
.menu{
  position:fixed; left:0; top:0;
  width:var(--button-size); height:var(--button-size);
  --menu-x: calc(50vw - var(--button-size) / 2);
  /* --nav-h is the MEASURED height of the tile grid, written by nav.js on every
     resize (see syncNavHeight). No viewport unit is correct here: `vh` is the
     URL-bar-hidden height and `svh` the bar-visible one, and .nav is whichever
     the browser is currently giving a fixed element — so one of the two states
     is always wrong. The svh fallback only has to hold for the frame before
     the first resize runs. */
  --menu-y: calc(var(--nav-h, 100svh) / 2 - var(--button-size) / 2);
  transform: translate(var(--menu-x), var(--menu-y));
  transition: transform .5s var(--nav-easing-swoop) .05s;
  display:flex; align-items:center; justify-content:center;
  z-index:101; pointer-events:none;
}

/* On inner pages the closed button parks against the left edge as a tab,
   one sixth of it hanging off screen — present, but not competing with the
   page. Opening the menu slides it back to the pinwheel's pivot. */
body[data-nav-mode="page"] .nav-container:not(.open) .menu{
  --menu-x: calc(var(--button-size) / -6);
}
@media (max-width:1024px){
  body[data-nav-mode="page"] .nav-container:not(.open) .menu{
    --menu-x: calc(50vw - var(--button-size) / 2);
    --menu-y: calc(var(--nav-h, 100svh) - var(--button-size) + var(--button-size) / 6);
  }
}
.nav-wrapper-gridlines .menu{ z-index:1; }

.nav-button{
  position:relative; flex:none;
  width:100%; height:100%;
  border:none; border-radius:6px;
  cursor:pointer; font:inherit; color:#fff;
  overflow:hidden;
  pointer-events:auto;
  /* At rest this is the favicon: an ink square holding the A. Push past the
     bottom of a page and orange sweeps around it like a dial. At 100% the
     menu opens itself. */
  background: conic-gradient(from 0deg,
    var(--orange) 0deg,
    var(--orange) calc(var(--overscroll-progress) * 360deg),
    var(--ink) calc(var(--overscroll-progress) * 360deg));
}
.nav-container.open .nav-button{ background: var(--ink); }

.nav-button-gridline{ position:relative; flex:none; width:100%; height:100%; }
.nav-button-gridline .tile-line{ background: var(--accent); }

.nav-button-content{
  position:absolute; inset:0;
  display:flex; flex-flow:column;
  padding: clamp(20px, 4.4%, 46px);
  pointer-events:none;
  /* The card's own width is set inline by the scroll engine, so making it a
     container lets the name and quote scale to the CARD instead of the
     viewport — they stay proportionally huge the whole way down. */
  container-type: inline-size;
}
.nav-button-title-container{ position:relative; flex:1; align-self:stretch; }
/* Both states are vertically centred in the card, with room left at the
   bottom for the A once it has walked into the corner. */
.nav-button-title{
  position:absolute; inset:0;
  display:flex; flex-flow:column; justify-content:center;
  padding-bottom: clamp(48px, 13%, 104px);
  transform-origin:0 50%;
  text-align:left;
}
.nav-button-title-2{ opacity:0; }

/* --- the introduction ------------------------------------------------
   Lives inside the big card at scroll 0 and simply fades as the card
   shrinks. Nothing here has to survive down to 90px — by then the card has
   become the favicon, which is the whole point. */
.intro{
  display:flex; flex-flow:column; align-items:center; text-align:center;
  gap: clamp(14px, 3.4cqi, 40px);
}
.intro-photo{
  flex:none;
  width: clamp(96px, 30cqi, 280px); aspect-ratio:1;
  border-radius:50%; overflow:hidden;
  background: var(--ghost);
  box-shadow: 0 8px 40px rgba(0,0,0,.12);
}
.intro-photo img{ width:100%; height:100%; object-fit:cover; }
.intro-copy{ min-width:0; display:flex; flex-flow:column; align-items:center; gap:.55em; }
.intro-hi{
  font-family: var(--font-impact); font-weight:400;
  /* cqi = 1% of the card's inline size, so the name scales with the CARD
     rather than the viewport — it stays huge at every card size. */
  font-size: clamp(30px, 13cqi, 150px);
  line-height:1; letter-spacing:-.005em; text-transform:uppercase;
  text-wrap:balance;
}
.intro-hi .dot{ color: var(--orange); }
.intro-role{
  font-family: var(--font-body); font-weight:500;
  font-size: clamp(13px, 3.1cqi, 36px); line-height:1.3;
  letter-spacing:-.01em; opacity:.78; max-width:30ch; text-wrap:balance;
}

/* --- the quote, state two ------------------------------------------- */
/* Centred to match the intro card, so the two states sit in the same place
   and the swap reads as a change of content, not a change of layout. */
.nav-quote{
  display:flex; flex-flow:column; align-items:center; text-align:center;
  gap: clamp(12px, 3%, 30px);
}
.nav-quote blockquote{
  font-family: var(--font-impact); font-weight:400;
  font-size: clamp(20px, 6.6cqi, 74px);
  line-height:1.02; letter-spacing:-.01em; text-transform:uppercase;
  max-width:17ch;
}
.nav-quote blockquote em{ font-style:normal; color: var(--orange); }
.nav-quote cite{
  font-family: var(--font-body); font-style:normal; font-weight:500;
  font-size: clamp(11px, 2.1cqi, 22px); letter-spacing:.02em; opacity:.6;
}
@media (max-width:1024px){ .intro-role{ max-width:100%; } }

/* menu glyphs: logo / hamburger / close, cross-faded by JS */
.nav-button-svg{
  position:absolute; left:50%; top:50%; width:44%; height:44%;
  transform: translate(-50%,-50%);
  display:flex; align-items:center; justify-content:center;
  transition: transform .3s cubic-bezier(.25,0,.25,1), opacity .3s cubic-bezier(.25,0,.25,1);
}
.nav-button-svg svg{ width:100%; height:100%; fill:currentColor; }
.nav-button-svg.menu-svg-hidden{ opacity:0; transform:translate(-50%,-50%) scale(.8); }

/* --- WARP -------------------------------------------------------------
   The button does not just scale between states, it squashes. Opening it
   stretches wide and flat then overshoots back; closing it snaps the other
   way. This is what makes the menu feel elastic rather than mechanical. */
@keyframes nav-button-open{
  20%{ transform: scale(2, .7); }
  40%{ transform: scale(1.2, .82); }
  60%{ transform: scale(1, .93); }
  75%{ transform: scale(.98, .97); }
  90%{ transform: scale(.99, .99); }
  100%{ transform: scale(1); }
}
@keyframes nav-button-close{
  30%{ transform: scale(1.2, .85); }
  60%{ transform: scale(1.9, .7); }
  80%{ transform: scale(1.2, calc(2/3)); }
  90%{ transform: scale(.7, .7); }
  100%{ transform: scale(.666667); }
}
.nav-container.warp.open .nav-button{
  animation: nav-button-open .4s linear .1s forwards;
  transform: scale(calc(2/3)); transition:none;
}
.nav-container.warp:not(.open) .nav-button{
  animation: nav-button-close .3s linear .05s forwards; transition:none;
}
.nav-container:not(.warp) .nav-button{ transform: scale(calc(2/3)); }

@media (max-width:1024px){
  .nav-container.warp.open .nav-button{
    transition: transform .5s var(--nav-easing-swoop) .1s; transform:scale(1); animation:none;
  }
  .nav-container.warp:not(.open) .nav-button{
    transition: transform .3s var(--nav-easing); transform:scale(.75); animation:none;
  }
}


/* ========================================================================
   CLOSED STATE (inner pages)
   The tiles park at exactly the same scale+offset the home page's scroll
   engine drives them through. That shared geometry is why navigating and
   opening the menu feel like the same gesture.
   ====================================================================== */
/* Deliberately vh, not svh. This is a displacement SCALAR, not a box that has
   to fit the screen — and svh/dvh would make it change as the URL bar hides,
   shifting every tile mid-scroll. JS mirrors it with Math.max(innerWidth,
   innerHeight) for the same reason. */
.nav-container{ --yeet: calc((max(100vw, 100vh) - 90px) / -4); }

.nav-container:not(.open) .nav-tile{ transition: transform .5s var(--nav-easing-swoop); }
.nav-container.open .nav-tile{
  opacity:1; transform:none;
  transition: transform .5s var(--nav-easing-swoop) .1s;
}
.nav-container:not(.open) .nav-wrapper-gridlines{ opacity:0; }
.nav-container.open  .nav-wrapper-gridlines{ opacity:.4; transition: opacity .5s ease .2s; }

/* The vector table. Sign = which side it parks on, magnitude = how far.
   Corner tiles at 4/2 start ~3600px offscreen; the inner four at 0.1-1
   barely drift. That spread is what stops it reading as a plain zoom. */
.nav-container:not(.open) .nav-tile-1{ transform: scale(2) translate(calc(var(--yeet) *  4),   calc(var(--yeet) *  2)); }
.nav-container:not(.open) .nav-tile-2{ transform: scale(2) translate(calc(var(--yeet) * -0.1), calc(var(--yeet) *  1)); }
.nav-container:not(.open) .nav-tile-3{ transform: scale(2) translate(calc(var(--yeet) * -1),   calc(var(--yeet) * -0.1)); }
.nav-container:not(.open) .nav-tile-4{ transform: scale(2) translate(calc(var(--yeet) * -4),   calc(var(--yeet) *  2)); }
.nav-container:not(.open) .nav-tile-5{ transform: scale(2) translate(calc(var(--yeet) *  4),   calc(var(--yeet) * -2)); }
.nav-container:not(.open) .nav-tile-6{ transform: scale(2) translate(calc(var(--yeet) *  1),   calc(var(--yeet) *  0.1)); }
.nav-container:not(.open) .nav-tile-7{ transform: scale(2) translate(calc(var(--yeet) *  0.1), calc(var(--yeet) * -1)); }
.nav-container:not(.open) .nav-tile-8{ transform: scale(2) translate(calc(var(--yeet) * -4),   calc(var(--yeet) * -2)); }

@media (max-width:1024px){
  .nav-container:not(.open) .nav-tile-1{ transform: scale(2) translate(calc(var(--yeet) *  2),    calc(var(--yeet) *  3)); }
  .nav-container:not(.open) .nav-tile-2{ transform: scale(2) translate(calc(var(--yeet) * -2),    calc(var(--yeet) *  3)); }
  .nav-container:not(.open) .nav-tile-3{ transform: scale(2) translate(calc(var(--yeet) *  0.25), calc(var(--yeet) *  1.5)); }
  .nav-container:not(.open) .nav-tile-4{ transform: scale(2) translate(calc(var(--yeet) * -1.5),  calc(var(--yeet) *  0.25)); }
  .nav-container:not(.open) .nav-tile-5{ transform: scale(2) translate(calc(var(--yeet) *  1.5),  calc(var(--yeet) * -0.25)); }
  .nav-container:not(.open) .nav-tile-6{ transform: scale(2) translate(calc(var(--yeet) * -0.25), calc(var(--yeet) * -1.5)); }
  .nav-container:not(.open) .nav-tile-7{ transform: scale(2) translate(calc(var(--yeet) *  2),    calc(var(--yeet) * -3)); }
  .nav-container:not(.open) .nav-tile-8{ transform: scale(2) translate(calc(var(--yeet) * -2),    calc(var(--yeet) * -3)); }
}

/* freeze every transition while the window is being dragged */
.nav-container.no-transitions *{ transition:none !important; }


/* ========================================================================
   HOME: load-in
   ====================================================================== */
body[data-nav-mode="home"] .nav-wrapper{ background:none !important; }
body[data-nav-mode="home"] .nav-container:not(.open) .nav-tile{ transition:none; }
body[data-nav-mode="home"] .nav-container:not(.open) .nav-tile[class]{ transform:none; }
body[data-nav-mode="home"] .nav-container:not(.open) .nav-wrapper-gridlines{ opacity:.4; }
body[data-nav-mode="home"] .nav-button{ transform:none !important; animation:none !important; }

.home-logo-container{
  position:absolute; left:50%; bottom:50%;
  width:100%; height:100%;
  transform: translate(-50%,50%);
}
.home-logo{ width:100%; height:100%; display:block; overflow:visible; }
.home-logo path{
  vector-effect:non-scaling-stroke;
  fill:currentColor; fill-opacity:0;
  stroke:currentColor; stroke-width:1px; stroke-dashoffset:1000;
  animation: drawIn 1.25s cubic-bezier(.4,0,.3,1) forwards .5s;
}
/* The dot is always orange — it's the one fixed note in the mark, whether
   the A is drawing in ink on white or in white on ink. */
.home-logo .a-dot{ fill: var(--orange); stroke: var(--orange); }
@keyframes drawIn{ to{ stroke-dashoffset:0; } }

/* The logo's end position is dead centre of the button it will BECOME after
   a full scroll -- it walks to where the button is going to be. */
.nav-container.animating-in .home-logo-container{
  transition: width 1.5s cubic-bezier(.5,0,.05,1) 1s, height 1.5s cubic-bezier(.5,0,.05,1) 1s,
              left 1.25s cubic-bezier(.5,0,.3,1) 1.25s, bottom 1.25s cubic-bezier(.5,0,.3,1) 1.25s;
}
.nav-container.final .home-logo-container{ width:91px; height:91px; left:45px; bottom:44.5px; }
@media (max-width:1024px){
  .nav-container.final .home-logo-container{ width:66px; height:66px; left:32px; bottom:31.5px; }
}
.nav-container.animating-in .home-logo path{
  transition: fill-opacity .75s ease-in-out 1.25s, stroke-opacity .5s ease-in-out 1.5s;
}
.nav-container.final .home-logo path{ fill-opacity:1; stroke-opacity:0; }

.nav-container.animating-in .nav-button-title-container{ transition: opacity 1.25s ease-in-out 1.25s; }
.nav-container:not(.final) .nav-button-title-container{ opacity:0; }

.nav-button-gridline .tile-line.nav-l{ transform:translateY(-50%) scaleY(0); transform-origin:center top; }
.nav-button-gridline .tile-line.nav-r{ transform:translateY(-50%) scaleY(0); transform-origin:center bottom; }
.nav-button-gridline .tile-line.nav-t{ transform:translateX(50%)  scaleX(0); transform-origin:right center; }
.nav-button-gridline .tile-line.nav-b{ transform:translateX(50%)  scaleX(0); transform-origin:left center; }

.scroll-chevrons{ display:none; position:absolute; inset:0; }
.scroll-chevrons-container{ opacity:0; transition:opacity 1.5s; }
.scroll-chevrons-container.visible{ opacity:1; }
.scroll-chevron{
  /* vmin, not cq — .scroll-chevrons is a sibling of the content box, so it
     isn't inside the card's container context. */
  position:absolute; width: clamp(24px, 3.6vmin, 46px);
  right: clamp(16px, 2.8vmin, 40px);
  color: currentColor; opacity:.4;
  transition: transform 1.6s cubic-bezier(.2,0,.3,1), opacity 1.5s cubic-bezier(.2,0,.3,1);
}
.scroll-chevron.activated{
  transform: translateY(9px); opacity:1;
  transition: transform .65s cubic-bezier(.6,0,.6,1), opacity .65s cubic-bezier(.6,0,.6,1);
}
/* stacked tight so they read as one arrow, not three separate marks */
.scroll-chevron.chevron-1{ bottom: calc(clamp(16px, 2.8vmin, 40px) + 22px); }
.scroll-chevron.chevron-2{ bottom: calc(clamp(16px, 2.8vmin, 40px) + 12px); }
.scroll-chevron.chevron-3{ bottom: clamp(16px, 2.8vmin, 40px); }
@media (max-width:1024px){
  .scroll-chevron{ width:22px; }
  .scroll-chevron.activated{ transform:translateY(7px); }
}

.scroll-spacer{ height: var(--scroll-length); pointer-events:none; }


/* ========================================================================
   PAGE TRANSITION -- clicking a tile
   The tile inflates to fill the viewport and goes dark while the button
   swoops off; the incoming page then paints its own hero in that same dark,
   so the navigation reads as one continuous move instead of a load.
   ====================================================================== */
.nav-container.close .menu{
  z-index:100;
  --menu-x: calc(100vw - var(--button-size) * 1.6) !important;
  transition: transform .5s var(--nav-easing-swoop) .05s !important;
}
@media (max-width:1024px){
  .nav-container.close .menu{
    --menu-x: calc(50vw - var(--button-size) / 2) !important;
    --menu-y: calc(var(--nav-h, 100svh) - var(--button-size) * 1.6) !important;
  }
}
.nav-container.close .nav-tile{ opacity:0; transition:.2s ease; }
.nav-container.close .nav-wrapper-gridlines{ opacity:0 !important; }

.nav-tile.enlarge{
  opacity:1 !important; z-index:99;
  width:100vw !important; height:100vh !important; height:100svh !important;
  transform:none !important;
  transition:.6s cubic-bezier(.65,0,.15,1) !important;
}
/* Full 600ms on mobile too. Trimming it to .44s was part of the same
   over-correction as compressing the artwork — the growth is a thing to watch,
   not a delay to minimise. */
/* Each tile keeps the two edges nearest its own corner, so it grows out of
   where it already sits. Forcing inset:0 instead makes every tile teleport
   to the top-left first, which breaks the illusion that the thing you
   clicked is the thing becoming the next page. */
.nav-tile-1.enlarge,.nav-tile-2.enlarge,.nav-tile-3.enlarge,.nav-tile-4.enlarge{ top:0 !important; bottom:auto !important; }
.nav-tile-5.enlarge,.nav-tile-6.enlarge,.nav-tile-7.enlarge,.nav-tile-8.enlarge{ bottom:0 !important; top:auto !important; }
.nav-tile-1.enlarge,.nav-tile-2.enlarge,.nav-tile-5.enlarge,.nav-tile-6.enlarge{ left:0 !important; right:auto !important; }
.nav-tile-3.enlarge,.nav-tile-4.enlarge,.nav-tile-7.enlarge,.nav-tile-8.enlarge{ right:0 !important; left:auto !important; }
@media (max-width:1024px){
  .nav-tile-2.enlarge,.nav-tile-6.enlarge{ right:0 !important; left:auto !important; }
  .nav-tile-3.enlarge,.nav-tile-7.enlarge{ left:0 !important; right:auto !important; }
}
/* The tile expands into its OWN deep tier, not a shared near-black. Eight
   sections, eight different arrivals — and because the incoming page paints
   its hero in the same --tN-ink, the colour never changes across the
   navigation. That shared value is the join. */
.nav-tile.enlarge .tile{
  background-color: var(--tile-ink) !important;
  border-radius:0 !important;
  justify-content:center;
  padding-left: 10vw;
}
/* EXCEPT tile 1. About is the one page that is about Angela rather than about
   a section, so it holds the house ink instead of tinting to its tile — and
   the expansion has to land on the colour the page actually opens in, or the
   navigation flashes rust and then cuts to black. */
.nav-tile-1.enlarge .tile{ background-color: var(--ink) !important; }
/* And tile 4, for a different reason but the same rule. Other Work holds the
   house ink with the amber as an accent — Angela's call, because seven pieces
   of loud key art run down that page and a tinted ground competed with all of
   them. So the expansion lands on --ink too. The title keeps --tile-lite, so
   it grows amber-on-ink, which is exactly what the page opens as. */
.nav-tile-4.enlarge .tile{ background-color: var(--ink) !important; }
/* Tile 3 is already showing Coffer's surface by the time you click it — the
   click always comes from the hover state — so it expands into that, and the
   Coffer page opens on the same value. */
.nav-tile-3.enlarge .tile{ background-color: var(--coffer-surface) !important; }
.nav-tile-3.enlarge .tile-title{ color: var(--coffer-ink) !important; }
/* Same trick for tile 6, and it pays off twice here: the wrapper page opens on
   this green AND the case study's own hero is this exact green, so the tile,
   the page and the case study are all one continuous colour. */
.nav-tile-5.enlarge .tile{ background-color: var(--tfy-canvas) !important; }
.nav-tile-5.enlarge .tile-title{ color:#000 !important; }
.nav-tile-6.enlarge .tile{ background-color: var(--bb-green) !important; }
.nav-tile-6.enlarge .tile-title{ color: var(--bb-surface) !important; }
/* And tile 7 into Squid Ink — the wrapper page opens on it and the case study's
   own hero is the same value, so tile, page and case study are one colour. */
.nav-tile-7.enlarge .tile{ background-color: var(--amz-ink) !important; }
.nav-tile-7.enlarge .tile-title{ color: var(--amz-white) !important; }
/* And tile 8 into Mayo's page ground — the wrapper opens on it and the case
   study's own body is the same value. */
/* Midnight, not the page grey. Every other wrapped tile grows into the colour
   of its case study's FIRST SCREENFUL (6 → hero green, 7 → header ink), and
   Mayo's hero is a `#012D61 → #003DA5` cobalt gradient. Expanding into the
   light page grey put a hard cobalt edge on screen the moment the frame faded
   up. The hover flood stays grey — that is the record card, a different
   moment. */
.nav-tile-8.enlarge .tile{ background-color: var(--mc-midnight) !important; }
.nav-tile-8.enlarge .tile-title{ color: #fff !important; }
.nav-tile.enlarge .tile-sub{ opacity:0; transition: opacity .3s ease; }
.nav-tile.enlarge .tile-title{
  color: var(--tile-lite) !important;
  transform: scale(4); transform-origin:left center;
  transition:.6s var(--nav-easing);
}
@media (max-width:1024px){
  .nav-tile.enlarge .tile{ padding-left:8vw; }
  .nav-tile.enlarge .tile-title{ transform: scale(2.5); }
}
.nav-tile.enlarge .visual{ opacity:0; transition:opacity .3s ease; }


/* ========================================================================
   INNER PAGE furniture
   ====================================================================== */
/* --page-ink is set per page from the tile that leads here, so the hero opens
   in the exact colour the outgoing tile expanded into. Falls back to the
   shared ink for any page that hasn't claimed a hue. */
.page-hero{
  min-height:100vh; min-height:100svh; background: var(--page-ink, var(--ink)); color: var(--page-lite, #fff);
  display:flex; align-items:center;
  padding: 0 10vw;
}
.page-hero h1{ font-size: clamp(48px, 11vw, 200px); line-height:1; }
@media (max-width:1024px){ .page-hero{ padding:0 8vw; } }

.page-section{ padding: 12vh 10vw; border-top:1px solid rgba(0,0,0,.12); }
.page-section h2{ font-size: clamp(28px, 4vw, 56px); margin-bottom:.5em; }
.page-section p{ max-width:60ch; line-height:1.6; font-weight:400; opacity:.75; }
@media (max-width:1024px){ .page-section{ padding:8vh 8vw; } }

/* The last section fades and shrinks as you push past the bottom, handing
   the screen over to the menu that is about to open. */
.page-section.dark{ background: var(--page-ink, var(--ink)); color: var(--page-lite, #fff); border-top:none; }
.page-section.dark > *{
  opacity: calc(1 - var(--overscroll-progress));
  transform: scale(calc(1 - var(--overscroll-progress) * .05));
}
.page-section.dark p{ opacity:.7; }

.overscroll-hint{ margin-top:3em; font-size:14px; opacity:.5; font-weight:400; }


/* ========================================================================
   TILE ARTWORK
   Placeholders in the spirit of the reference. Each block is independent —
   rip one out and drop your own in without touching anything above.
   ====================================================================== */
.bezier-point{
  /* Scale down inside the narrow 20% columns — at a fixed 20px the handles
     collide with each other and read as a blob. */
  position:absolute; width:min(20px, 22cqw); height:min(20px, 22cqw); z-index:3;
  fill:currentColor; stroke:currentColor; stroke-width:2px;
  transition: left var(--tile-transition), right var(--tile-transition),
              fill var(--tile-transition), stroke var(--tile-transition);
}
.bezier-point path{ vector-effect:non-scaling-stroke; }
@media (max-width:1024px){ .bezier-point{ transform:scale(.667); } }
/* Was wrapped in @media (any-hover: hover). Unwrapped, and every :hover now
   also answers to .previewing — the class the tap transition sets on touch,
   where :hover never fires. One selector serves both inputs instead of the
   artwork being unreachable on a phone. */
.tile:is(:hover, .previewing) .bezier-point{ fill:var(--ink); stroke:#fff; }


/* 1 · Framework */
.framework-curve{ position:absolute; inset:0; width:100%; height:100%; fill:none; stroke-width:2px; }
.framework-curve path{ vector-effect:non-scaling-stroke; stroke:currentColor; transition:opacity var(--tile-transition); }
.tile-1 .bezier-point.p1{ left:0; top:0; }
.tile-1 .bezier-point.p2{ right:0; top:calc(50% - 10px); }
.tile-1 .bezier-point.p3{ left:0; bottom:0; }

/* 2 · Design System — the palette assembles itself onto a screen.
   The monitor is drawn in currentColor so it inverts with the tile for free.
   The eight small tiles do NOT: they carry explicit palette values, so when
   the tile drains to black and white on hover they are the one thing that
   keeps its colour. That contrast is the whole point of the tile. */
.ds{
  position:absolute; inset:0;
  display:flex; flex-flow:column; align-items:center; justify-content:center;
}
/* overflow:hidden is doing the work — the small tiles are parked outside
   these bounds, so the screen reads as empty until they slide in past its
   edge. Without the clip they'd be visible scattered around the monitor,
   which is a different tile entirely. */
.ds-screen{
  position:relative; width:100%; height:70cqh;
  border:2px solid currentColor; border-radius:4px;
  /* SOLID at rest. A 2px outline reads as thin and wiry next to the Coffer
     tile's heavy mark, and an empty rectangle is not really an image of
     anything. Filled, it is a screen — and the hover then has something to
     open rather than just something to populate. */
  background: currentColor;
  overflow:hidden;
  display:grid; grid-template-columns:repeat(4,1fr); grid-template-rows:repeat(2,1fr);
  gap:3%; padding:3%;
  transition: border-color var(--tile-transition), background-color var(--tile-transition);
}
.ds-stand{ width:15cqw; height:9cqh;  background:currentColor;
           transition: background-color var(--tile-transition); }
.ds-base{  width:42cqw; height:5cqh;  background:currentColor; border-radius:2px;
           transition: background-color var(--tile-transition); }

.ds-screen i{
  display:block; border-radius:1px; background:var(--c);
  /* Parked off the screen on one of four sides. The overshoot in the curve is
     what makes them land rather than arrive. */
  transform: translate(var(--dx, 0), var(--dy, 0));
  transition: transform .55s cubic-bezier(.3, 1.3, .4, 1);
}
/* Colour = the eight section hues, in order. Direction alternates around the
   four sides so it reads as converging, not as a wipe. Offsets are in % of
   each tile's OWN size and are sized to clear the screen from any column. */
.ds-screen i:nth-child(1){ --c:var(--t1-lite); --dx:-460%; transition-delay:.00s; }
.ds-screen i:nth-child(2){ --c:var(--t2-lite); --dy:-280%; transition-delay:.04s; }
.ds-screen i:nth-child(3){ --c:var(--t3-lite); --dy:-280%; transition-delay:.08s; }
.ds-screen i:nth-child(4){ --c:var(--t4-lite); --dx: 460%; transition-delay:.12s; }
.ds-screen i:nth-child(5){ --c:var(--t5-lite); --dx:-460%; transition-delay:.06s; }
.ds-screen i:nth-child(6){ --c:var(--t6-lite); --dy: 280%; transition-delay:.10s; }
.ds-screen i:nth-child(7){ --c:var(--t7-lite); --dy: 280%; transition-delay:.14s; }
.ds-screen i:nth-child(8){ --c:var(--t8-lite); --dx: 460%; transition-delay:.18s; }

/* Was wrapped in @media (any-hover: hover). Unwrapped, and every :hover now
   also answers to .previewing — the class the tap transition sets on touch,
   where :hover never fires. One selector serves both inputs instead of the
   artwork being unreachable on a phone. */
/* The screen clears as the palette arrives, so the fill reads as the thing
     the tiles come to replace rather than a background they sit on. */
  .tile-2:is(:hover, .previewing) .ds-screen{ background: transparent; }
  .tile-2:is(:hover, .previewing) .ds-screen i{ transform:none; }

/* With motion reduced the tiles never travel, so park them assembled — and
   clear the fill that would otherwise hide them. */
@media (prefers-reduced-motion: reduce){
  .ds-screen{ background: transparent; }
  .ds-screen i{ transform:none; }
}

/* 3 · PROJECT COFFER ---------------------------------------------------
   The only tile that does not hand over to the house ink on hover. It hands
   over to COFFER'S OWN dark-green theme — surface, ink and rich lifted
   straight out of the app's `dark-green` token set — because the point of the
   moment is the project asserting its own identity, not the site's.

   At rest it is Coffer's real mark.png, recoloured to the tile's deep tier by
   an SVG filter. On hover it recedes and the shipped app icon lands in its
   place: bevel, gradient, gloss. The flat logo becoming the real object — and
   it only reads that way because both states are the SAME artwork. */
:root{
  --coffer-surface:#10201A; --coffer-ink:#E7F0E3; --coffer-rich:#2D6235;
  --coffer-mid:#8FA58C;     --coffer-wash:#22392A; --coffer-button:#9CB894;
}
/* Was wrapped in @media (any-hover: hover). Unwrapped, and every :hover now
   also answers to .previewing — the class the tap transition sets on touch,
   where :hover never fires. One selector serves both inputs instead of the
   artwork being unreachable on a phone. */
.tile-3:is(:hover, .previewing){
    background-color: var(--coffer-surface) !important;
    color: var(--coffer-ink) !important;
  }

.coffer-art{ position:absolute; inset:0; }
/* Carries Coffer's actual mark.png, recoloured to currentColor by an SVG
   filter (see the markup in nav.js). The rest glyph and the hover app icon are
   now the same artwork, which a hand-traced version could never be. */
.coffer-mark{
  position:absolute; inset:0; width:100%; height:100%;
  transition: opacity .4s var(--nav-easing), transform .55s cubic-bezier(.6,0,0,1);
}
.coffer-app{
  position:absolute; inset:0; width:100%; height:100%; object-fit:contain;
  opacity:0; transform: scale(.74) rotate(-8deg);
  filter: drop-shadow(0 10px 26px rgba(0,0,0,.45));
  transition: opacity .45s var(--nav-easing), transform .7s cubic-bezier(.3,1.25,.4,1);
  z-index:2;                       /* always in front of the cash fan */
}

/* --- the cash fan ------------------------------------------------------
   Four Lucide money icons stacked dead centre, behind the app icon, and dealt
   outward on hover like a hand of cards. They start at scale(.4) with no
   offset, so they genuinely emerge FROM behind the icon rather than fading in
   where they land.

   Deliberately quiet: --coffer-rich on Coffer's own dark-green surface is a
   green-on-green accent you notice second, after the icon. Anything brighter
   competed with the thing it is meant to be decorating. */
.coffer-cash{ position:absolute; inset:0; z-index:1; pointer-events:none; }
.coffer-cash svg{
  position:absolute; left:50%; top:50%;
  width:27%; height:27%; margin:-13.5% 0 0 -13.5%;
  /* `rich` alone measures 2.33:1 on Coffer's surface — genuinely invisible
     rather than subtle. Pulled a little toward `mid` to land near 3:1: present
     if you look, never competing with the icon it sits behind. */
  fill:none; stroke: color-mix(in srgb, var(--coffer-rich) 72%, var(--coffer-mid));
  stroke-width:2;
  stroke-linecap:round; stroke-linejoin:round;
  opacity:0; transform: translate(0,0) scale(.4) rotate(0deg);
  /* Rotate about the icon's OWN centre. Without this the rotation happens
     about a corner, which shifts the whole box — two icons carrying the same
     -70cqw offset landed 13px apart, and the maths for clearing the app icon
     stopped holding. Same trap as the A-dot's transform-box. */
  transform-origin: 50% 50%;
  transition: opacity .35s var(--nav-easing) calc(var(--d) * var(--stagger, 1)),
              transform .6s cubic-bezier(.3,1.25,.4,1) calc(var(--d) * var(--stagger, 1));
}
/* Keeps the stroke a hairline at any tile size — these scale with the tile. */
.coffer-cash svg *{ vector-effect:non-scaling-stroke; }

/* WHERE EACH ICON LANDS.

   Offsets are in cqw/cqh so they measure the art SQUARE. To sit fully clear of
   the app icon rather than peeking from behind it, an icon has to travel more
   than half the square plus its own half-width — and because each one is
   rotated, its bounding box is bigger than its nominal size (64–74px against a
   nominal 59). That works out at ~67% of the square, so these sit at 72–76%.

   The arc opens UP AND LEFT: the square is pinned to the tile's bottom-right
   corner, so that is the only direction with room. */
.coffer-cash svg:nth-child(1){ --tx:-76cqw; --ty:-20cqh; --rot:-16deg; --d:60ms; }
.coffer-cash svg:nth-child(2){ --tx:-72cqw; --ty:-72cqh; --rot:-7deg;  --d:110ms; }
.coffer-cash svg:nth-child(3){ --tx:-20cqw; --ty:-76cqh; --rot:7deg;   --d:160ms; }
.coffer-cash svg:nth-child(4){ --tx:24cqw;  --ty:-76cqh; --rot:16deg;  --d:210ms; }

/* SHORT TILES re-aim the arc sideways.

   On the 2x4 layout the tile goes wide and short, and there is then no way to
   clear both the app icon AND the title going upward — the two demands
   overlap. But a wide tile has room to the LEFT that a tall one does not, so
   the fan rotates to a vertical arc opening left instead. Same idea, aimed at
   where the space actually is. */
/* On the extreme letterbox tile the title alone fills the height, and there is
   no arc that clears both it and the app icon. The icon carries the tile there
   on its own — same call as Box Buddy's stickers. */
@container tile (max-height: 120px){
  .coffer-cash{ display:none; }
}
@container tile (max-height: 300px){
  .coffer-cash svg:nth-child(1){ --tx:-78cqw; --ty:40cqh;  --rot:-14deg; }
  .coffer-cash svg:nth-child(2){ --tx:-88cqw; --ty:12cqh;  --rot:-5deg; }
  .coffer-cash svg:nth-child(3){ --tx:-88cqw; --ty:-18cqh; --rot:5deg; }
  .coffer-cash svg:nth-child(4){ --tx:-80cqw; --ty:-46cqh; --rot:14deg; }
}
/* Was wrapped in @media (any-hover: hover). Unwrapped, and every :hover now
   also answers to .previewing — the class the tap transition sets on touch,
   where :hover never fires. One selector serves both inputs instead of the
   artwork being unreachable on a phone. */
.tile-3:is(:hover, .previewing) .coffer-mark{ opacity:0; transform: scale(.86); }
  .tile-3:is(:hover, .previewing) .coffer-app{ opacity:1; transform: scale(1) rotate(0deg); }
  .tile-3:is(:hover, .previewing) .coffer-cash svg{
    opacity:1;
    transform: translate(var(--tx), var(--ty)) scale(1) rotate(var(--rot));
  }

  /* Coffer's own display face, on hover only — the tile is Impact at rest like
     every other one, and hands over to the product's voice at the same moment
     it hands over to the product's colour. Plus Jakarta Sans 800, sentence
     case at -0.01em, which is how Coffer actually sets display type; the site's
     uppercase would be the site talking, not Coffer. */
  .tile-3:is(:hover, .previewing) .tile-title{
    font-family:'Plus Jakarta Sans', var(--font-body), sans-serif;
    font-weight:800; text-transform:none; letter-spacing:-.01em;
    line-height:1.1;
  }
  .tile-3:is(:hover, .previewing) .tile-sub{
    font-family:'Plus Jakarta Sans', var(--font-body), sans-serif;
    font-weight:800; text-transform:none; letter-spacing:0;
  }

@media (prefers-reduced-motion: reduce){
  .coffer-cash svg{ transition:none; }
}
@media (prefers-reduced-motion: reduce){
  .coffer-mark, .coffer-app{ transition:none; }
}

/* The subtitle under a tile title. Only Project Coffer carries one so far —
   it is a role line, not a description, so it stays short and quiet. */
.tile-head{ position:relative; z-index:5; }
.tile-sub{
  font-family: var(--font-body); font-weight:500;
  font-size: clamp(10px, .78vw, 13px); letter-spacing:.04em;
  text-transform:uppercase; opacity:.72; margin-top:.7em;
}
@media (max-width:1024px){ .tile-sub{ font-size:10px; margin-top:.5em; } }
@media (max-height:399px), (max-width:249px){ .tile-sub{ display:none; } }

/* 5 · Iconography */
.glyph{
  position:absolute; inset:0; width:100%; height:100%;
  fill:currentColor; stroke:transparent; stroke-width:2px;
  transition: fill var(--tile-transition), stroke var(--tile-transition), transform var(--tile-transition);
}
.glyph path, .glyph circle{ vector-effect:non-scaling-stroke; }
.shackle{ transition: transform .45s cubic-bezier(.3,1.4,.5,1); transform-origin:center; }
/* Was wrapped in @media (any-hover: hover). Unwrapped, and every :hover now
   also answers to .previewing — the class the tap transition sets on touch,
   where :hover never fires. One selector serves both inputs instead of the
   artwork being unreachable on a phone. */
.tile-5:is(:hover, .previewing) .glyph{ fill:transparent; stroke:#fff; }
  .tile-5:is(:hover, .previewing) .shackle{ transform:translateY(-13%); }


/* --- 1 · About Angie: the dot rolls in and finishes the mark ---------
   It waits off to the left, then rolls across and drops into its slot,
   spinning twice on the way. `transform-box: fill-box` puts the origin at the
   dot's own centre rather than the SVG viewBox corner — without it the dot
   orbits the whole letterform instead of spinning about itself. */
.a-glyph .a-roll{
  fill: var(--orange);
  transform-box: fill-box;
  transform-origin: 50% 50%;
  transform: translateX(-190px) rotate(0deg);
  transition: transform .85s cubic-bezier(.3,1.15,.35,1);
}
/* Was wrapped in @media (any-hover: hover). Unwrapped, and every :hover now
   also answers to .previewing — the class the tap transition sets on touch,
   where :hover never fires. One selector serves both inputs instead of the
   artwork being unreachable on a phone. */
.tile-1:is(:hover, .previewing) .a-glyph .a-roll{ transform: translateX(0) rotate(720deg); }
  /* the A hollows out so the dot is the only solid thing left */
  .tile-1:is(:hover, .previewing) .a-glyph{ fill:transparent; stroke:#fff; }

@media (prefers-reduced-motion: reduce){
  .a-glyph .a-roll{ transform:none; }
}

/* 4 · MORE PROJECTS -----------------------------------------------------
   The tile whose hover has a JOB: say what is behind it before you click.
   Everything else on this site is expressive; this one is an index.

   Tile 4 takes the house black on hover (the same rule About and the design
   system use), so there is no background override anywhere in this block —
   that is deliberate, not an omission. */

.mp-term{
  position:absolute; left:6%; right:6%; top:23%;
  z-index:1; pointer-events:none;
  display:flex; flex-flow:column; gap:.42em;
  /* System monospace. No webfont to load, and `ch` is only a reliable unit
     for the typing trick if every glyph is the same width. */
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  /* clamp, not min: a phone tile is only ~139px of content wide and 4.1cqw
     lands at 5.7px there, which is unreadable — and unreadable defeats the
     entire point of this tile. The floor holds it at 8px. */
  font-size: clamp(8px, 4.1cqw, 13px);
}
.mp-line{
  display:block; overflow:hidden; white-space:nowrap;
  font-style:normal; color:#fff;
  width:0; opacity:0;
  /* The typing itself. `--n` is the character count, written per line in
     nav.js, so `--n * 1ch` is the exact final width and steps(--n) advances
     one character at a time. Get either wrong and it reads as a wipe. */
  transition: width .5s steps(var(--n)) calc(var(--d) * var(--stagger, 1)),
              opacity 0s linear calc(var(--d) * var(--stagger, 1));
}
.mp-head{
  color: var(--orange); font-size:.82em; letter-spacing:.14em; --ls:.14em;
  margin-bottom:.3em;
}
/* LETTER-SPACING HAS TO BE IN THE WIDTH. `ch` is one glyph advance, but
   letter-spacing adds to every advance on top of that — so `--n * 1ch` came
   up short and every line lost its last character or two. The header, at
   .14em of tracking, lost three. The trailing .6ch is slack so a rounding
   error can never clip the final glyph. */
.tile-4:is(:hover, .previewing) .mp-line{
  width: calc(var(--n) * (1ch + var(--ls, 0em)) + .6ch);
  opacity:1;
}

/* The caret only shows up once the last name has finished typing, then
   blinks. One caret at the end beats a cursor per line, which would leave
   seven blocks sitting on screen. */
.mp-caret{
  display:block; width:.62em; height:1.05em;
  background: var(--orange); opacity:0;
}
.tile-4:is(:hover, .previewing) .mp-caret{
  animation: mp-blink 1.06s step-end infinite;
  animation-delay: calc(var(--d) * var(--stagger, 1));
}
@keyframes mp-blink{ 0%,49%{ opacity:1 } 50%,100%{ opacity:0 } }

/* --- the glyph: a stack of sheets, a list on the front one --------- */
.mp-art{ position:absolute; inset:0; }
.mp-stack{
  position:absolute; inset:0; width:100%; height:100%;
  overflow:visible;
  transition: opacity var(--tile-transition);
}
.mp-card{
  fill:none; stroke:currentColor; stroke-width:4.5;
  transition: transform .5s cubic-bezier(.2,.8,.3,1.3);
}
.mp-front{ fill:currentColor; }
/* On hover the stack fans apart and drops back to a watermark, so the list
   typing over it stays the thing you actually read. */
.tile-4:is(:hover, .previewing) .mp-stack{ opacity:.16; }
.tile-4:is(:hover, .previewing) .mp-card.c3{ transform: translate(9px,-9px); }
.tile-4:is(:hover, .previewing) .mp-card.c2{ transform: translate(4px,-4px); }

/* On a phone tile the qualifiers go and only the names type out. Holding
   8px legible matters more than the studio credit, and the names alone are
   what tell you what is behind the tile. `--nn` is the name-only character
   count, carried on each line for exactly this. */
@container tile (max-height: 200px){
  .mp-q{ display:none; }
  /* The title wraps to two lines on a tile this narrow, so the head band eats
     86px of 211 — the list has to start below it, not at the 23% that works
     on a wide tile. The header line goes too: it is the least informative
     thing here and the seven names need the room. */
  .mp-head{ display:none; }
  .mp-term{ top:44%; gap:.3em; }
  .tile-4:is(:hover, .previewing) .mp-line{
    width: calc(var(--nn) * (1ch + var(--ls, 0em)) + .6ch);
  }
}

@media (prefers-reduced-motion: reduce){
  .mp-line, .mp-card, .mp-stack{ transition:none; }
  .mp-caret{ animation:none; }
}


/* 6 · PROJECT BOX BUDDY ------------------------------------------------
   The second tile that refuses the house ink on hover. Coffer hands over to
   its own dark green; Box Buddy hands over to the whole GAME — because Box
   Buddy is a children's product and a monochrome treatment would be a lie
   about what it is.

   Every value below is lifted from boxbuddy-app/index.html, not sampled off a
   screenshot: the palette, the pillar positions, the mascot path, the bob
   timing and the pop easing. */
:root{
  --bb-green:#7AC142;  --bb-green-deep:#3D6B1C;
  --bb-orange:#F58220; --bb-blue:#29ABE2;   --bb-yellow:#FFD200;
  --bb-purple:#9B5DE5; --bb-purple-dark:#6C3BB0;
  --bb-red:#E63946;    --bb-magenta:#EC008C;
  --bb-ink:#2B2B2B;    --bb-surface:#FFFFFF; --bb-blush:#FFC2C2;
  /* The app's own overshoot curve. Kids' UI: everything arrives past its mark
     and settles back. */
  --bb-pop: cubic-bezier(.34,1.56,.64,1);
  --bb-snap: cubic-bezier(.16,1,.3,1);
}

.bb-world{ position:absolute; inset:0; z-index:0; pointer-events:none; overflow:hidden; }

/* --- LAYER 1 · the field ----------------------------------------------
   The case study's hero background: a green ground with big overlapping
   circles bleeding off every edge. Hues, order and rough placement are its
   `.orb` set. They stay fully opaque — these are flat colour blocks, not
   tints; the whole system is flat colour with hard edges. */
.bb-orbs i{
  position:absolute; border-radius:50%;
  left: var(--l); top: var(--t, auto); bottom: var(--b, auto);
  width: var(--w); aspect-ratio:1; background: var(--c);
  transform: scale(.4); opacity:0;
  transition: transform .8s var(--bb-snap) calc(var(--d) * var(--stagger, 1)), opacity .5s ease calc(var(--d) * var(--stagger, 1));
}

/* --- LAYER 2 · the iso blocks -----------------------------------------
   The PBS-Kids-GO! extruded block, ported from the case study's own `.iso`.

   Three flat faces, no gradient and no blur anywhere: the front face is the
   hue, the top face is it mixed 76% toward white, the side face 70% toward
   black. Both faces are DERIVED rather than hand-picked, which is what keeps
   the light source unconditional across every hue.

   ::before is the top face — skewX(-45deg) from its bottom-left pushes its far
   edge right by exactly --iso-d. ::after is the side face — skewY(-45deg) from
   its top-left lifts its far edge by the same amount. Both land on the
   identical top-right point, and that shared corner is what makes the two
   flaps read as one solid block rather than two bits of paper taped on.

   Blocks stay axis-aligned. In the reference art the isometric scene is rigid;
   only the stickers pinned onto it are crooked. */
.bb-iso{
  --iso-top:  color-mix(in srgb, var(--f) 76%, #FFFFFF);
  --iso-side: color-mix(in srgb, var(--f) 70%, #000000);
  --iso-d: 2.2cqmin;
  position:absolute; bottom:0;
  left: var(--l); width: var(--w); height: var(--h);
  background: var(--f);
  border-radius:4px;
  /* Rises out of the floor, overshooting once before it settles. */
  transform: translateY(115%);
  transition: transform .66s var(--bb-pop) calc(var(--d) * var(--stagger, 1));
}
.bb-iso::before, .bb-iso::after{ content:''; position:absolute; }
.bb-iso::before{                       /* top face — lit */
  left:0; right:0; bottom:100%; height:var(--iso-d);
  background: var(--iso-top);
  transform: skewX(-45deg); transform-origin: bottom left;
}
.bb-iso::after{                        /* side face — shadowed */
  left:100%; top:0; bottom:0; width:var(--iso-d);
  background: var(--iso-side);
  transform: skewY(-45deg); transform-origin: top left;
}

/* --- the stickers -----------------------------------------------------
   Emoji as DIE-CUT STICKERS: a ring of hard, zero-blur white drop-shadows in
   eight directions. Box Buddy's system is explicit that shadows are hard and
   never blurred, and that emoji read as stickers rather than as text — and
   -webkit-text-stroke cannot do it, because emoji are bitmap glyphs with no
   outline to stroke. drop-shadow works on the rendered pixels instead. */
.bb-stickers b{
  position:absolute; left: var(--l); top: var(--t);
  /* Clamped, not raw cqmin. Straight 11cqmin fell under 12px on the short
     portrait tiles, which is what made the stickers vanish there. */
  font-size: clamp(17px, 10cqmin, 44px); line-height:1;
  opacity:0; transform: translate(-50%,-50%) scale(.4) rotate(0deg);
  transition: opacity .4s ease calc(var(--d) * var(--stagger, 1)), transform .66s var(--bb-pop) calc(var(--d) * var(--stagger, 1));
  filter:
    drop-shadow( 2px  0 0 #fff) drop-shadow(-2px  0 0 #fff)
    drop-shadow( 0  2px 0 #fff) drop-shadow( 0 -2px 0 #fff)
    drop-shadow( 1.4px  1.4px 0 #fff) drop-shadow(-1.4px  1.4px 0 #fff)
    drop-shadow( 1.4px -1.4px 0 #fff) drop-shadow(-1.4px -1.4px 0 #fff)
    drop-shadow(0 3px 0 rgba(0,0,0,.22));
}

/* Four stickers sit in the lower-left band and are shown at EVERY tile size,
   portrait included. Two more only appear once the tile is tall enough to have
   somewhere safe to put them — the title block and the mascot both take a much
   bigger share of a short tile, which is what squeezed the earlier six-sticker
   set off the portrait forms entirely.

   Keyed to the TILE's own box, not the viewport: `.tile` is a size container,
   and its height is the thing that actually decides this. */
.bb-extra{ display:none; }
@container (min-height: 300px){
  .bb-extra{ display:block; }
}
/* Below this the title and the mascot between them leave only a sliver of
   clear band, and a 17px sticker in it is noise rather than decoration. The
   field, the blocks and the mascot still carry the tile on their own. */
@container (max-height: 190px){
  .bb-stickers{ display:none; }
}

/* --- the mascot -------------------------------------------------------
   At rest it is a filled silhouette in the tile's deep tier, like every other
   glyph on the site. On hover it becomes the character: white body, blush,
   and the app's real face-happy.png. */
.bb-art{ position:absolute; inset:0; }
.bb-cloud{
  position:absolute; inset:0; width:100%; height:100%;
  overflow:visible;
  transition: transform .5s var(--bb-pop);
}
.bb-body{ fill: currentColor; transition: fill var(--tile-transition); }
.bb-blush, .bb-face{ opacity:0; transition: opacity .3s ease .18s; }

/* Was wrapped in @media (any-hover: hover). Unwrapped, and every :hover now
   also answers to .previewing — the class the tap transition sets on touch,
   where :hover never fires. One selector serves both inputs instead of the
   artwork being unreachable on a phone. */
/* Green is the ground, not grey. --ink is also the only type colour that
     clears AA on the bright hues (5.9:1 on green, 4.7:1 on orange) — white is
     2.4:1 on green and would fail, which is why the case study puts dark type
     on bright blocks too. */
  .tile-6:is(:hover, .previewing){ background-color: var(--bb-green) !important; }

  /* The title takes Box Buddy's own voice on hover — the case study's h1:
     Baloo 2 800, white with yellow accent words, over the hard zero-blur drop
     shadow the whole system runs on. The subtitle takes the kicker's face,
     Nunito 900 uppercase.

     The dark RING under the offset is the part that matters. Her h1 can sit on
     a plain green field because the hero keeps its left side clear; this tile
     is a quarter the width with circles and blocks moving under the type, and
     white is only 2.2:1 on green while the yellow words would disappear into
     the yellow orb entirely. The ring is the app wordmark's own answer —
     there it is `paint-order: stroke fill` with a thick ink stroke — done with
     hard, zero-blur shadows so it stays inside the system's rules.

     It also means legibility no longer depends on where the orbs happen to
     sit, which at eight tile aspect ratios is not something worth policing. */
  .tile-6:is(:hover, .previewing) .tile-title{
    font-family:'Baloo 2', var(--font-body), sans-serif;
    font-weight:800; text-transform:none; letter-spacing:-.02em;
    line-height:1.06;
    color: var(--bb-surface);
    text-shadow:
       2px 0 0 var(--bb-ink), -2px 0 0 var(--bb-ink),
       0 2px 0 var(--bb-ink),  0 -2px 0 var(--bb-ink),
       1.5px 1.5px 0 var(--bb-ink), -1.5px 1.5px 0 var(--bb-ink),
       1.5px -1.5px 0 var(--bb-ink), -1.5px -1.5px 0 var(--bb-ink),
       0 6px 0 rgba(0,0,0,.3);
  }
  .tile-6:is(:hover, .previewing) .tile-title .bb-y{ color: var(--bb-yellow); }
  .tile-6:is(:hover, .previewing) .tile-sub{
    font-family:'Nunito', var(--font-body), sans-serif;
    font-weight:900; letter-spacing:.1em;
    color: var(--bb-surface); opacity:1;
    text-shadow:
       1px 0 0 var(--bb-ink), -1px 0 0 var(--bb-ink),
       0 1px 0 var(--bb-ink),  0 -1px 0 var(--bb-ink),
       0 3px 0 rgba(0,0,0,.3);
  }
  .tile-6:is(:hover, .previewing) .bb-orbs i{ transform: scale(1); opacity:1; }
  .tile-6:is(:hover, .previewing) .bb-iso{ transform: translateY(0); }
  .tile-6:is(:hover, .previewing) .bb-stickers b{
    opacity:1; transform: translate(-50%,-50%) scale(1) rotate(var(--r));
  }
  .tile-6:is(:hover, .previewing) .bb-body{ fill: var(--bb-surface); }
  .tile-6:is(:hover, .previewing) .bb-blush{ opacity:1; fill: var(--bb-blush); }
  .tile-6:is(:hover, .previewing) .bb-face{ opacity:1; }
  /* The app's own bob: 3.6s, -10px, rotating between -1 and 1 degrees.
     The shadow is the app's too, and it is HARD — zero blur. Box Buddy's
     system is explicit that every elevated surface uses a hard offset shadow
     and never a blurred one; a soft shadow here would be the one thing on this
     tile that isn't the real system. */
  .tile-6:is(:hover, .previewing) .bb-cloud{
    animation: bb-bob 3.6s ease-in-out infinite;
    filter: drop-shadow(0 7px 0 rgba(0,0,0,.16));
  }

@keyframes bb-bob{
  0%,100%{ transform: translateY(0)    rotate(-1deg); }
  50%    { transform: translateY(-5%)  rotate(1deg); }
}
@media (prefers-reduced-motion: reduce){
  .bb-orbs i, .bb-iso, .bb-stickers b, .bb-cloud{ transition:none; }
  .tile-6:hover .bb-cloud{ animation:none; }
}

/* 7 · AMAZON --------------------------------------------------------------
   The third tile that refuses the house ink on hover. Coffer hands over to its
   own dark green, Box Buddy to the whole game; Amazon hands over to the RETAIL
   identity — Squid Ink, the warm grey, the orange — which is precisely what her
   case study is about: amazon.com's consumer brand applied to an internal tool.

   Every value is from her own `amazon-retail-design-system.md`, and the ones
   she flagged [VERIFIED] against Amazon's Logo Usage Guidelines are the ones
   doing the heavy lifting. Ember is deliberately not used: her doc notes it is
   not licensed for third-party work, and picks Archivo as the substitute. */
:root{
  --amz-ink:#232F3E;      /* Squid Ink  [VERIFIED]  */
  --amz-ink-2:#37475A;    /* the header's second bar */
  --amz-orange:#FF9900;   /* Amazon Orange [VERIFIED] */
  --amz-cart:#FFD814;     /* Add to Cart  */
  --amz-buy:#FFA41C;      /* Buy Now — the committing action */
  --amz-page:#EAEDED;     /* the warm grey, NOT #F5F5F5; her doc calls this out
                             as a meaningful difference from most SaaS */
  --amz-border:#D5D9D9;
  --amz-white:#FFFFFF;
}

.amz-world{ position:absolute; inset:0; z-index:0; pointer-events:none; overflow:hidden; }

/* --- the miniature page ------------------------------------------------
   Left half only. The title owns the top of the tile and the art square owns
   the bottom-right; a full-width header bar ran through both, hiding the title
   and putting the white wordmark on a white card where it vanished. Keeping
   the screen to one side fixes both and reads better — a little amazon.com
   sitting next to the mark. */
.amz-screen{
  position:absolute; left:5%; top:31%; width:50%; bottom:9%;
  background: var(--amz-page); border-radius:6px; overflow:hidden;
  display:flex; flex-flow:column;
  transform: translateX(-118%);
  transition: transform .58s var(--nav-easing-swoop);
}
.amz-bar{
  flex:none; height:22%; min-height:14px;
  background: var(--amz-ink-2);
  display:flex; align-items:center; gap:4%; padding:0 5%;
}
.amz-field{ flex:1; height:46%; background: var(--amz-white); border-radius:3px 0 0 3px; }
.amz-go{ width:14%; height:46%; background: var(--amz-orange); border-radius:0 3px 3px 0; }

.amz-body{ flex:1; padding:7% 6%; display:flex; flex-flow:column; gap:7%; }

.amz-cards{ flex:1; display:flex; gap:5%; }
.amz-cards i{
  flex:1;
  background: var(--amz-white); border:1px solid var(--amz-border); border-radius:8px;
  /* No shadow. Her doc: cards sit on the warm grey for contrast "without
     needing shadow" — adding one would be the tell that this is a pastiche. */
  transform: translateY(16%); opacity:0;
  transition: transform .46s var(--nav-easing-swoop) calc(var(--d) * var(--stagger, 1)),
              opacity .32s ease calc(var(--d) * var(--stagger, 1));
}

.amz-stars{ flex:none; display:flex; gap:2.5%; width:46%; }
.amz-stars i{
  flex:1; aspect-ratio:1; background: var(--amz-orange);
  clip-path: polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%);
  transform: scale(0);
  transition: transform .32s cubic-bezier(.34,1.56,.64,1) calc(var(--d) * var(--stagger, 1));
}

.amz-btns{ flex:none; display:flex; gap:4%; width:72%; }
.amz-btns i{
  flex:1; height:0; padding-bottom:11%;
  border-radius:8px;                    /* her doc: gently rounded, not pill */
  transform: scale(.72); opacity:0;
  transition: transform .38s cubic-bezier(.34,1.56,.64,1) calc(var(--d) * var(--stagger, 1)),
              opacity .28s ease calc(var(--d) * var(--stagger, 1));
}
/* 1px border in a darker shade of the fill, exactly as the doc specifies. */
.amz-btns i.cart{ background: var(--amz-cart); border:1px solid #FCD200; }
.amz-btns i.buy { background: var(--amz-buy);  border:1px solid #FA8900; }

/* --- the logo: symbol at rest, wordmark completes it -------------------
   Every number here was MEASURED off the PNG's own pixels. Smile box
   x129-600 y167-273, so its centre is (364.5, 220) against a viewBox centre
   of (448.5, 140) — hence +84 / -80 to centre it. 1.75 is the largest scale
   that still leaves margin inside 897x280.

   transform-box is VIEW-BOX, not fill-box: these are <g> elements and a
   group's bounding box IGNORES its clip-path, so fill-box would have measured
   the whole 897x280 image and put the origin in the wrong place. */
.amz-art{ position:absolute; inset:0; }
.amz-logo{
  position:absolute; inset:0; width:100%; height:100%;
}
.amz-smile{
  transform: translate(84px, -80px) scale(1.75);
  transform-box: view-box; transform-origin: 364.5px 220px;
  transition: transform .66s cubic-bezier(.22,1,.32,1);
}
.amz-s-real{ opacity:0; transition: opacity .4s var(--nav-easing) .2s; }
.amz-s-ink { opacity:1; transition: opacity .4s var(--nav-easing) .2s; }

/* Still wiped left to right — the mark being signed, a to z, which is the
   brand's own story about itself. What it reveals is now the missing half of
   the logo rather than a recolour, so the wipe finishes something. */
.amz-word{
  clip-path: inset(0 100% 0 0);
  transition: clip-path .72s cubic-bezier(.6,0,0,1) .3s;
}

.tile-7:is(:hover, .previewing){
  background-color: var(--amz-ink) !important;
  color: var(--amz-white) !important;
}
.tile-7:is(:hover, .previewing) .amz-screen{ transform: translateX(0); }
.tile-7:is(:hover, .previewing) .amz-cards i{ transform: translateY(0); opacity:1; }
.tile-7:is(:hover, .previewing) .amz-stars i{ transform: scale(1); }
.tile-7:is(:hover, .previewing) .amz-btns i{ transform: scale(1); opacity:1; }
.tile-7:is(:hover, .previewing) .amz-smile{ transform:none; }
.tile-7:is(:hover, .previewing) .amz-s-ink{ opacity:0; }
.tile-7:is(:hover, .previewing) .amz-s-real{ opacity:1; }
.tile-7:is(:hover, .previewing) .amz-word{ clip-path: inset(0 0 0 0); }

/* Archivo is the face her doc chose as the Ember substitute, so the title
   hands over to it at the same moment the colour does. */
.tile-7:is(:hover, .previewing) .tile-title{
  font-family:'Archivo', var(--font-body), sans-serif;
  font-weight:800; text-transform:none; letter-spacing:-.02em;
}
.tile-7:is(:hover, .previewing) .tile-sub{
  font-family:'Archivo', var(--font-body), sans-serif;
  font-weight:700; text-transform:none; letter-spacing:0; opacity:1;
}

/* The art square sits bottom-right; the UI beneath it must not run under the
   logo. Everything above is placed on the LEFT half for that reason. */
/* On the short tile forms the screen has no room to be a screen. The Squid Ink
   ground and the logo carry it there, same call as the other two tiles. */
@container tile (max-height: 190px){
  .amz-screen{ display:none; }
}

@media (prefers-reduced-motion: reduce){
  .amz-screen, .amz-cards i, .amz-stars i, .amz-btns i,
  .amz-smile, .amz-s-ink, .amz-s-real, .amz-word{ transition:none; }
}

/* 8 · MAYO CLINIC ---------------------------------------------------------
   The fourth tile that hands over to a real brand. Where Amazon's assembles a
   busy retail page and Box Buddy's throws a party, this one is deliberately
   QUIET — because that is what Mayo's system actually is. Her doc: 2-4px
   radius, shadows "very subtle, low-opacity", separation carried by borders
   and whitespace rather than depth, a "flat, clinical, trustworthy tone".

   So there is no overshoot anywhere in here. Every curve is a clean
   decelerate. A pop curve on a hospital would be saying the wrong thing. */
:root{
  --mc-cobalt:#003DA5;    /* primary brand blue  [VERIFIED] */
  --mc-midnight:#012D61;  /* footer, hover states */
  --mc-page:#F4F4F4;      /* the live theme-color value */
  --mc-white:#FFFFFF;
  --mc-ink:#1A1A1A;
  --mc-border:#D9D9D9;
  --mc-info-bg:#EEF6FD;   /* the info callout pair */
  --mc-info-br:#C8E4F8;
  /* page / ink / border / info are currently UNREFERENCED — the record-card
     hover they were written for is gone. Kept because they are the documented
     system, not leftovers, and the replacement animation is still being
     chosen. Delete them only if it lands without needing them. */
  --mc-ease: cubic-bezier(.16,1,.3,1);   /* no overshoot. that is the point. */
}

/* ==========================================================================
   XBOX / TRIVIA FOR YOU -- tile 5

   Every token below is a real production value from Angela's own handoff doc
   ("Design System - Design | Trivia" in Figma), which is unusually exact and
   worth following to the letter. Its three headline rules:

     4px radius EVERYWHERE and nothing is a pill. If it looks oval, it's wrong.
     Discrete cards get a white-to-colour VERTICAL GRADIENT, never a flat fill.
     Shadows are hard offsets with ZERO BLUR, on a 2px #1C2B38 border.

   Emphasis (the double shadow: category-darkest inside, plain black behind)
   is reserved by her doc for the logo, the Play button and the status badge --
   about five elements. It is not used on the answer cards here for that
   reason; the correct answer earns its emphasis through her documented HOVER
   formula instead (lift 4px, every shadow layer grows by exactly 4px).
   ========================================================================== */
:root{
  --tfy-tech:#91D0FD;   --tfy-tech-d:#5AA4D8;   --tfy-tech-dd:#1D547C;
  --tfy-sci:#93E16D;    --tfy-sci-d:#69AF46;    --tfy-sci-dd:#3A6F20;
  --tfy-games:#78ABFC;
  --tfy-canvas:#F9FBFE; --tfy-mid:#BBD2EF;      --tfy-dark:#28475E;
  --tfy-ink:#1C2B38;    /* every border. dark navy, NOT pure black */
  --tfy-r:4px;          /* the one radius */
  --tfy-anim: cubic-bezier(.2,.8,.3,1.3);   /* her 150ms hover curve */
}

/* --- the category icons -----------------------------------------------
   Real Lucide icons, one per category in the live game's own chip row.
   They are BACKGROUND: thin, low-opacity, tilted, and behind everything. */
.tfy-world{ position:absolute; inset:0; z-index:0; pointer-events:none; overflow:hidden; }

.tfy-ico{
  position:absolute; aspect-ratio:1; height:auto;
  fill:none; stroke: var(--tfy-ink); stroke-width:1.9;
  stroke-linecap:round; stroke-linejoin:round;
  opacity:0; transform: rotate(var(--rot)) scale(.8);
  transition: opacity .5s ease, transform .6s cubic-bezier(.2,.8,.3,1.3);
}
.tile-5:is(:hover, .previewing) .tfy-ico{
  opacity:.13; transform: rotate(var(--rot)) scale(1);
}
/* stagger them off their own position so they do not all bloom at once */
.tfy-ico:nth-child(2n)  { transition-delay: 60ms; }
.tfy-ico:nth-child(3n)  { transition-delay: 130ms; }
.tfy-ico:nth-child(5n)  { transition-delay: 190ms; }

/* --- the quiz card and the character ---------------------------------- */
.tfy-quiz, .tfy-char{
  position:absolute; display:block; height:auto;
  --tilt:0deg;
  opacity:0; transform: translateY(16px) scale(.94) rotate(var(--tilt));
  transition: opacity .38s ease calc(var(--d) * var(--stagger, 1)),
              transform .55s cubic-bezier(.2,.8,.3,1.3) calc(var(--d) * var(--stagger, 1));
}
/* MEASURED, not guessed. The title band is a fixed 91px at EVERY tile size,
   which is 22% of the desktop tile but 43% of a phone one — that, not the
   width, is what makes this composition hard.

   Desktop tile 364x404: head ends at y91 and the mark occupies y251-365, so
   the quiz has a 160px band to live in. 58% wide is 211px => 142px tall,
   sitting y97-239 and clearing the mark by 12px. It used to be 88%, which
   ran straight under the logo. */
/* Tilted and floating — a sticker dropped on the tile, not a screenshot
   pasted into a slot. Nudged toward centre to use the empty right-hand space,
   and a few px bigger now that the size is settled.

   The tilt is NEGATIVE on purpose. Rotating a wide card swings one bottom
   corner below where the flat edge sat, and at -2.5deg that is the bottom
   LEFT, which lives at x54 — nowhere near the mark. A positive tilt drops the
   bottom RIGHT instead, straight back into the logo. */
.tfy-quiz{ left:13%; top:23%; width:59%; --tilt:-2.5deg; }
/* the popcorn man stands beside it, overlapping the corner so he reads as
   being IN the scene rather than parked next to it */
.tfy-char{ left:4%; top:60%; width:26%; }

/* THRESHOLDS ARE CONTENT-BOX HEIGHTS, NOT TILE HEIGHTS. `.tile` carries
   23px of padding top and bottom and container queries measure the CONTENT
   box, so a 404px tile queries as 358, a 290px tile as 244 and a 211px tile
   as 165 — 46px less than you think. Writing these against the outer height
   fired the phone rule at tablet size and blanked the quiz. */

/* --- the tile turns LANDSCAPE below 1024 -------------------------------
   Tile 5 goes from 364x404 portrait to roughly 440x290 wide-and-short, and
   the mark moves with it to x287-401 y137-251. The quiz was overflowing the
   bottom edge by 43px here — genuinely cut off, exactly as Angela reported.

   So it stops sitting ABOVE the mark and sits BESIDE it instead: 57% wide
   ends at x264 against a mark starting at x287, and 169px tall from y96 fits
   inside 290 with room to spare. */
@container tile (max-height: 300px){
  /* top:35%, not 30%. The tilt LOWERS the top-left corner by ~5px, and the
     top-left is exactly where "UX/UI DESIGNER" sits — at 30% the card cut
     through it. Measure the rotated corner, not the element's box: the
     subtitle element is full-width so its rect tells you nothing about where
     the glyphs actually end. Clears the text by 4px, same as desktop. */
  .tfy-quiz{ left:4%; top:35%; width:52%; --tilt:-2.5deg; }
  .tfy-char{ left:3%; top:58%; width:15%; }
}

/* --- phone -------------------------------------------------------------
   185x211, and the fixed 91px title band takes 43% of it. Whatever is left
   cannot hold a legible quiz — at the widest that clears the mark it would be
   92px across, and the question text becomes mush. So the quiz goes and the
   character and the category icons carry the tile.

   The whole composition used to be hidden below 240px. It is not any more:
   the point of the tap transition is that touch users finally SEE the
   artwork, and a colour flood is not artwork. */
@container tile (max-height: 200px){
  .tfy-quiz{ display:none; }
  .tfy-char{ left:5%; top:44%; width:26%; }
}

.tile-5:is(:hover, .previewing) .tfy-quiz,
.tile-5:is(:hover, .previewing) .tfy-char{
  opacity:1; transform: translateY(0) scale(1) rotate(var(--tilt));
}
/* Both bob once they have landed — her doc's 3.5s hero float. The card runs
   slower than the character so the two never sync and start reading as one
   rigid object. */
.tile-5:is(:hover, .previewing) .tfy-char{
  animation: tfy-float 3.5s ease-in-out infinite;
  animation-delay: calc(var(--d) + 560ms);
}
.tile-5:is(:hover, .previewing) .tfy-quiz{
  animation: tfy-float 4.9s ease-in-out infinite;
  animation-delay: calc(var(--d) + 700ms);
}
/* The tilt is carried THROUGH the keyframes. An animation replaces the whole
   transform once it starts, so leaving rotate() out here would snap the card
   straight the instant it began to float. */
@keyframes tfy-float{
  0%,100%{ transform: translateY(0)    scale(1) rotate(var(--tilt)); }
  50%    { transform: translateY(-7px) scale(1) rotate(var(--tilt)); }
}

/* --- the mark ---------------------------------------------------------
   Two of Angela's PNGs, crossfaded. The black one is re-inked to the tile's
   tier for the default; the Trivia one is placed untouched for the hover.
   Nothing is reconstructed and no fill is overridden on her artwork. */
.tfy-art{ position:absolute; inset:0; display:flex; align-items:center; justify-content:center; }
.tfy-mark{
  position:absolute; width:78%; height:auto; display:block;
  transition: opacity .34s ease 120ms,
              transform .4s cubic-bezier(.2,.8,.3,1.3) 120ms;
}
.tfy-mark-flat{ aspect-ratio:1; opacity:1; }
.tfy-mark-tfy { opacity:0; transform: scale(.94); }

.tile-5:is(:hover, .previewing) .tfy-mark-flat{ opacity:0; }
.tile-5:is(:hover, .previewing) .tfy-mark-tfy { opacity:1; transform: scale(1); }

/* --- the type ---------------------------------------------------------
   Oswald 600 uppercase is the game's own heading style. Set in white with a
   heavy ink outline, which is what the Trivia For You wordmark itself does
   and why everything in this system reads as a sticker.

   Stroke is --tfy-ink #1C2B38, not pure black: her doc is explicit that
   #1C2B38 is the border colour for EVERYTHING, and true black beside it looks
   like a different system.

   `paint-order: stroke fill` puts the stroke UNDER the glyph so it grows
   outward. Without it -webkit-text-stroke centres on the outline and eats
   1px into the letterforms from inside, which closes up the counters. */
.tile-5:is(:hover, .previewing) .tile-title{
  font-family:'Oswald', var(--font-body), sans-serif;
  font-weight:600; letter-spacing:.01em;
  color:#fff;
  /* em, NOT px. The title is 41px on desktop but drops to 20px below 1024,
     where a fixed 2px stroke is proportionally twice as heavy and the letters
     close up. .05em holds the same weight at both: 2.05px and 1px. */
  -webkit-text-stroke: .05em var(--tfy-ink);
  paint-order: stroke fill;
  text-shadow: .07em .07em 0 var(--tfy-ink);  /* the hard zero-blur offset */
}
.tile-5:is(:hover, .previewing) .tile-sub{
  font-family:'Oswald', var(--font-body), sans-serif;
  font-weight:500; text-transform:uppercase; letter-spacing:.06em;
  color:#fff; opacity:1;
  /* Lighter again in proportion: the subtitle runs 12px down to 10px, and at
     that size even 1px of stroke turned it to mud. */
  -webkit-text-stroke: .045em var(--tfy-ink);
  paint-order: stroke fill;
  text-shadow: .1em .1em 0 var(--tfy-ink);
}

/* Below 1024 the subtitle drops to 10px, and at that size white-with-an-
   outline fights itself no matter how fine the stroke — the counters fill in
   and it reads as a smudge. It goes solid ink instead. The title still
   carries the personality; the subtitle only has to be readable. */
@media (max-width:1024px){
  .tile-5:is(:hover, .previewing) .tile-sub{
    color: var(--tfy-ink);
    -webkit-text-stroke: 0;
    text-shadow: none;
  }
}

/* The palette IS the background. Five soft washes drawn from the category
   colours. Her doc bars the white-to-colour card gradient from large
   backdrops, so this is a flat-tint wash and not that recipe. */
.tile-5:is(:hover, .previewing){
  background-color: var(--tfy-canvas) !important;
  background-image:
    radial-gradient(circle at 16% 20%, rgba(145,208,253,.9), transparent 44%),
    radial-gradient(circle at 86% 12%, rgba(253,170,213,.8), transparent 42%),
    radial-gradient(circle at 90% 72%, rgba(252,194,95,.8), transparent 46%),
    radial-gradient(circle at 10% 86%, rgba(147,225,109,.85), transparent 44%),
    radial-gradient(circle at 52% 48%, rgba(158,155,253,.45), transparent 56%) !important;
  color:#000 !important;
}

/* Only at genuinely tiny sizes does the whole composition go. Above this the
   character and the icons still read; it is the QUIZ that drops out first,
   handled in the phone block above. */
@container tile (max-height: 130px){
  .tfy-world{ display:none; }
}
@media (prefers-reduced-motion: reduce){
  .tfy-ico, .tfy-quiz, .tfy-char, .tfy-mark{ transition:none; animation:none; }
}

/* --- the care model ----------------------------------------------------
   A square, placed upper-left so it sits diagonally opposite the crest in the
   bottom-right art square — the same two-part composition as tile 7, turned
   for a portrait tile. 24%/52% keeps it clear of the title above and stops
   short of the art square below; verified against tile 8 at 364x504. */
.mc-world{
  position:absolute; left:6%; top:24%;
  /* Sized off BOTH axes, so it can never be taller than the box it sits in.
     `width:52%` alone was width-only, which is fine on the portrait desktop
     tile and overflows the moment the tile turns landscape — and below 1024
     tile 8 is always landscape. 24% from the top plus 56% of the height
     leaves 20% of clearance underneath at every size. */
  width: min(52cqw, 56cqh); aspect-ratio:1;
  z-index:0; pointer-events:none;
}
.mc-world svg{ width:100%; height:100%; overflow:visible; }

/* Rings EXPAND from the patient rather than fading in — the direction is the
   point, since the model radiates outward from one person. */
.mc-ring{
  fill:none; stroke: var(--mc-white); stroke-width:1.2;
  opacity:0; transform: scale(0);
  transform-box: view-box; transform-origin: 50px 50px;
  transition: opacity .3s var(--mc-ease) calc(var(--d) * var(--stagger, 1)),
              transform .62s var(--mc-ease) calc(var(--d) * var(--stagger, 1));
}
.mc-ring.r1{ stroke-width:1.5; }
.mc-ring.r2{ stroke-width:1.1; }
.mc-ring.r3{ stroke-width:.9; }

/* pathLength="1" normalises five lines of two different lengths onto one
   dash value, so they all draw at the same rate instead of the short ones
   finishing early. */
.mc-link{
  stroke: var(--mc-white); stroke-width:.7;
  stroke-dasharray:1; stroke-dashoffset:1; opacity:0;
  transition: stroke-dashoffset .34s var(--mc-ease) calc(var(--d) * var(--stagger, 1)),
              opacity .18s linear calc(var(--d) * var(--stagger, 1));
}
.mc-node{
  fill: var(--mc-white);
  opacity:0; transform: scale(0);
  transform-box: fill-box; transform-origin: 50% 50%;
  transition: opacity .26s var(--mc-ease) calc(var(--d) * var(--stagger, 1)),
              transform .42s var(--mc-ease) calc(var(--d) * var(--stagger, 1));
}
/* The patient. No delay — everything else is arranged around it, so it has to
   be there before anything can be arranged. */
.mc-core{
  fill: var(--mc-white);
  opacity:0; transform: scale(0);
  transform-box: fill-box; transform-origin: 50% 50%;
  transition: opacity .24s var(--mc-ease), transform .44s var(--mc-ease);
}

.tile-8:is(:hover, .previewing) .mc-ring{ opacity:1; transform:none; }
.tile-8:is(:hover, .previewing) .mc-ring.r1{ opacity:.9; }
.tile-8:is(:hover, .previewing) .mc-ring.r2{ opacity:.55; }
.tile-8:is(:hover, .previewing) .mc-ring.r3{ opacity:.32; }
.tile-8:is(:hover, .previewing) .mc-link{ stroke-dashoffset:0; opacity:.4; }
.tile-8:is(:hover, .previewing) .mc-node{ opacity:.9; transform:none; }
.tile-8:is(:hover, .previewing) .mc-core{ opacity:1; transform:none; }

/* --- the logo: crest at rest, wordmark completes it --------------------
   All four numbers below were MEASURED with getBBox on the real paths, not
   estimated:
       wordmark  x-0.07 y0     w45.47 h22.10   centre (22.66, 11.05)
       crest     x 4.50 y28.20 w36.60 h27.80   centre (22.80, 42.10)
       viewBox centre (22.70, 28.00)
   So -0.1 / -14.1 puts the crest dead centre, and 1.22 is the largest scale
   that still clears the 45.4-unit width.

   transform-box is VIEW-BOX so the origins above are read in viewBox
   coordinates directly. */
.mc-art{ position:absolute; inset:0; }
.mc-logo{
  position:absolute; inset:0; width:100%; height:100%;
  fill: currentColor;
  transition: fill var(--tile-transition);
}

/* The crest SETTLES: it is large and centred at rest, and shrinks down into
   its true place in the lockup as the name arrives above it. The two halves
   close on each other like a clasp. */
.mc-crest{
  transform: translate(-0.1px, -14.1px) scale(1.22);
  transform-box: view-box; transform-origin: 22.8px 42.1px;
  transition: transform .62s var(--mc-ease);
}

/* The name comes DOWN into place — no wipe. The Amazon tile next door wipes
   a to z, and these two are adjacent and share a hue, so they must not make
   the same gesture. */
.mc-word{
  opacity:0; transform: translateY(-4.5px);
  transform-box: view-box; transform-origin: 22.66px 11.05px;
  transition: opacity .42s var(--mc-ease) .18s,
              transform .52s var(--mc-ease) .18s;
}

.tile-8:is(:hover, .previewing){
  /* Cobalt, which is how Mayo actually presents itself — the case study's own
     hero is this exact treatment, a white lockup on cobalt. It also means
     hover, the tile expanding, and the page that arrives are ONE colour with
     no seam anywhere in the sequence. */
  background-color: var(--mc-cobalt) !important;
  color: var(--mc-white) !important;
}
.tile-8:is(:hover, .previewing) .mc-crest{ transform:none; }
.tile-8:is(:hover, .previewing) .mc-word{ opacity:1; transform:none; }

.tile-8:is(:hover, .previewing) .tile-title{
  font-family:'Source Sans 3', var(--font-body), sans-serif;
  font-weight:700; text-transform:none; letter-spacing:-.01em;
  color: var(--mc-white);
}
.tile-8:is(:hover, .previewing) .tile-sub{
  font-family:'Source Sans 3', var(--font-body), sans-serif;
  font-weight:600; text-transform:none; letter-spacing:0;
  color:#BBD3F0; opacity:1;
}

/* THE DIAGRAM SHRINKS, IT DOES NOT VANISH.

   It used to `display:none` below 240px of container height, and the effect
   was that Angela's signature tile-8 hover — the care model drawing itself
   around a patient — never played at ANY size below 1024px. Tile 8 is
   `height: calc(25% - g2)` there, so on an 800x717 window its content box is
   358x133. The threshold was never reached in portrait; it was reached
   immediately.

   The real problem was never the height, it was that the diagram is sized off
   the tile's WIDTH (52cqw). On the desktop tile that is portrait and 52% of
   the width fits. Below 1024 the tile turns landscape — 52% of 358 is 186px
   inside a 133px box — so it overflowed, and hiding it was the quick way out.

   Driving it off the HEIGHT instead makes it fit by construction: it scales
   with the short axis, sits centred on the left, and stays clear of the crest
   in the art square (which is min(46cqw, 60cqh) pinned bottom-right, so ~80px
   at this size). */
@container tile (max-height: 240px){
  .mc-world{
    /* Bottom-left, not centred. The title and subtitle take the top ~43px of a
       133px box and centring put the diagram's outer ring straight through
       "Senior Digital Designer". Anchoring to the bottom drops it into the one
       clear region — below the type, left of the crest. Only the ANCHOR
       changes here; the size is already safe from the base rule. */
    top:auto; bottom:4%; left:5%;
    width: min(52cqw, 64cqh);
  }
}
/* Below this there is no room for a five-ring diagram next to the crest, and
   a 60px version reads as a smudge rather than as a care model. */
@container tile (max-height: 96px){
  .mc-world{ display:none; }
}
@media (prefers-reduced-motion: reduce){
  .mc-logo, .mc-crest, .mc-word,
  .mc-ring, .mc-link, .mc-node, .mc-core{ transition:none; }
}

/* ========================================================================
   REDUCED MOTION
   ====================================================================== */
@media (prefers-reduced-motion: reduce){
  .home-logo path{ animation:none; stroke-dashoffset:0 !important; fill-opacity:1 !important; stroke-opacity:0 !important; }
  .nav-container *{ transition-duration:.001s !important; animation-duration:.001s !important; }
  .page-section.dark > *{ opacity:1; transform:none; }
}


/* ==========================================================================
   THE IDENTITY LINE — home, and only once the pinwheel has fully assembled

   Angela: "I like the idea of us doing, like, a small identity footer line
   when we are a hundred percent zoomed out. But no sooner than that. I hope it
   doesn't ruin the effect."

   It cannot ruin the effect, because it does not exist during the effect.
   nav.js flags `home-rest` on the body at p >= 0.999 — the very end of the
   assembly, after the intro card has finished collapsing into the mark — and
   the line fades up a beat later. Scroll back up and it leaves again.

   This is also where the contact detail lives at the top level of the site.
   Everything else is one click deep, so a recruiter who lands, watches the
   grid build and reaches for a phone number has one in front of them.
   ========================================================================== */
.home-footer{
  position:fixed; left:0; right:0; bottom:0; z-index:60;
  height: var(--home-footer);
  display:flex; align-items:center; justify-content:space-between;
  gap: 16px; padding: 0 calc(var(--g) + 6px);
  font-family: var(--font-body); font-weight:500;
  font-size:10.5px; letter-spacing:.07em; text-transform:uppercase;
  color: var(--ink);
  opacity:0; transform: translateY(7px);
  /* The delay is the point: the tiles land, THEN the line arrives. Landing
     together would read as one event and make the line part of the assembly. */
  transition: opacity .55s ease .3s, transform .55s var(--nav-easing-swoop) .3s;
  pointer-events:none;
}
body.home-rest .home-footer{ opacity:1; transform:none; pointer-events:auto; }
/* Opening the pinwheel menu puts a backdrop over the page; the line goes with
   the page rather than floating on top of the overlay. */
body:has(.nav-container.open) .home-footer{ opacity:0; pointer-events:none; }

.home-footer p{ margin:0; font-weight:500; letter-spacing:inherit; }
.home-footer b{ font-weight:700; }
.home-footer .hf-sep{ opacity:.34; margin:0 .62em; }
.home-footer .hf-id{ opacity:.62; }
.home-footer a{
  color:inherit; text-decoration:none; opacity:.62;
  border-bottom:1px solid transparent;
  /* The band is 32px, so these cannot be 44px targets without eating the
     pinwheel. padding-block buys the hit area inside the band's own height and
     clears WCAG 2.5.8's 24px floor; the same details sit on About at full size
     for anyone who needs a bigger target. */
  padding-block:6px;
  transition: opacity .22s ease, color .22s ease, border-color .22s ease;
}
@media (any-hover:hover){
  .home-footer a:hover{ opacity:1; color: var(--orange); border-bottom-color: currentColor; }
}
.home-footer a:focus-visible{ opacity:1; color: var(--orange); outline:none; border-bottom-color: currentColor; }

@media (max-width:640px){
  /* Two centred lines. The band grew to match, so the tiles lift with it. */
  .home-footer{
    flex-flow:column; align-items:center; justify-content:center;
    /* 9.5px audited as the smallest live text on the site. 11 is the floor. */
    gap:2px; font-size:11px; letter-spacing:.04em; text-align:center;
  }
  /* The role is on every tile already; the name and how to reach her are not. */
  .home-footer .hf-role{ display:none; }
}
@media (prefers-reduced-motion: reduce){
  .home-footer{ transition: opacity .2s linear; transform:none; }
}
