/* ============================================================================
   CASE STUDY — the shared vocabulary for every case study page.

   Lifted verbatim out of payments-home.html, where it had been inline, so that a
   second study does not mean a second copy of 488 lines. It is linked in exactly
   the position the inline block occupied, after site.css and before mobile.css, so
   the cascade is unchanged.
   ========================================================================== */
/* ================= CASE STUDY: Payments Home ================= */
html{scroll-behavior:smooth}
body{background:var(--parchment)}
#cs-stage{position:absolute;left:0;top:0;width:1440px;transform-origin:top left;z-index:1}
.cs-page{position:relative;width:1440px;background:var(--parchment)}

/* Sticky, and for the same reason as .cs-nav it has to live OUTSIDE #cs-stage: that
   element's scale() transform would make it the containing block and turn `fixed` back
   into `absolute`. fit() gives it the stage's scale and offset.

   Hover darkens the arrow rather than recolouring it: the colour stays --dusty-granite and only
   the opacity closes, .48 -> 1. Persimmon made a plain "go back" control read as loudly
   as the accent links in the body copy. */
.cs-back{position:fixed;left:33px;top:31px;width:28px;height:28px;z-index:30;
  transform-origin:top left;color:var(--dusty-granite);opacity:.48;display:block;
  transition:opacity .2s ease,visibility .3s}
.cs-back:hover{opacity:1}
.cs-back.is-away{opacity:0;visibility:hidden}
.cs-back svg{display:block;width:28px;height:28px}

/* Section menu, travelling with the reader.

   It sits OUTSIDE #cs-stage. Everything inside that element inherits a scale()
   transform, and a transformed ancestor becomes the containing block for fixed
   descendants -- so in there `fixed` silently behaves like `absolute`. The previous
   fix kept it inside and cancelled the scroll from script, which works but always
   lags the compositor by a frame, so it visibly bounced during smooth scrolling.
   Out here it is genuinely fixed and never moves.

   fit() gives it the stage's scale and offset so it stays welded to the layout at
   any viewport width.

   Colour: --antique-gold at rest, --dusty-granite on hover and for the section in view.
   --dusty-granite is #423E3D, the value Figma calls dusty-granite.

   Indent, measured off the reference and additive there: current +10px, hover +12px,
   both +22px.

   Timing is deliberately quicker than the reference's .4s. Because hover and current
   are plain CSS state, the moment another item takes over, the previous one's state is
   gone and its outdent starts on the same frame -- so the only thing governing how
   promptly it lets go is this duration. .4s made handing over feel like it dragged;
   .2s on an ease-out curve reads as immediate but still glides. */
.cs-nav{position:fixed;left:33px;top:180px;z-index:30;transform-origin:top left;
  display:flex;flex-direction:column;gap:8px;
  transition:opacity .3s ease,visibility .3s}
.cs-nav a{font-family:'Mackinac';font-weight:400;font-size:16px;line-height:1.5;
  color:var(--antique-gold);text-decoration:none;will-change:transform;
  transition:color .2s cubic-bezier(.33,.85,.4,1),transform .2s cubic-bezier(.33,.85,.4,1)}
.cs-nav a:hover,
.cs-nav a.is-current{color:var(--dusty-granite)}
/* Indent is a hover affordance only. The current section is marked by colour alone --
   it is a state, not something you are pointing at, and moving it made the rail drift
   as you scrolled rather than sitting still. */
.cs-nav a:hover{transform:translateX(12px)}
/* Subpoints under Key decisions. Set back from the main items on three axes at once --
   smaller, lighter, and indented -- because any one of those alone at this size reads
   as an inconsistency rather than a hierarchy. Their hover indent is scaled down to
   match (8 against 12) so the whole rail moves by the same proportion. */
.cs-nav a.sub{font-size:13px;line-height:1.45;max-width:196px;
  padding-left:14px;color:var(--antique-gold);opacity:.72}
.cs-nav a.sub:hover,
.cs-nav a.sub.is-current{color:var(--dusty-granite);opacity:1}
.cs-nav a.sub:hover{transform:translateX(8px)}
/* the parent stays lit while any of its children is the one in view */
.cs-nav a.is-parent{color:var(--dusty-granite)}

/* A persimmon dot marks the section you are in. The rail was gold-to-ink throughout,
   so it had no accent at all; this gives it one that travels down the page as you
   read. It sits in the margin at a fixed offset from the text rather than in the
   flow, so nothing shifts when it appears, and it rides the hover indent with its
   link. Deliberately the same mark as the hero's location dot, at a smaller size.

   Only the item actually in view gets one: is-parent stays colour-only, or a
   subpoint and its parent would both be dotted and the rail would read as having
   two current items. */
.cs-nav a{position:relative}
.cs-nav a::before{content:"";position:absolute;left:-14px;top:.72em;
  width:6px;height:6px;margin-top:-3px;border-radius:50%;
  background:var(--persimmon);opacity:0;transform:scale(.2);
  transition:opacity .2s ease,transform .28s cubic-bezier(.34,1.56,.64,1)}
.cs-nav a.is-current::before{opacity:1;transform:scale(1)}
/* the subpoints are set back by 14px, so their dot sits inside that indent and is
   scaled down with the rest of the hierarchy. top is .72em rather than 50% so it
   aligns to the FIRST line: two of these wrap, and centring on the whole link left
   the dot floating between the lines. */
.cs-nav a.sub::before{left:2px;width:5px;height:5px;margin-top:-2.5px}
@media (prefers-reduced-motion:reduce){
  .cs-nav a::before{transition:none}
}
/* antique-gold on persimmon is unreadable, so the rail steps aside for the footer */
.cs-nav.is-away{opacity:0;visibility:hidden}
@media (prefers-reduced-motion:reduce){
  .cs-nav,.cs-nav a{transition:none}
}

/* The bottom padding is the gap to the footer, matching the 160px that separates every
   other top-level section. Ink to ink that reads as 211px against the sections' 206,
   because the footer contributes 51px of its own interior air on the far side. */
.cs-main{position:relative;width:799px;margin-left:320px;padding:38px 0 160px;
  display:flex;flex-direction:column;gap:160px}

/* ----- Overview ----- */
.cs-overview{display:flex;flex-direction:column;gap:60px;align-items:flex-end;width:100%}
/* ----- Case-study hero: the two-device composition from Figma 557:190 -----
   Two 115x250 devices, the empty state and the populated screen, overlapping and
   counter-rotated. Geometry taken from the file rather than eyeballed: the card is
   799x307 with a 12px radius, --pay-wash on a --outline-stroke hairline, and each phone is a
   115x250 image rotated about its own centre. Figma gives the rotated bounding boxes,
   so the unrotated top-left is centre minus half the natural size:
     pux  centre (461.594, 144.531) -> left 404.094, top 19.531, rotate  4.99deg
     nux  centre (341.208, 162.130) -> left 283.708, top  37.13, rotate -5.98deg
   Checked against Figma's own bounding boxes: 115|cos|+250|sin| = 136.31 vs 136.328
   and 140.42 vs 140.416, so the angles and sizes are right.

   The empty state sits on top, which is the stacking order in the file and reads
   correctly -- it is the screen you meet first, with the populated one behind it.
   The modifiers are hp-back / hp-front, NOT pux / nux: .pux is already Asset 2's
   container class further down this file, and at equal specificity it would win on
   source order and stretch the phone to 797x391.

   Motion: the pair settles into place once when it scrolls into view, then the two
   drift a few pixels on long, mismatched periods (11s and 13s) so they never sync
   into a pulse. Nothing narrates; it just keeps the image from being dead. */
.cs-hero{position:relative;width:100%;height:307px;border-radius:12px;overflow:hidden;
  border:1px solid var(--outline-stroke);background:var(--pay-wash)}
/* Two wrappers so nothing new ever changes the phones' positions RELATIVE to each
   other -- that relative motion at the overlap is what read as a crossover. .hero-stage
   carries scroll parallax (set from script), .hero-float carries the entrance and the
   idle breathing (CSS). Both move the pair as a single object, so the geometry between
   the two devices is fixed forever. */
.hero-stage{position:absolute;inset:0;will-change:transform}
.hero-float{position:absolute;inset:0;will-change:transform,opacity}
.hero-ph{position:absolute;width:115px;height:250px;display:block;will-change:transform}
.hero-ph.hp-back{left:404.094px;top:19.531px;transform:rotate(4.99deg)}
.hero-ph.hp-front{left:283.708px;top:37.13px;transform:rotate(-5.98deg)}
/* Hidden from the first paint, gated on the .js class that site-motion.js sets in the
   <head>. Masking the wrapper rather than each phone means there is one thing to
   reveal, so they can never appear separately. With no JS the class never lands and
   the pair simply shows at rest. */
.js .cs-hero .hero-float{opacity:0}
.cs-hero.shown .hero-float{opacity:1}
/* Entrance: the pair arrives as one object -- rising, unwinding a slight tilt and
   settling up to size, with a touch of overshoot. No stagger between the two. */
.cs-hero.in .hero-float{animation:hero-in 1s cubic-bezier(.22,.9,.28,1.04) both,
                        hero-breathe 9s ease-in-out 1.15s infinite}
/* No per-phone drift at all. Two drifts on mismatched periods are almost never in
   phase, so even moving the same direction the gap between the phones swung 4.4px --
   measurably worse than it looks in the keyframes. The pair is rigid now; every bit of
   life comes from the wrappers, which move both together. */
@keyframes hero-in{
  from{opacity:0;transform:translateY(30px) scale(.955) rotate(-1.8deg)}
  to  {opacity:1;transform:translateY(0)    scale(1)    rotate(0deg)}
}
@keyframes hero-breathe{
  0%,100% {transform:translateY(0)    scale(1)}
  50%     {transform:translateY(-3px) scale(1.006)}
}
@media (prefers-reduced-motion:reduce){
  .js .cs-hero .hero-float{opacity:1}       /* nothing will reveal it, so do it here */
  .cs-hero.in .hero-float{animation:none}
  .hero-stage{transform:none !important}
  .hero-ph.hp-back{transform:rotate(4.99deg)}
  .hero-ph.hp-front{transform:rotate(-5.98deg)}
}
.cs-head{display:flex;flex-direction:column;gap:16px;width:100%;align-items:flex-start}
.cs-titleblock{display:flex;flex-direction:column;gap:12px;width:653px}
.cs-kicker{font-family:'ApercuMonoPro';font-weight:500;font-size:16px;line-height:normal;color:var(--antique-gold)}
.cs-title{font-family:'Kyoto';font-weight:500;font-style:italic;font-size:42px;line-height:1.3;color:var(--dusty-granite)}
.cs-headgroup{display:flex;flex-direction:column;gap:32px;width:100%;align-items:flex-start}
.cs-intro{font-family:'Diatype';font-weight:400;font-size:16px;line-height:1.5;color:var(--dusty-granite);width:100%}
.cs-meta{border:1px solid var(--outline-stroke);border-radius:12px;padding:16px 24px;display:flex;gap:32px;width:100%;box-sizing:border-box}
.cs-meta .col{flex:1 1 0;display:flex;flex-direction:column;gap:8px;min-width:0}
.cs-meta .lbl{font-family:'ApercuMonoPro';font-weight:500;font-size:16px;line-height:normal;color:var(--antique-gold)}
.cs-meta .val{font-family:'Diatype';font-weight:400;font-size:14px;line-height:1.5;color:var(--dusty-granite)}
/* inline link inside prose: persimmon at rest so it is findable mid-sentence.
   The underline stays neutral (per the mock) — persimmon on persimmon read as
   too much — and takes the accent only on hover. */
/* The rule itself comes from the shared .touch::after in site.css; this only sets the
   type and the underline's colour.

   The rule stays --outline-stroke on hover too. Persimmon text over a persimmon rule is
   the double-red that the tan was chosen to avoid, so recolouring it on hover undid the
   reason it exists. The sweep supplies the feedback; the colour does not have to. */
/* nowrap, and it is load-bearing rather than cosmetic. The underline is an
   absolutely positioned ::after pinned with left:0;right:0 to the anchor. When an
   inline anchor breaks across two line boxes its containing block is degenerate, and
   Chrome resolves that pair to a width of ZERO -- so the rule silently disappears
   while every computed value still reports as correct. That is exactly what happened
   here: "please get in touch" broke mid-link, the meta link lost its underline on
   every case study, and Pix kept its own because it never wraps.

   The link needs 75px and the column is 124px, so holding it on one line costs
   nothing. Any future .touch link that has to wrap needs the background-gradient
   underline the About page uses instead, because a gradient paints on every line box
   and an absolute box cannot. */
.cs-meta .touch{color:var(--persimmon);font-family:'Diatype';font-weight:700;font-size:14px;
  white-space:nowrap}
.cs-meta .touch::after{background:var(--outline-stroke);bottom:-1px}
/* The same treatment for a link in running prose: persimmon on an --outline-stroke
   rule, sweeping on hover from the shared .touch::after. The colours are restated
   because the pair above is scoped to the meta box. Weight is left at the
   paragraph's own -- the meta link is bold because it sits in 14px small print. */
.cs-body .touch{color:var(--persimmon);font-weight:700}
/* Both links carry their rule exactly 4px under the baseline, which is why the two
   offsets differ: the anchor is the inline box bottom, and that sits 4.0px under the
   baseline at 14px but 4.5px at 16px, so an identical `bottom` would NOT look
   identical. 4px is the tightest shared value that still clears a descender -- the g
   in "get in touch" reaches 3.11px -- and it lifts Pix off the 7px the inherited
   -2px gave it at this size, which read as floating loose. */
.cs-body .touch::after{background:var(--outline-stroke);bottom:-0.5px}

/* ----- Sections (Context / Key decisions / Impact) ----- */
.cs-section{display:flex;flex-direction:column;gap:32px;width:100%}
.cs-sechead{display:flex;flex-direction:column;gap:12px}
.cs-tag{display:flex;flex-direction:column;gap:16px}
.cs-line{width:45px;height:2px;background:#C8D665}
.cs-seclabel{font-family:'ApercuMonoPro';font-weight:500;font-size:16px;line-height:normal;color:var(--antique-gold)}
.cs-h2{font-family:'Mackinac';font-weight:500;font-size:28px;line-height:1.3;color:var(--dusty-granite)}
.cs-body{font-family:'Diatype';font-weight:400;font-size:16px;line-height:1.5;color:var(--dusty-granite);width:100%}
.cs-body p{margin:0 0 1.5em}
.cs-body p:last-child{margin-bottom:0}
.cs-visual{font-family:'Diatype';font-weight:700}

/* Key-decisions group: several sub-sections */
/* 128, not 100. The section label (.cs-tag) is 34px tall with a 12px gap beneath it,
   so every TAGGED heading starts 46px lower than an untagged one -- which meant the
   160/100 written here rendered as 206/100 to the eye, and the subsections read twice
   as tight rather than the intended 1.6x. 128 puts the optical rhythm back at
   206 : 128, the ratio the numbers were always meant to express. */
.cs-group{display:flex;flex-direction:column;gap:128px;width:100%}
.cs-sub{display:flex;flex-direction:column;gap:32px;width:100%}

/* ----- Context media: wa-chat, looping, with a camera that follows the conversation -----
   The phone is 600px tall inside a 391px window. translateY(0) is the wide resting
   shot (45px of space above the phone, so it never reads as cut off); the pan then
   runs down to -215px at 1.3x to sit on the Pix card. */
.cs-media{position:relative;width:100%;height:391px;overflow:hidden;border-radius:12px;
  background:var(--dove-ivory);border:1px solid var(--outline-stroke)}
.cs-phone{position:absolute;left:260px;top:45px;width:280px;height:600px;
  transform:translateY(0) scale(1);transform-origin:50% 50%;will-change:transform}
.cs-phone.is-playing{animation:wa-follow 11.424s linear infinite}
.cs-phone img{display:block;width:280px;height:600px}
/* Camera move, keyed to the chat's real timeline (loop 11.424s):
   rest wide on the phone (space above the frame, never looks cut off) while the
   chat is empty -> push in on the first messages -> pan down to the reply -> pan
   down to the Pix card -> hold; the chat clears at the loop edge and the camera
   pulls back out to the resting shot while the screen is empty. */
@keyframes wa-follow{
  /* chat has just cleared — pull back out to the wide resting shot */
  0%    {transform:translateY(-215px) scale(1.3); animation-timing-function:cubic-bezier(.45,0,.25,1)}
  10.5% {transform:translateY(0) scale(1)}
  /* rest wide, empty screen (first message lands at 1.4s) */
  14%   {transform:translateY(0) scale(1); animation-timing-function:cubic-bezier(.45,0,.25,1)}
  /* push in on the two outgoing messages (1.4s, 2.5s) */
  26.3% {transform:translateY(-20px) scale(1.3)}
  /* hold through the typing indicator (3.9s) and Carlos's reply landing (5.5s) */
  49.9% {transform:translateY(-20px) scale(1.3); animation-timing-function:cubic-bezier(.45,0,.25,1)}
  /* pan down to the reply */
  60.4% {transform:translateY(-124px) scale(1.3)}
  /* hold through the second typing beat (6.7s) and the Pix card landing (8.1s) */
  72.7% {transform:translateY(-124px) scale(1.3); animation-timing-function:cubic-bezier(.45,0,.25,1)}
  /* pan down to the Pix card and hold until the chat clears */
  83.2% {transform:translateY(-215px) scale(1.3)}
  100%  {transform:translateY(-215px) scale(1.3)}
}
@media (prefers-reduced-motion:reduce){.cs-phone.is-playing{animation:none;transform:translateY(0) scale(1)}}
/* Figure = media + caption (Figma 532:93).
   One rhythm for every block boundary: the section's 32px gap separates heading,
   copy and figures alike, wherever a figure falls. The caption sits 20px under
   its card, so proximity alone binds it to the image above (20px up vs 32px
   down) without inflating the gap that follows. */
.cs-figure{display:flex;flex-direction:column;gap:20px;align-items:flex-start;width:100%;margin:0}
.cs-caption{font-family:'ApercuMono';font-weight:400;font-size:14px;line-height:normal;
  color:var(--antique-gold);text-align:center;width:100%;margin:0}

/* full-width persimmon bleed behind the (centred) footer */
#bg-footer{position:absolute;left:0;width:100%;z-index:0;background:var(--persimmon)}

/* ----- Interactive Payments Home walkthrough (Figma 516:336 / 536:80) -----
   Same 799x391 dove-ivory card as the Context media. The phone screen is a real
   scroll: one tall content strip clipped to the screen viewport, with the phone
   chrome and the floating Request-payment button held fixed on top. Clicking the
   arrow scrolls between the two annotated states. */
.pux{position:relative;width:100%;height:391px;background:var(--dove-ivory);
  border:1px solid var(--outline-stroke);border-radius:12px;overflow:hidden}
.pux-phone{position:absolute;left:233px;top:21px;width:161px;height:350px}
.pux-base{position:absolute;inset:0;width:161px;height:350px;display:block}
.pux-view{position:absolute;left:3.43px;top:42.89px;width:154.15px;height:303.6px;overflow:hidden;
  border-bottom-left-radius:20.5px;border-bottom-right-radius:20.5px}
.pux-strip{display:block;width:154.15px;height:548.27px;
  transition:transform .85s cubic-bezier(.4,0,.2,1);will-change:transform}
.pux[data-state="b"] .pux-strip{transform:translateY(-244.7px)}
.pux-fab{position:absolute;left:64.87px;top:304.32px;width:87.67px;height:29.49px;display:block}

/* annotation groups: leader line + label, with a description revealed on hover.
   Hovering a label zooms the phone to that section (Figma 522:503, 532:81, 535:59,
   541:98, 541:112, 541:123) and hides the other labels. */
.pux-ann{position:absolute;left:401.33px;top:var(--t);width:var(--w,178px);
  display:flex;flex-direction:column;gap:9px;pointer-events:none;
  opacity:0;transform:translateX(-6px);
  transition:opacity .3s ease,transform .3s ease,
             top .5s cubic-bezier(.4,0,.2,1),left .5s cubic-bezier(.4,0,.2,1)}
.pux[data-state="a"] .pux-ann.a,.pux[data-state="b"] .pux-ann.b{opacity:1;transform:none}
.pux-row{display:flex;align-items:center}
.pux-line{width:29.68px;height:5.33px;flex:none;display:block}
.pux-note{background:var(--parchment);border:1px solid var(--outline-stroke);border-radius:12px;
  padding:8px 12px;font-family:'ApercuMono';font-weight:400;font-size:12px;line-height:normal;
  color:var(--dusty-granite);white-space:nowrap;pointer-events:none;cursor:default}
/* Invisible, stationary hit target sitting on each label's resting position. The
   visible label animates away on hover, so hit-testing must not follow it, and
   only the current view's targets are live (the other view's are still in the DOM). */
.pux-hit{position:absolute;left:431.01px;top:var(--t);padding:8px 12px;border:1px solid transparent;
  font-family:'ApercuMono';font-weight:400;font-size:12px;line-height:normal;white-space:nowrap;
  opacity:0;pointer-events:none;cursor:default}
.pux[data-state="a"] .pux-hit.a,.pux[data-state="b"] .pux-hit.b{pointer-events:auto}
.pux-desc{margin:0;padding-left:28px;font-family:'Diatype';font-weight:400;font-size:12px;
  line-height:1.2;color:var(--antique-gold);opacity:0;transition:opacity .3s ease .05s}
.pux-desc strong{font-family:'Diatype';font-weight:700}
/* active: slide into the annotation slot and reveal the copy. Driven by a
   data-attribute rather than :hover, because the label moves out from under the
   cursor as it animates, which would otherwise drop the hover and flicker. */
.pux[data-hover] .pux-ann:not(.on){opacity:0}
.pux-ann.on{left:397.33px;top:var(--th)}
.pux-ann.on .pux-desc{opacity:1}
/* phone zooms to the active section (scale 230/161) */
.pux-phone{transform-origin:top left;transition:transform .55s cubic-bezier(.4,0,.2,1)}
.pux[data-hover="tx"]      .pux-phone{transform:translate(-74px,-55px)  scale(1.42857)}
.pux[data-hover="shared"]  .pux-phone{transform:translate(-74px,-179px) scale(1.42857)}
.pux[data-hover="request"] .pux-phone{transform:translate(-74px,-309px) scale(1.42857)}
.pux[data-hover="pixkey"]  .pux-phone{transform:translate(-74px,30px)   scale(1.42857)}
.pux[data-hover="privacy"] .pux-phone{transform:translate(-74px,-60px)  scale(1.42857)}
.pux[data-hover="support"] .pux-phone{transform:translate(-74px,-190px) scale(1.42857)}

/* toggle button */
.pux-toggle{position:absolute;left:742px;top:335px;width:36px;height:36px;padding:0;
  border:1px solid var(--outline-stroke);border-radius:50%;background:var(--parchment);
  cursor:pointer;display:flex;align-items:center;justify-content:center;
  transition:transform .25s ease,border-color .25s ease}
.pux-toggle:hover{transform:scale(1.08);border-color:var(--antique-gold)}
.pux-toggle svg{display:block;width:24px;height:24px;transition:transform .5s cubic-bezier(.4,0,.2,1)}
.pux[data-state="b"] .pux-toggle svg{transform:rotate(180deg)}
/* draw the eye to the arrow until it has been used */
.pux-toggle.idle{animation:pux-bob 2.1s ease-in-out infinite}
@keyframes pux-bob{0%,72%,100%{transform:translateY(0)}82%{transform:translateY(3.5px)}92%{transform:translateY(0)}}
.pux-toggle.idle::after{content:"";position:absolute;inset:-1px;border-radius:50%;
  border:1px solid var(--antique-gold);opacity:0;animation:pux-ring 2.1s ease-out infinite}
@keyframes pux-ring{0%{transform:scale(1);opacity:.5}60%,100%{transform:scale(1.5);opacity:0}}
@media (prefers-reduced-motion:reduce){
  .pux-strip,.pux-toggle svg,.pux-ann{transition:none}
  .pux-toggle.idle{animation:none}
  .pux-toggle.idle::after{animation:none;opacity:0}
}

/* ----- Transactions walkthrough (Figma 557:190 / 557:209 / 560:46 / 560:50) -----
   A looping, non-interactive playback: tap "See all", long-press the first
   transaction, then tap Report. The cursor is the Figma reference exactly
   (Ellipse 23: 32px, 1px white stroke, drop shadow dy14 blur34 black 25%). */
.flow{position:relative;width:100%;height:391px;background:var(--dove-ivory);
  border:1px solid var(--outline-stroke);border-radius:12px;overflow:hidden}
.flow-phone{position:absolute;left:305px;top:21px;width:161px;height:350px}
.flow-screen{position:absolute;inset:0;width:161px;height:350px;display:block;opacity:0}
/* One tap cursor, shared by the hero and the transactions walkthrough. */
.tap-cursor{position:absolute;left:0;top:0;width:32px;height:32px;margin:-16px 0 0 -16px;
  border-radius:50%;border:1px solid rgba(255,255,255,.92);
  background:rgba(0,0,0,.10);-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px);
  box-shadow:0 14px 34px rgba(0,0,0,.25),0 2px 6px rgba(0,0,0,.18);
  opacity:0;will-change:transform}
.flow-play .flow-screen{animation:flow-s1 10s linear infinite}
.flow-play .s2{animation-name:flow-s2}
.flow-play .s3{animation-name:flow-s3}
.flow-play .s4{animation-name:flow-s4}
.flow-play .flow-cursor{animation:flow-cur 10s linear infinite}
@keyframes flow-s1{0%,16%{opacity:1}17.5%,95%{opacity:0}96.5%,100%{opacity:1}}
@keyframes flow-s2{0%,16%{opacity:0}17.5%,35%{opacity:1}36.5%,100%{opacity:0}}
@keyframes flow-s3{0%,35%{opacity:0}36.5%,52%{opacity:1}53.5%,100%{opacity:0}}
@keyframes flow-s4{0%,52%{opacity:0}53.5%,95%{opacity:1}96.5%,100%{opacity:0}}
@keyframes flow-cur{
  0%   {transform:translate(420px,250px) scale(1);opacity:0;animation-timing-function:cubic-bezier(.4,0,.2,1)}
  4%   {transform:translate(430px,212px) scale(1);opacity:1;animation-timing-function:cubic-bezier(.4,0,.2,1)}
  13%  {transform:translate(440.8px,152px) scale(1);opacity:1}      /* over "See all" */
  15%  {transform:translate(440.8px,152px) scale(.82);opacity:1}    /* tap */
  16.5%{transform:translate(440.8px,152px) scale(1);opacity:1;animation-timing-function:cubic-bezier(.4,0,.2,1)}
  26%  {transform:translate(385px,103.6px) scale(1);opacity:1}      /* over LaLueur Beauty */
  28%  {transform:translate(385px,103.6px) scale(.82);opacity:1}    /* press and hold */
  35%  {transform:translate(385px,103.6px) scale(.82);opacity:1}    /* long press ends (~0.7s) */
  37.5%{transform:translate(385px,103.6px) scale(1);opacity:1;animation-timing-function:cubic-bezier(.4,0,.2,1)}
  48%  {transform:translate(450.3px,53.6px) scale(1);opacity:1}     /* over Report */
  50%  {transform:translate(450.3px,53.6px) scale(.82);opacity:1}   /* tap */
  52%  {transform:translate(450.3px,53.6px) scale(1);opacity:1}
  58%,95%{transform:translate(450.3px,53.6px) scale(1);opacity:0}
  100% {transform:translate(420px,250px) scale(1);opacity:0}
}
@media (prefers-reduced-motion:reduce){
  .flow-play .flow-screen,.flow-play .flow-cursor{animation:none}
  .flow-screen.s1{opacity:1}
  .flow-cursor{opacity:0}
}

/* ----- Explored vs shipped comparison (the rejected two-tab structure) ----- */
.cmp{position:relative;width:100%;height:391px;background:var(--dove-ivory);
  border:1px solid var(--outline-stroke);border-radius:12px;overflow:hidden}
.cmp-item{position:absolute;top:22px;width:140px;display:flex;flex-direction:column;
  align-items:center;gap:14px}
.cmp-item.left{left:229.5px}
.cmp-item.right{left:429.5px}
.cmp-item img{display:block;width:140px;height:304px}
.cmp-label{background:var(--parchment);border:1px solid var(--outline-stroke);border-radius:12px;
  padding:8px 12px;font-family:'ApercuMono';font-weight:400;font-size:12px;line-height:normal;
  color:var(--dusty-granite);white-space:nowrap}
.cmp-label em{font-style:normal;color:var(--antique-gold)}

/* ----- How the prototype was built -----
   The prototype itself can't be shown, so the figure shows how it was made instead.
   The prose calls the toolchain a cocktail rather than a sequence, so this converges
   four inputs on one build rather than running left to right -- a pipeline would say
   the tools ran in an order they didn't. Only what the paragraph states appears here,
   so the two can't drift apart. Plays once, like the Impact callout.

   Geometry is fixed px inside a 719px content box (799 panel - 40px padding each
   side); the whole page is authored at 1440 and scaled, so this never reflows. */
.chain{width:100%;background:var(--dove-ivory);border:1px solid var(--outline-stroke);
  border-radius:12px;padding:44px 40px;box-sizing:border-box}
.chain-body{position:relative;width:100%;height:190px}
.chain-wires{position:absolute;inset:0;width:100%;height:100%;overflow:visible}
.chain-wires path{fill:none;stroke:var(--persimmon);stroke-opacity:.5;stroke-width:1;
  stroke-dasharray:1;stroke-dashoffset:1;
  transition:stroke-dashoffset 1s cubic-bezier(.4,0,.2,1)}
.chain.is-in .chain-wires path{stroke-dashoffset:0}
.chain.is-in .chain-wires path:nth-child(2){transition-delay:.08s}
.chain.is-in .chain-wires path:nth-child(3){transition-delay:.16s}
.chain.is-in .chain-wires path:nth-child(4){transition-delay:.24s}

/* rows are a fixed height so the wire endpoints land on their centres: 17, 69,
   121, 173 = (34/2) + i*(34+18) */
.chain-in{position:absolute;left:0;top:0;width:200px;margin:0;padding:0;
  list-style:none;display:flex;flex-direction:column;gap:18px}
/* flush right, so every label sits the same 12px off the wire that leaves it --
   left-aligned, the short names floated a long way from their own connector.
   Centred rather than baseline-aligned so the text sits on the row's midpoint,
   which is where the wire attaches. */
.chain-in li{height:34px;display:flex;align-items:center;justify-content:flex-end;gap:10px}
/* Full height and centred by flex, NOT by translateY(-50%): the entrance below sets
   `transform` on this element, which would overwrite a transform-based centring and
   drop the block off the wire junction. */
.chain-out{position:absolute;left:434px;top:0;height:100%;
  display:flex;align-items:center;gap:16px}
.chain-outtext{display:flex;flex-direction:column;gap:7px;max-width:256px}
.chain-dot{width:11px;height:11px;border-radius:50%;background:var(--persimmon);flex:none}
.chain-t{font-family:'ApercuMonoPro';font-weight:500;font-size:13px;line-height:1.35;
  color:var(--dusty-granite)}
.chain-s{font-family:'Diatype';font-weight:400;font-size:12px;line-height:1.4;
  color:var(--antique-gold)}
.chain-outtext .chain-s:first-of-type{margin-top:3px}

.chain-in li,.chain-out{opacity:0;transform:translateY(8px);
  transition:opacity .45s ease,transform .5s cubic-bezier(.22,.8,.3,1)}
.chain.is-in .chain-in li,.chain.is-in .chain-out{opacity:1;transform:none}
.chain.is-in .chain-in li:nth-child(2){transition-delay:.09s}
.chain.is-in .chain-in li:nth-child(3){transition-delay:.18s}
.chain.is-in .chain-in li:nth-child(4){transition-delay:.27s}
.chain.is-in .chain-out{transition-delay:.62s}
@media (prefers-reduced-motion:reduce){
  .chain-in li,.chain-out{opacity:1;transform:none;transition:none}
  .chain-wires path{stroke-dashoffset:0;transition:none}
}

/* ----- Impact metrics callout ----- */
.stats{width:100%;background:var(--dove-ivory);border:1px solid var(--outline-stroke);
  border-radius:12px;padding:36px 40px;box-sizing:border-box;display:flex;flex-direction:column;gap:28px}
.stats-eyebrow{display:flex;align-items:center;gap:12px;
  font-family:'ApercuMonoPro';font-weight:500;font-size:14px;line-height:normal;color:var(--antique-gold)}
.stats-eyebrow span{width:0;height:2px;background:var(--persimmon);flex:none;
  transition:width .7s cubic-bezier(.4,0,.2,1)}
.stats-row{display:flex;align-items:stretch}
.stat{flex:1 1 0;display:flex;flex-direction:column;gap:6px;padding-right:32px}
.stat + .stat{padding-left:32px;border-left:1px solid var(--outline-stroke)}
.stat-n{font-family:'Mackinac';font-weight:500;font-size:44px;line-height:1.1;color:var(--dusty-granite)}
.stat-l{font-family:'Diatype';font-weight:400;font-size:14px;line-height:1.4;color:var(--dusty-granite)}
/* the definition each metric needs, so the prose doesn't have to carry it */
.stat-d{font-family:'Diatype';font-weight:400;font-size:12px;line-height:1.4;color:var(--antique-gold)}
.stats-note{display:flex;align-items:baseline;gap:10px;padding-top:24px;
  border-top:1px solid var(--outline-stroke);
  font-family:'Diatype';font-weight:400;font-size:14px;line-height:1.4;color:var(--antique-gold)}
.stats-note b{font-family:'Diatype';font-weight:700;color:var(--dusty-granite)}

/* The callout animates in once when it reaches the reader: the rule draws out,
   the three stats rise in sequence, and the numbers count up to their value. */
.stats.is-in .stats-eyebrow span{width:45px}
.stats .stat,.stats .stats-note{opacity:0;transform:translateY(10px);
  transition:opacity .5s ease,transform .55s cubic-bezier(.22,.8,.3,1)}
.stats.is-in .stat,.stats.is-in .stats-note{opacity:1;transform:none}
.stats.is-in .stat:nth-child(2){transition-delay:.10s}
.stats.is-in .stat:nth-child(3){transition-delay:.20s}
.stats.is-in .stats-note{transition-delay:.42s}
@media (prefers-reduced-motion:reduce){
  .stats .stat,.stats .stats-note{opacity:1;transform:none;transition:none}
  .stats-eyebrow span{width:45px;transition:none}
}

/* ---------- Numbered rules ----------
   A short list of constraints or principles, set as a bordered table rather than
   prose, because that is what they were: things written down and agreed before
   anything was drawn. Lifted out of payment-requests.html when the Copilot study
   needed the same object for its guiding principles. A page can still set its own
   row padding; everything else is shared. */
.cs-rules{display:flex;flex-direction:column;gap:0;width:100%;
  border:1px solid var(--outline-stroke);border-radius:12px;overflow:hidden}
.cs-rule{display:flex;gap:24px;align-items:baseline;padding:20px 24px}
.cs-rule + .cs-rule{border-top:1px solid var(--outline-stroke)}
/* Antique gold, not the accent. These numbers mark nothing -- they are scaffolding,
   there so the rules can be referred to. In persimmon they competed with the places
   where the accent is carrying an argument, and spending a loud colour on ordinal
   numbers is what makes it stop meaning anything elsewhere. */
.cs-rule .n{font-family:'ApercuMonoPro';font-weight:500;font-size:14px;
  color:var(--antique-gold);flex:none;width:24px}
.cs-rule .t{font-family:'Mackinac';font-weight:500;font-size:18px;line-height:1.3;
  color:var(--dusty-granite);flex:none;width:190px}
.cs-rule .d{font-family:'Diatype';font-weight:400;font-size:16px;line-height:1.5;
  color:var(--muted)}

/* ============================ FIGURE LENS ============================
   Not inside mobile.css, and that placement is load-bearing. The lens is opened from a
   phone held upright, and its caption asks the reader to turn the device -- at which
   point the viewport is 844 wide, past the 760 breakpoint, and every rule in that file
   stops applying. The overlay would have lost its styling at the exact moment it was
   being used. Here it survives the turn.

   The button that opens it is still mobile-only; mobile.js adds and removes it. */
/* Tap to expand. Every figure is scaled to 0.438 of the width it was drawn for, which
   is legible as a composition and not as detail, so each one gets a way to be seen at
   size. The overlay turns the page ninety degrees and gives the figure the long edge:
   844 against 350 is 2.4x the room, so it lands near its authored scale.

   Nothing about the figure itself changes -- the same node is scaled by a different
   number -- so there is no second copy to keep in step, and desktop never sees any of
   it. The button is a real <button> injected by mobile.js, so it is reachable by
   keyboard and announced. */
.cs-zoom {
  position: absolute; right: 8px; bottom: 8px; z-index: 4;
  width: 34px; height: 34px; padding: 0; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: rgba(253,251,239,.92); border: 1px solid var(--outline-stroke);
  box-shadow: 0 2px 10px rgba(66,62,61,.14);
  color: var(--dusty-granite); cursor: pointer;
}
.cs-zoom svg { width: 16px; height: 16px; display: block; }
.cs-figure { position: relative; }

#cs-lens {
  position: fixed; inset: 0; z-index: 200;
  background: rgba(28,24,22,.93);
  display: flex; align-items: center; justify-content: center;
  opacity: 0; visibility: hidden;
  transition: opacity .3s ease, visibility .3s;
  overscroll-behavior: contain;
}
#cs-lens.is-on { opacity: 1; visibility: visible; }
/* The stage is rotated, so its width is the viewport's height and vice versa.

   The entrance rides on `scale`, not on `transform`: the rotation is written inline by
   script and the two would overwrite each other, whereas the individual transform
   properties compose. It settles rather than snaps -- a touch past nothing on the way in,
   which is what stops a full-screen overlay reading as a hard cut. */
#cs-lens .lens-stage { position: relative; transform-origin: center center; }
/* The wrapper carries the FLIP; the stage carries the rotation. Keeping them on separate
   elements is what lets the two compose instead of overwriting each other. */
#cs-lens .lens-anim {
  transform-origin: center center;
  transition: transform .38s cubic-bezier(.25,.9,.3,1);
  will-change: transform;
}
/* The caption and the close button arrive a beat after the figure, so the figure is
   what the eye lands on first. */
#cs-lens .lens-hint, #cs-lens .lens-close {
  opacity: 0; transition: opacity .22s ease;
}
#cs-lens.is-on .lens-hint, #cs-lens.is-on .lens-close {
  opacity: 1; transition-delay: .26s;
}
@media (prefers-reduced-motion: reduce) {
  #cs-lens, #cs-lens .lens-anim,
  #cs-lens .lens-hint, #cs-lens .lens-close { transition: none !important; }
}
#cs-lens .lens-close {
  position: fixed; top: 12px; right: 12px; z-index: 2;
  width: 40px; height: 40px; border-radius: 50%; padding: 0;
  display: flex; align-items: center; justify-content: center;
  background: rgba(253,251,239,.94); border: 1px solid var(--outline-stroke);
  color: var(--dusty-granite); cursor: pointer;
}
#cs-lens .lens-close svg { width: 18px; height: 18px; display: block; }
/* A pill, not bare text: it sits over whatever the figure happens to be and the
   backgrounds here run from near-white to photographic. */
#cs-lens .lens-hint {
  position: fixed; bottom: 16px; left: 0; right: 0; z-index: 3;
  text-align: center; margin: 0;
}
#cs-lens .lens-hint span {
  display: inline-block; padding: 7px 14px; border-radius: 999px;
  background: rgba(253,251,239,.94); border: 1px solid var(--outline-stroke);
  font-family: 'ApercuMono'; font-size: 12px; line-height: 1;
  color: var(--dusty-granite); white-space: nowrap;
}
body.lens-open { overflow: hidden; }
