/* ═══════════════════════════════════════════════════════════════════════
   DreamPlay Concise — live presentation stage.
   Target device: MacBook 14 inch, PRESENTED FULLSCREEN: the stage is
   1512x982. In a browser window with chrome it is ~1512x860; everything
   here is derived from the live viewport, so both are correct.

   ONE HEADING SIZE
   The deck used to run two competing title sizes: .stage-title at 40px and
   .stage-sub at 26px. Adjacent scenes jumped between them, which reads as
   inconsistency rather than as hierarchy. There is now exactly one heading
   size — 40px, weight 600 — and it belongs to the scene that OPENS a
   chapter. A scene that continues a chapter carries the 12px kicker and,
   ONE heading size. Every scene titles itself at 40px, the chapter-title
   size, whether the line is a statement or just a name. The 18px
   .stage-label role is retired: two heading sizes read as two hierarchies
   from across a room, and there is only one here.

   THE CENTRING MODEL
   Every scene is a THREE row grid:
       minmax(--band, 1fr)  ·  auto  ·  minmax(--gutter, 1fr)
   The content is row two. Rows one and three are equal flexible tracks with
   floors, so:
     · short content splits the free space evenly and lands on the true
       centre of the viewport, not on the centre of the space under the
       title band;
     · tall content bottoms out on row one's floor (the title band) and
       still cannot come closer than --gutter to the bottom edge.
   The old model was `--band 1fr`, which centred content inside row two only
   and therefore sat every scene (band/2) px low.

   --band is the FLOOR under the title, not padding. It is set per scene in
   the markup from that scene's own header stack:
     kicker only ................ 92
     kicker + 40px title ....... 169   (40px under the title's baseline box)
     cover (title + lede stack)  172
   --gutter is the minimum clearance to the bottom edge: 40px everywhere.

   CONTENT IMAGES vs BACKGROUND IMAGES
   A content image (.plate board, .frame-phone, showcase strip) carries its
   own information and often its own type. No deck text may cross one.
   A background image (.bleed cover art, .film__bg blurred backdrop) is a
   ground, and text over it is expected. Only those two carry a scrim.

   MEDIA IS HEIGHT-BOUND, NOT WIDTH-BOUND
   A board's --pw is derived from its own aspect ratio against the height
   left under its own band, so the picture plus its caption always clears
   the floor. Nothing in the deck is sized from width alone any more.
   ═══════════════════════════════════════════════════════════════════ */

:root{
  /* ── DreamSetGo theme, measured off the Figma source ─────────────────
     Ground white, slide titles blue, body ink near-black, secondary and
     muted greys for supporting text, hairline borders, one blue accent.
     --ink-3 (muted, #9ea3b2) is 2.52:1 on white and fails AA, so it is kept
     for the record but never assigned to running text below; anywhere the
     dark-theme original reached for --ink-3 as copy, --ink-2 (5.38:1) is
     used instead. */
  --ground-1:#ffffff;
  --ink-1:#131416; --ink-2:#646a7d; --ink-3:#9ea3b2;
  --title-blue:#0000b7;
  --border-1:#e0e2e7; --border-2:#eeeef1;
  --accent:#3548d4; --tint:#b2c4ff;
  /* Retained: the gate and the closing slide reference these by name. The
     gate is the same ground the deck opens onto, so it tracks --ground-1. */
  --dark-ground:var(--ground-1);
  --dark-ink-1:var(--ink-1); --dark-ink-2:var(--ink-2); --dark-ink-3:var(--ink-2);
  --indigo:#3548d4; --chartreuse:#3548d4; --payne:#b2c4ff;
  --indigo-on-dark:#3548d4;    /* the deck's one accent stroke, #3548d4 */

  --font-display:'Schibsted Grotesk',system-ui,sans-serif;
  --font-body:'Inter',system-ui,sans-serif;

  /* Fixed desktop values. Production clamps are already pinned at 1440. */
  --stage-title:40px;          /* the deck's ONLY heading size */
  --stage-kicker:12px;         /* .ch-eyebrow */
  --mediacap-title:18px;       /* .cs-mediacap-title, .frame-cap-title */
  --mediacap:15px;             /* .cs-mediacap */
  --card-title:18px;           /* .cs-card-name */
  --card-body:16px;            /* .cs-card-desc */
  --statement:19px;

  --safe-x:64px; --title-top:56px;
  --band:92px;                 /* kicker-only fallback */
  --gutter:40px;               /* minimum clearance at the floor */
  --plate-x:64px;
  /* A title that fits on one line must BE on one line. The longest title in
     the deck, "A low-cost way into the Dream Sports ecosystem.", measures
     915px at 40/600, so the measure is set just above it. */
  --title-measure:944px;
  --row-h:520px;               /* phone frame, identical in every phone row */
  --hero-h:564px;              /* single phone, brand film */

  --s2:8px; --s3:12px; --s4:17px; --s5:24px; --s6:32px; --s7:48px;
  --r-capsule:11px; --r-tile:18px; --r-phone:20px;
  --r-card:22px;               /* the reference card radius; --r-tile stays 18 for .plate--edge */
  --card-fill:linear-gradient(180deg,#ffffff 0%,#f6f7fb 100%);

  --ease-standard:cubic-bezier(.4,0,.2,1);
  --ease-in-out:cubic-bezier(.42,0,.58,1);
  --ease-decel:cubic-bezier(0,0,.2,1);
}

*,*::before,*::after{box-sizing:border-box}
html,body{margin:0;height:100%;background:var(--ground-1);overscroll-behavior:none;
  scrollbar-width:none;-ms-overflow-style:none}
html::-webkit-scrollbar,body::-webkit-scrollbar{width:0;height:0;display:none}
body{font-family:var(--font-body);color:var(--ink-1);-webkit-font-smoothing:antialiased}
#stage{-webkit-user-select:none;user-select:none}

/* ═══════════════════════════════════════════════════════════════════════
   THE CANVAS

   Every slide in the Figma source is 1920x1080 and every element in it has
   an absolute x/y/w/h. Reproducing that faithfully means one fixed canvas
   per scene, scaled to fit, with absolutely positioned children — not a
   re-layout in flow, which is what drifted last time.

   --k is min(innerWidth/1920, innerHeight/1080), set by the small script at
   the end of index.html: CSS scale() takes a unitless number, so that one
   value cannot be expressed here.
   ═══════════════════════════════════════════════════════════════════════ */
.scene{position:relative;width:100vw;height:100dvh;
  display:grid;place-items:center;overflow:hidden;background:var(--sbg,#ffffff)}
/* Centred by offset rather than by the grid: a grid track sizes itself to the
   1920px canvas and then the scaled result sits off to one side. left/top 50%
   with a translate is exact at every viewport. */
.slide{position:absolute;left:50%;top:50%;width:1920px;height:1080px;flex:none;
  overflow:hidden;
  transform:translate(-50%,-50%) scale(var(--k,1)) scale(var(--settle,1));transform-origin:center center}
.slide > *{position:absolute;margin:0}
.slide img{display:block}

/* ── The deck's own type ramp, derived from the source ─────────────────
   Sizes, weights, colours and line heights below are the discrete set the
   Figma file actually uses. Roles are classes, so no element carries a
   one-off font declaration.

   SPINE          x = 100  (left margin on every slide)
   TITLE BASELINE y = 100  (light slides) · y = 240 / 344 (dark slides)
   ─────────────────────────────────────────────────────────────────── */
:root{
  --dsg-blue:#0000b7;      /* ground of the four dark slides, and the title ink */
  --dsg-blue-hi:#1414bd;   /* the one emphasis blue, slide 09 */
  --ink-900:#000000;
  --ink-800:#0b0d10;
  --ink-700:#111111;
  --ink-650:#141416;
  --ink-600:#1d1d1f;
  --ink-500:#383838;
  --ink-450:#3c3d46;
  --ink-400:#444448;
  --ink-300:#63636b;   /* was #6a6a72: 4.34:1 on --tint-10, under AA */
  --ink-250:#86868b;
  --tint-10:rgba(0,0,183,.1);
}
/* Base sits on the canvas itself, not on `.slide p`, so a single role class
   still wins on specificity. */
.slide{font-family:'Schibsted Grotesk',system-ui,sans-serif;
  font-weight:400;color:var(--ink-650)}
.slide p{white-space:normal}

/* ═══ THE RAMP ═══════════════════════════════════════════════════════════
   Six text steps plus two display steps, on a 1.25 mean ratio:

       16 · 20 · 24 · 32 · 40 · 48   ·   80 · 100
         1.25 1.20 1.33 1.25 1.20      1.67 1.25

   Every step is at least 1.20, so two adjacent levels can never be mistaken
   for each other. The old file had .t-subh, .t-body, .t-body-mut, .t-name
   and .t-lead all at 32px: five roles, one size, no hierarchy. Body now sits
   a full 1.33 step below the section head, and the head a full 1.5 below the
   title.

   WHY BODY IS 24 AND NOT 32
   The canvas is 1920 and the deck is presented at 1440, so k = 0.75 and a
   canvas px renders at three quarters. 32px canvas rendered at 24px, which is
   sub-head scale, not body scale: a wall of it read heavy from the room. 24px
   canvas renders at 18px, which is the size body copy should be on a slide
   read from six feet away. The floor is 16px canvas = 12px rendered, and
   nothing here goes below it.

   ROLE PER STEP
     100  hero, dark slides
      80  display: cover, vision, the slide 06 press, the slide 03 eyebrow
      48  TITLE, the anchor: .t-title, .t-h2, .t-hero-sub, .t-statnum, .t-goal
      40  STATEMENT: .t-claim, .t-vision, .t-lede-sm
      32  LEDE and SECTION HEAD: .t-lead, .t-deck, .t-subh, .t-name,
          .p11-name, .impact-val
      24  BODY: .t-body, .t-body-mut, .t-li, .t-known, .t-value,
          .t-goal-lab, .t-eyebrow-sm, .p11-sub, .flow-step
      20  SUPPORTING: .t-annot, .t-tag, .t-statlab
      16  FLOOR: .t-label, .t-annot-sm, .impact-eyebrow, .impact-lab

   THE HEAD STEP KEEPS 32 AND GIVES UP WEIGHT
   .t-subh and .t-name were 700. With a 1.33 size step under them the extra
   weight is redundant and reads as shouting, so both drop to 600, the same
   weight .p11-name already carried. One head role, one weight.

   CONTRAST AFTER THE CUT
   Smaller type raises the AA bar from 3:1 to 4.5:1. --ink-250 #86868b is
   3.62:1 on white: it cleared AA only under the large-text exemption, which
   24px body no longer qualifies for. Every muted RUN OF TEXT therefore moves
   to --ink-300 #6a6a72, measured 5.36:1, which passes AA at any size.
   --ink-250 survives only where the type is 40px or larger (.t-lede-sm,
   .t-vision .mut, .t-big-mut) and on the .flow-arrow rule, which is a
   graphic at 3:1.

   OFF-RAMP, DELIBERATELY
   .chip > span (25) and .chip--sm > span (18) hold their sizes. Every pill
   width is baked into the markup as an inline px value; shrinking the text
   without shrinking the pill would leave the right padding half again as
   wide as the left. Chips need a width pass, not a type pass.
   ═══════════════════════════════════════════════════════════════════════ */

/* hero pair — dark slides */
.t-hero    {font-size:100px;line-height:96px;font-weight:600;letter-spacing:-.045em;color:#fff}
.t-hero-sub{font-size:48px; line-height:96px;font-weight:500;letter-spacing:-.045em;color:#fff}
/* cover */
.t-cover   {font-size:80px; line-height:96px;font-weight:600;letter-spacing:-.045em;color:#fff}
/* light-slide title */
.t-title   {font-size:48px; line-height:96px;font-weight:500;letter-spacing:-.045em;color:var(--dsg-blue)}
.t-h2      {font-size:48px; line-height:40px;font-weight:500;color:var(--ink-900)}
/* statement */
.t-claim   {font-size:40px; font-weight:500;letter-spacing:-.024em;color:var(--ink-800);line-height:normal}
.t-claim .s40{font-size:40px}
.t-claim .s72{font-size:72px}
.t-claim .s80{font-size:80px}
.t-claim b {font-weight:600}
/* body */
.t-subh    {font-size:32px; line-height:40px;font-weight:600;color:var(--ink-800)}
.t-body    {font-size:24px; line-height:32px;font-weight:500;color:var(--ink-600)}
.t-body--48{line-height:36px}
.t-body-mut{font-size:24px; line-height:32px;font-weight:500;color:var(--ink-300)}
.t-name    {font-size:32px; line-height:40px;font-weight:600;color:var(--ink-800)}
.t-lead    {font-size:32px; line-height:48px;font-weight:400;color:var(--ink-600)}
.t-lead b  {font-weight:600;color:var(--ink-800)}
.t-lead .mut{color:var(--ink-300)}
.t-deck    {font-size:32px; line-height:48px;font-weight:500;color:var(--ink-600)}
/* label / value / chip */
.t-label   {font-size:16px;line-height:20px;font-weight:500;letter-spacing:.1em;
            text-transform:uppercase;color:var(--ink-300)}
.t-value   {font-size:24px; line-height:32px;font-weight:500;color:var(--ink-600)}
.chip{background:#ffffff;box-shadow:0 0 0 1px var(--border-1);border-radius:6.25px;box-sizing:border-box}
.chip > span{position:absolute;left:28.1px;top:12.5px;font-size:25px;line-height:120%;
            font-weight:500;color:var(--ink-400);white-space:nowrap}
.chip--sm > span{font-size:18px;top:12.5px}
/* stats */
.t-statnum {font-size:48px; line-height:48px;font-weight:600;letter-spacing:-.042em;color:var(--ink-650)}
.t-statlab {font-size:20px; line-height:20px;font-weight:500;letter-spacing:-.013em;color:var(--ink-650)}
/* annotation */
.t-annot   {font-size:20px; line-height:28px;font-weight:500;color:var(--ink-400)}
.t-annot b.ink{font-weight:600;color:var(--ink-800)}
.t-annot-sm{font-size:18px; line-height:44px;font-weight:500;color:var(--ink-400)}
.t-tag     {font-size:20px; line-height:40px;font-weight:500;color:var(--ink-600)}
/* vision */
.t-known   {font-size:24px; font-weight:400;color:var(--ink-600);line-height:32px}
.t-big     {font-size:80px; line-height:96px;font-weight:500;letter-spacing:-.02em;color:var(--ink-500)}
.t-big-mut {font-size:80px; line-height:96px;font-weight:400;letter-spacing:-.02em;color:var(--ink-250)}
.t-vision  {font-size:40px; line-height:50px;font-weight:500}
.t-vision .mut{color:var(--ink-250)}
.t-vision .blue{font-weight:700;color:var(--dsg-blue-hi)}
.t-arrow   {font-size:110.07px;line-height:161.87px;font-weight:400;color:var(--ink-900)}
/* goal panel, slide 05 */
.t-goal-lab{font-size:24px; line-height:32px;font-weight:400;color:#fff}
.t-goal    {font-size:48px; font-weight:400;color:#fff}
.t-goal b  {font-weight:800}
/* the eyebrow pair on slide 03 */
.t-eyebrow-xl{font-size:80px;font-weight:500;color:var(--ink-800)}
.t-eyebrow-sm{font-size:24px;font-weight:400;color:var(--ink-600)}
/* the six principles, slide 11 — Montserrat in the source, not Schibsted */
.t-principle{font-family:'Montserrat',system-ui,sans-serif;
  font-size:26.604px;line-height:normal;font-weight:600;letter-spacing:20px;color:#fff}

/* Brittany Signature is not on Google Fonts. Nothing in the seventeen slides
   sets it, but the fallback is declared so a future line does not go to
   Times. */
.t-script{font-family:'Brittany Signature','Snell Roundhand','Apple Chancery',cursive}

/* Figma's UNORDERED list: bullet at +26, text at +47 from the node's own x,
   measured off the source so the wrap points match. */
.t-li{padding-left:47px}
.t-li b{font-weight:600;color:var(--ink-800)}
.t-li::before{content:"\2022";position:absolute;left:26px;top:0}
.t-strike{text-decoration:line-through;text-decoration-thickness:from-font}
/* Line 92 on slide 13: 1px, black at 60%, dash pattern 6,6 */
.dash{background:repeating-linear-gradient(to bottom,rgba(0,0,0,.6) 0 6px,transparent 6px 12px)}
/* Line 6/7/8 on slide 11: white hairlines at 20% */
.panel .rule{position:absolute;background:#ffffff;opacity:.2}

/* the one nested clip: slide 11's principles panel */
.panel{overflow:hidden;border-radius:20px}
.panel img{position:absolute}

/* ── Chrome: nav, reveals, counter, rail, overview, gate ─────────── */
/* ── The portfolio's navigation, inside the deck ─────────────────────────
   A talk should not be framed by chrome, so this is hidden until the
   presenter asks for it: any pointer move, any click, or a tab into it. It
   hides again after a few still seconds, unless the Work menu is open or
   focus is inside. Dark glass rather than the site's white bar, because it
   sits over the deck, and it is 56px tall so a scene's kicker at y=56 is the
   first thing clear of it once it goes.

   Links resolve one level up: the deck lives in dreamplay-concise/. */
#deckNav{position:fixed;top:0;left:0;right:0;z-index:60;
  transform:translateY(-100%);opacity:0;pointer-events:none;
  transition:transform 260ms var(--ease-decel),opacity 200ms linear}
#deckNav.on{transform:none;opacity:1;pointer-events:auto}
.dn-bar{display:flex;align-items:center;gap:var(--s5);height:56px;
  padding:0 var(--safe-x);
  background:rgba(11,6,18,.82);backdrop-filter:blur(18px);
  border-bottom:1px solid rgba(245,243,247,.12)}
.dn-logo{display:grid;place-items:center;width:30px;height:30px;flex:none;
  border-radius:7px;background:var(--indigo-on-dark);color:#fff;
  font-size:13px;font-weight:700;letter-spacing:.02em;text-decoration:none}
.dn-links{display:flex;align-items:center;gap:var(--s5);margin-left:auto}
/* The nav bar is dark glass regardless of the deck's own theme (it floats
   over whatever scene is showing), so its text uses dedicated on-dark-chrome
   inks rather than the light-theme --ink-2/--ink-1, which are tuned for the
   white ground and fail AA against this bar's near-black glass. */
.dn-link{position:relative;background:none;border:0;padding:6px 2px;cursor:pointer;
  display:inline-flex;align-items:center;gap:6px;
  font-family:var(--font-display);font-size:14px;font-weight:500;
  color:rgba(245,243,247,.82);text-decoration:none;
  transition:color 160ms linear}
.dn-link:hover,.dn-link:focus-visible{color:#ffffff}
.dn-link--back{color:#9db1ff}
.dn-drop{position:relative}
.dn-menu{position:absolute;top:calc(100% + 12px);right:0;min-width:248px;
  display:flex;flex-direction:column;padding:8px;
  background:rgba(21,14,31,.98);backdrop-filter:blur(18px);
  border:1px solid rgba(245,243,247,.14);border-radius:12px;
  box-shadow:0 18px 44px rgba(0,0,0,.55)}
.dn-menu[hidden]{display:none}
.dn-menu a{padding:9px 12px;border-radius:8px;
  font-size:14px;color:rgba(245,243,247,.82);text-decoration:none;white-space:nowrap;
  transition:background 140ms linear,color 140ms linear}
.dn-menu a:hover,.dn-menu a:focus-visible{background:rgba(245,243,247,.08);color:#ffffff}
.dn-rule{height:1px;margin:6px 8px;background:rgba(245,243,247,.14)}

/* ── Reveals. Explicit enter and exit, so stepping back replays. ────── */
.rv{opacity:0;transform:translateY(16px);
  transition:opacity 420ms var(--ease-decel),transform 420ms var(--ease-decel)}
.is-in .rv{opacity:1;transform:none}
.is-in .rv.held{opacity:0;transform:translateY(16px)}
.scene--payne .rv{transition:opacity 900ms var(--ease-in-out);transform:none}

/* ── Chrome ─────────────────────────────────────────────────────────── */
/* No plate behind either of these. The counter used to sit on a grey capsule,
   which read as a bar across the bottom of every slide; the numerals carry
   the same information without putting furniture on the page. */
#counter,#mute{position:fixed;bottom:var(--s5);z-index:40;padding:8px 14px;
  font:inherit;font-size:12px;letter-spacing:.1em;text-transform:uppercase;
  background:none;color:var(--dark-ink-3);border:0;
  text-shadow:none}
#counter{right:var(--s5);font-variant-numeric:tabular-nums}
#mute{right:calc(var(--s5) + 92px);cursor:pointer}
#counter,#mute{color:var(--ink-700)}
.on-payne #counter,.on-payne #mute{color:#ffffff}

#rail{position:fixed;right:var(--s5);top:50%;transform:translateY(-50%);z-index:40;
  display:flex;flex-direction:column;gap:0;padding:0}
#rail button{width:24px;height:24px;padding:9.5px;border:0;border-radius:50%;cursor:pointer;
  background-color:rgba(19,20,22,.30);background-clip:content-box;
  transition:background-color 300ms var(--ease-standard),transform 300ms var(--ease-standard)}
#rail button.on{background-color:var(--chartreuse);transform:scale(1.6)}
#rail button.chapter-start{margin-top:10px}

#overview{position:fixed;inset:0;z-index:60;background:rgba(0,0,0,.94);
  display:grid;place-items:center;padding:var(--s7)}
#overview[hidden]{display:none}
#ovGrid{display:grid;grid-template-columns:repeat(8,1fr);gap:var(--s4);max-width:1280px;width:100%}
#ovGrid button{aspect-ratio:16/10;border:2px solid transparent;border-radius:8px;
  background-size:cover;background-position:center;background-color:#2E2240;
  cursor:pointer;color:rgba(245,243,247,.85);font-size:12px;letter-spacing:.06em;display:grid;
  place-items:end center;padding:5px;text-transform:uppercase;overflow:hidden}
#ovGrid button[aria-current="true"]{border-color:var(--chartreuse)}

#gate{position:fixed;inset:0;z-index:80;display:grid;place-items:center;
  background:var(--dark-ground);color:var(--dark-ink-1);cursor:pointer;transition:opacity 600ms var(--ease-in-out)}
#gate.gone{opacity:0;pointer-events:none}
#gate .k{font-size:72px;font-weight:600;letter-spacing:-.03em;line-height:1;margin:0}
#gate .h{margin-top:var(--s6);font-size:var(--stage-kicker);letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink-2)}

:focus-visible{outline:3px solid var(--chartreuse);outline-offset:3px;border-radius:4px}
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}
@media (prefers-reduced-motion:reduce){
  .rv{transition:opacity 1ms linear!important;transform:none!important}
  #rail button{transition:none}
}
@media (prefers-reduced-transparency:reduce){ .film__bg{display:none} }

/* The deck scales to any width: every slide is a fixed 1920x1080 canvas
   placed by a transform, so it renders correctly at any size and simply gets
   smaller. There is no width floor and no refusal screen; opening the project
   from anywhere drops you straight into the presentation. */

/* ═══ Revisions ══════════════════════════════════════════════════════════
   1 · Grounds run to the viewport edge, not to the canvas edge. The wash on
       each slide is a scene layer behind the canvas, so the letterbox above
       and below a 16:9 canvas carries the artwork instead of flat paper.  */
.scene-bg{position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;object-position:center;z-index:0;pointer-events:none}
.slide{z-index:1}

/* 2 · The start gate is gone: the deck opens in presentation mode. */
#gate{display:none!important}

/* 3 · Slide 05 — the bullet hangs outside the measure so the text itself
       starts on the same x as the heading above it. */
/* The bullet sits on the column spine, level with the heading above it,
   and the text runs on from there. */
.t-li--hang{padding-left:34px}
.t-li--hang::before{left:0}

/* 4 · Slide 06 — two weights of statement, the second on a press. */
.t-lede-sm{font-size:40px;line-height:56px;font-weight:500;color:var(--ink-250);
  letter-spacing:-.01em}
.t-lede-xl{font-size:80px;line-height:96px;font-weight:500;color:var(--ink-650);
  letter-spacing:-.03em}
.t-lede-xl b{font-weight:700;color:var(--dsg-blue)}

/* 6 · Slides 13 + 14, now one scene. Press one: the old page and the rule
       leave, and the two live frames travel left and grow into the detail
       layout. Press two: the chips arrive beside them. The flag is an
       invisible .rv the engine already toggles, so no engine change. */
.xf-flag{position:absolute;left:0;top:0;width:1px;height:1px;opacity:0!important}
.xf-web,.xf-app{transition:left 620ms var(--ease-decel),top 620ms var(--ease-decel),
  border-radius 620ms var(--ease-decel),
  width 620ms var(--ease-decel),height 620ms var(--ease-decel),
  opacity 380ms var(--ease-decel),transform 620ms var(--ease-decel)}
.xf-web{left:793px;top:276px;width:771px;height:487px;border-radius:28px}
.xf-app{left:1595px;top:276px;width:246px;height:487px;border-radius:20px}
.scene[data-xf="on"] .xf-web{left:100px;top:276px;width:981px;height:620px;border-radius:40px}
.scene[data-xf="on"] .xf-app{left:1108px;top:276px;width:312px;height:617px;border-radius:28px}
.scene[data-xf="on"] .xf-gone{opacity:0;transform:translateX(-32px)}

/* 7 · Video frames sit exactly where their still sat. */
.slide video{display:block;object-fit:cover;object-position:top center;
  background:#0b0d10}

/* ═══ Round two ══════════════════════════════════════════════════════════
   Frames carry the radius and the hairline the Figma nodes carry. Radius
   is per-node, so it is a modifier rather than one blanket value.        */
.fr{overflow:hidden;object-fit:cover;object-position:top center}
.fr--20{border-radius:20px}
.fr--28{border-radius:28px}
.fr--edge{box-shadow:0 0 0 1px #d4d4d4}




/* Slide 09. The old positioning is struck through on the click that brings in
   the new one, so the rule is a drawn line that can travel rather than a
   text-decoration that can only be present or absent. */
.strike-line{background-image:linear-gradient(currentColor,currentColor);
  background-repeat:no-repeat;background-position:0 .56em;background-size:0 4px;
  transition:background-size 520ms var(--ease-decel)}
.strike-line--2{transition-delay:140ms}
.scene[data-xf="on"] .strike-line{background-size:100% 4px}

/* The exported phone still is the whole device, not a cut-out, so the live
   screen sits on top of it and the bezel shows around the video. */
.fr--46{border-radius:46px;overflow:hidden;object-fit:cover;object-position:top center}

/* Off screen the slide holds its start state with no animation, so resetting
   after the presenter leaves is instant and stepping back in snaps to the
   finished state rather than replaying the move backwards. */
.scene:not(.is-in) .xf-web,
.scene:not(.is-in) .xf-app,
.scene:not(.is-in) .xf-gone{transition:none}

/* Slide 11. Six cards, one treatment, nothing else. The three priorities keep
   their qualifier in a lighter tone so the meaning survives without the slide
   splitting into two families. Blue is spent on the title alone. */
.s11 .p11-card{background:#ffffff;border:1px solid var(--border-1);
  border-radius:20px;box-sizing:border-box}

/* Card title steps down so the reason underneath can carry weight. Title in
   the deck ink, the reason a tone quieter, both in the deck face. */
.s11 .p11-name{font-size:32px;line-height:40px;font-weight:600;
  letter-spacing:-.012em;color:var(--ink-650)}
.s11 .p11-sub{font-size:24px;line-height:32px;font-weight:400;
  letter-spacing:-.004em;color:var(--ink-300)}

/* Slide 10: the frosted plate the "Why this works" column sits on. White at
   80% over a background blur, and only the bottom corners are rounded, so it
   reads as a panel hung from the top of the slide. */
.s10-plate{background:rgba(255,255,255,.8);border-radius:0 0 12px 12px;
  -webkit-backdrop-filter:blur(40px);backdrop-filter:blur(40px)}
.fr--7{border-radius:7.35px;overflow:hidden;object-fit:cover;object-position:top center;
  box-shadow:0 0 0 1px rgba(128,128,128,.55)}

/* ── The app sequence: slide 16 and 17 of the file as one stepped scene ──
   Entry shows the copy, the list and the phone centred. One press brings the
   chips. A second press sends the phone to the left, takes the copy and chips
   away, and brings in the widget row. Geometry is the file's, both states. */
.s16 .s16-frame,.s16 .s16-vid,.s16 .s16-out{
  transition:left 640ms var(--ease-decel),top 640ms var(--ease-decel),
    width 640ms var(--ease-decel),height 640ms var(--ease-decel),
    border-radius 640ms var(--ease-decel),opacity 380ms var(--ease-decel)}
.s16 .s16-frame{left:742px;top:129px;width:437px;height:886px}
.s16 .s16-vid{left:764.5px;top:146px;width:393px;height:851px;border-radius:48.78px;
  overflow:hidden;object-fit:cover;object-position:top center}
.scene[data-xf="on"] .s16-frame{left:102px;top:233px;width:381px;height:772px}
.scene[data-xf="on"] .s16-vid{left:121px;top:248px;width:343px;height:742px;border-radius:42.53px}
.scene[data-xf="on"] .s16-out{opacity:0}
.scene:not(.is-in) .s16-frame,.scene:not(.is-in) .s16-vid,.scene:not(.is-in) .s16-out{transition:none}
.fr--16{border-radius:16px;overflow:hidden;object-fit:cover;object-position:top center}

/* ── Impact: the block that closes a slide on results ────────────────────
   The old version was a tint-filled rounded box with hand-picked padding and
   a hard-set label height. Three things were wrong with it and all three are
   fixed here.

   NO FILL, A RULE INSTEAD.
   On the home page slide this block lands directly under a column of seven
   --tint-10 chips. A --tint-10 box under seven --tint-10 chips reads as an
   eighth chip: the results become one more attribute in the list instead of
   the thing the list was building towards. The fill is gone. What separates
   the block is a --border-1 hairline across its full measure, the same
   hairline the deck already uses on .s11 .p11-card and .dash. Attributes are
   filled objects; the payoff is typography on the open ground. That also
   frees the block to sit flush on x=1476, so its rule and its type start on
   the exact vertical the seven chip edges above it already draw. With the
   fill gone there is no inset to guess at: side padding is zero because
   there is no longer an edge to hold off.

   ONE 8px UNIT, AND EVERY LEADING IS A MULTIPLE OF IT.
   Nothing here is a free number. The unit is 8 (--impact-unit) and every
   vertical measure is 1u, 2u, 3u or 6u of it, so the block sits on a real
   baseline grid rather than on four unrelated guesses:
     rule to eyebrow ....... 2u = 16
     eyebrow line box ...... 3u = 24   (16px type, the canvas floor)
     eyebrow to metric ..... 3u = 24
     value line box ........ 6u = 48   (40px type)
     label line box ........ 3u = 24   (16px type, 1.5 leading, per line)
     metric to metric ...... 3u = 24
   The eyebrow gets the same 3u below it that separates one metric from the
   next, so the eyebrow is a row in the same rhythm rather than a floating
   caption. Inside a metric the gap is 0: the two line boxes touch, which
   half-leading turns into an 8px optical gap, exactly 1u. So the gap that
   binds a value to its label is 1u and the gap that separates one metric
   from the next is 3u, a 4:1 optical step. Grouping is unambiguous without
   spending a single rule or divider on it.

   THE TYPE IS PULLED OFF THE DECK'S OWN RAMP.
   Value 40px/600 at -.024em is .t-claim's size and tracking. That is the one
   step that is larger than every piece of body copy on both slides (32px)
   and still clearly below .t-title (48px), so the figure wins its column
   without picking a fight with the slide title. It keeps the blue, so it is
   where the eye lands. Label 16px --ink-400, eyebrow 16px --ink-300 uppercase
   at .1em to match .t-label. 16px is the canvas floor: at the 1440 presenting
   width (k = 0.75) it renders at 12px.

   IT SIZES ITSELF.
   Container height is auto and the children are in normal flow, so a label
   may wrap to one, two or three lines and the block simply grows downwards
   on the same 3u step. Nothing clips, and no instance needs a hand-set
   height. Only left/top/width are placed on the canvas. */
.impact{background:var(--tint-10);border-radius:20px;box-sizing:border-box;padding:24px 28px;
  display:flex;flex-direction:column;gap:20px}
.impact p{margin:0}
.impact-eyebrow{font-size:16px;line-height:24px;font-weight:600;
  letter-spacing:.1em;text-transform:uppercase;color:var(--ink-400)}
/* The pair is its own flex column with no gap, so value and label are bound
   by their own half-leading and never by a number typed in here. */
.impact-metric{display:flex;flex-direction:column;gap:6px}
.impact-val{font-size:32px;line-height:40px;font-weight:600;
  letter-spacing:-.024em;color:var(--ink-650);white-space:nowrap}
.impact-lab{font-size:16px;line-height:24px;font-weight:400;
  color:var(--ink-400);text-wrap:pretty}

/* The widget row waits for the phone to finish travelling. app.js writes
   transition-delay inline on every .rv, so this has to be !important to sit
   on top of it. 640ms is the phone's journey, plus a beat. */
.s16 .s17-in{transition-delay:700ms !important}
/* Step four: the widget row retires and the profile row takes the right side. */
.s16 .s18-in{transition-delay:120ms !important}
.scene[data-xf2="on"] .s17-out{opacity:0;transition-delay:0ms !important}

/* The points screen is the one export without a baked drop shadow, so it gets
   the same lift in CSS. drop-shadow, not box-shadow, so it follows the alpha
   of the rounded screen rather than boxing it. */
.fr--24{border-radius:24px;overflow:hidden;object-fit:cover;object-position:top center}

/* ── App screens: the file's own shadow and hairline, not a baked one ─────
   Exports are cropped back to each frame's box so no shadow is burned into
   the pixels; the lift and the 1px #808080 at 55% come from here instead,
   which keeps them crisp and lets both be corrected in one place. */
.app-screen,.phone-screen{box-sizing:border-box;overflow:hidden;
  object-fit:cover;object-position:top center;
  box-shadow:0 0 0 1px rgba(128,128,128,.55),0 25px 50px -12px rgba(0,0,0,.10)}
.app-screen{border-radius:20px}
.phone-screen{border-radius:42.53px}
.fr--35{border-radius:35px;overflow:hidden;object-fit:cover;object-position:top center}

/* ── Holidays media: frame box plus the file's own shadow ─────────────── */
.media{box-sizing:border-box;overflow:hidden;object-fit:cover;object-position:top center;
  box-shadow:0 0 0 1px rgba(128,128,128,.55),0 25px 50px -12px rgba(0,0,0,.10)}
.media--24{border-radius:24px}
.media--35{border-radius:35px}

/* ── The flow: four steps with a drawn arrow between, on one baseline ─── */
.flow{display:flex;align-items:center;gap:20px}
.flow-step{font-size:24px;line-height:32px;font-weight:500;
  letter-spacing:-.008em;color:var(--ink-600);white-space:nowrap}
.flow-arrow{flex:none;width:34px;height:8px;position:relative;
  background:linear-gradient(var(--ink-250),var(--ink-250)) left center/26px 1.5px no-repeat}
.flow-arrow::after{content:"";position:absolute;right:0;top:50%;
  width:8px;height:8px;border-top:1.5px solid var(--ink-250);
  border-right:1.5px solid var(--ink-250);transform:translateY(-50%) rotate(45deg)}

/* Wide placements lay the metrics in a row, which is what lets the block sit
   low enough to bottom-align with a tall frame beside it. */
.impact--row{gap:18px}
.impact--row .impact-rows{display:flex;gap:40px}
.impact--row .impact-metric{flex:1 1 0;min-width:0}
.impact-lab{white-space:normal}

/* The points screen is a live window onto the full page, so it can be
   scrolled by hand while presenting. The wheel is taken off the deck engine
   for this element only, in the inline script, so scrolling it does not
   advance the slide. */
.scroller{overflow-y:auto;overflow-x:hidden;-webkit-overflow-scrolling:touch;
  scrollbar-width:none}
.scroller::-webkit-scrollbar{display:none}
.scroller img{display:block;width:100%;height:auto}
/* The menu and search panels carry the same lift as the app screens, at the
   radius the file gives them. */
.app-screen--panel{border-radius:18.4px}

/* ═══ Slides 19 to 21 ════════════════════════════════════════════════════
   Three additions: the chapter break into detail, the holidays product
   page, and partial payments. Two components are new, both scoped. */

/* 20 · The phone's own chrome, laid over the recording. The footer is
   sticky in the product, so it must not scroll with the video; the points
   pill sits 10px above it, states its case, and leaves. The recording is
   held still underneath until the pill has gone, otherwise two things
   move at once and neither reads. */
.s22-footer{border-bottom-left-radius:24px;border-bottom-right-radius:24px}

/* Both overlays are chrome painted over the recording, so they must not
   swallow the pointer: the clip keeps its own controls, and the footer band
   is exactly where the native control bar sits. On hover the video lifts
   above them so the scrubber is reachable and visible, then drops back. */
.s22-footer,.s22-pill{pointer-events:none;z-index:2}
.s22-mob{z-index:1}
.s22-mob:hover,.s22-mob:focus,.s22-mob:focus-visible{z-index:3}

/* The pill nudges, waits, nudges again, and is gone. It pivots about its own
   centre by a degree and a half and settles; there is no rise and no
   collapse, so nothing about its arrival or its exit competes with the
   recording underneath. The second swing of each pair is damped to one
   degree, which keeps it a nudge rather than a shake.
     0 to  4%   120ms  fade in, no movement
     4 to 20%   480ms  nudge, nudge
    20 to 53%   990ms  hold
    53 to 69%   480ms  nudge, nudge
    69 to 88%   570ms  hold
    88 to 100%  360ms  shrink gently and go                              */
.s22-pill{opacity:0;transform-origin:50% 50%;will-change:opacity,transform;
  border-radius:6px;box-shadow:0 8px 16px -4px rgba(0,0,0,.10)}
.s22-pill.run{animation:s22-pill 3s ease-in-out .3s both}
@keyframes s22-pill{
  0%   {opacity:0;transform:none}
  4%   {opacity:1;transform:none}
  6%   {transform:rotate(1.5deg)}
  10%  {transform:rotate(-1.5deg)}
  12%  {transform:rotate(0deg)}
  14%  {transform:rotate(1deg)}
  18%  {transform:rotate(-1deg)}
  20%  {transform:none}
  53%  {opacity:1;transform:none}
  55%  {transform:rotate(1.5deg)}
  59%  {transform:rotate(-1.5deg)}
  61%  {transform:rotate(0deg)}
  63%  {transform:rotate(1deg)}
  67%  {transform:rotate(-1deg)}
  69%  {transform:none}
  88%  {opacity:1;transform:none}
  100% {opacity:0;transform:scale(.86)}
}
@media (prefers-reduced-motion:reduce){
  .s22-pill.run{animation:none;opacity:1}
}

/* A learning is not a metric: it earns width, not scale. Same tint and
   radius as .impact so the two read as one family on the slide, but the
   eyebrow carries the difference. */
.learn{background:var(--tint-10);border-radius:20px;box-sizing:border-box;
  padding:24px 28px;display:flex;flex-direction:column;gap:10px}
.learn-eyebrow{margin:0;display:flex;align-items:center;gap:8px;
  font-size:16px;line-height:24px;font-weight:600;
  letter-spacing:.08em;text-transform:uppercase;color:var(--ink-400)}
.learn-icon{width:16px;height:16px;flex:none;color:#B45309}
.learn-body{margin:0;font-size:20px;line-height:30px;font-weight:400;color:var(--ink-400)}

/* 21 · The rule that splits booking from post booking runs the full slide,
   so it takes the dash pattern the deck already uses rather than a border. */
.s23-rule{opacity:.45}
.s23-note{line-height:30px}

/* The overlaid footer is page chrome: when a popup owns the screen in the
   recording, the real footer is gone and this one must go with it. */
.s22-footer{transition:opacity 140ms linear}
.s22-footer.is-off{opacity:0}

/* Slide 21's payment page pins its header, so the header rides above the
   scroll rather than leaving with it. It is cropped from the same export as
   the body, so the two are pixel identical where they meet at the top, and it
   takes the frame's top corners and stays out of the pointer's way. */
.s23-sticky{border-radius:20px 20px 0 0;pointer-events:none}
.s23-sticky--btm{border-radius:0 0 20px 20px}

/* A scene being entered lands in its state rather than animating into it.
   Held for one frame while the mirrored flag is written, then released so
   stepping through the scene animates as normal. */
.scene.xf-snap .xf-web,.scene.xf-snap .xf-app,.scene.xf-snap .xf-gone,
.scene.xf-snap .s16-frame,.scene.xf-snap .s16-vid,.scene.xf-snap .s16-out,
.scene.xf-snap .strike-line{transition:none !important}

/* ═══ Slides 23 and 24 ═══════════════════════════════════════════════════
   The Scout pair, built entirely from existing roles. The one addition is a
   guard: this journey row carries seven steps where slide 17's carries four,
   and flex items shrink by default. Without flex:none an overlong row would
   ride the words over the arrows rather than run past its box. */
.s25 .flow-step{flex:none}

/* Both Scout stills are 1:2 phone frames sized to an exact 0.5 box, so
   neither is stretched. .app-screen sets no object-fit and carries a 1px
   border with box-sizing:border-box, so a single rounded pixel would reach
   the picture as a stretch. .media already covers; this gives the entry
   still the same protection. */
.s25 .s25-entry{object-position:top center}

/* 23 · The journey is the thesis on this slide, so it carries more weight
   than the four step row on slide 17. The footer names the stack once and
   gets out. */
.s25-stack{color:var(--ink-300)}

/* The journey is one object, so it sits in one container rather than floating
   as seven loose words on the page ground. */
.flow-panel{background:#f5f5f7;border-radius:16px;padding:0 32px;
  justify-content:space-between;gap:16px}

/* Slide 29 captions are titles for the frames above them, so they take the
   body step rather than the deck step. All four frames share a 936px floor
   and every caption clears it by 48px. */

/* ═══ Scene change ═══════════════════════════════════════════════════════
   The engine owns vertical travel and cannot be touched, so the travel is
   made unreadable rather than removed. While the stage is moving, every
   scene's artwork and canvas drop to zero and only .scene's own flat --sbg
   ground is on screen; a full bleed flat colour has no edge, so nothing
   appears to arrive from below. On landing the incoming canvas resolves in
   place with a 1.2% settle. Out is fast at 120ms so the ground is reached
   before the bulk of the travel; in is slower at 300ms so it reads as a
   resolve. Opacity and transform only: both composite, and several of these
   slides carry a playing clip.
   Stepping WITHIN a held scene calls nudge(), which never scrolls, so the
   .rv reveals and their delays are untouched by any of this. */
.slide,.scene-bg{transition:opacity 300ms var(--ease-decel),
  transform 300ms var(--ease-decel)}
html.deck-travel .slide,html.deck-travel .scene-bg{
  opacity:0;transition-duration:120ms;transition-timing-function:var(--ease-in-out)}
html.deck-travel .slide{--settle:.988}

@media (prefers-reduced-motion:reduce){
  /* The engine jumps instead of tweening here, so there is nothing to cover. */
  .slide,.scene-bg{transition:none}
  html.deck-travel .slide,html.deck-travel .scene-bg{opacity:1;--settle:1}
}

/* ── Password gate ─────────────────────────────────────────────────────
   Black scrim at .8 opacity, white field, white text, orange error.
   #FF8A3D on the darkest ground this scrim can sit on (a white slide
   showing through at .8 black, #333) measures 5.39:1, AA for this size. */
#pwgate{position:fixed;inset:0;z-index:1000;display:grid;place-items:center;
  background:rgba(0,0,0,.8);padding:24px;box-sizing:border-box}
/* Rhythm: the 8px pairs read as attached, the 28px is the one real break
   between what you read and what you do. */
#pwgate-form{width:100%;max-width:360px;display:flex;flex-direction:column;
  align-items:center;text-align:center}
#pwgate-title{margin:0;font-size:32px;font-weight:600;color:#fff;letter-spacing:-.01em}
.pwgate-sub{margin:8px 0 0;font-size:16px;font-weight:400;color:#fff;opacity:.8}
#pwgate-input{margin-top:28px}
#pwgate-form button{margin-top:12px}
#pwgate-error{margin-top:8px}
/* The shake belongs on the field that was wrong, not the whole dialog. */
#pwgate-input.shake{animation:pwgate-shake 320ms cubic-bezier(.36,.07,.19,.97)}
@keyframes pwgate-shake{
  10%,90%{transform:translateX(-2px)} 20%,80%{transform:translateX(4px)}
  30%,50%,70%{transform:translateX(-8px)} 40%,60%{transform:translateX(8px)}}
#pwgate-input{width:100%;box-sizing:border-box;background:#fff;color:#141416;
  border:none;border-radius:8px;padding:14px 16px;font-size:16px;font-family:inherit}
#pwgate-input::placeholder{color:#6a6a72}
#pwgate-input:focus{outline:3px solid #fff;outline-offset:2px}
#pwgate-input.is-wrong{box-shadow:0 0 0 2px #FF8A3D}
/* The CTA sleeps until the field holds more than four characters, then wakes
   in the portfolio's own blue. White on #3548D4 is 6.93:1, AA. */
#pwgate-form button{width:100%;box-sizing:border-box;border:none;border-radius:8px;
  padding:14px 16px;font-size:16px;font-weight:600;font-family:inherit;
  background:#3A3A42;color:#A8A8B2;cursor:not-allowed;
  transition:background 160ms ease,color 160ms ease}
#pwgate-form button:not(:disabled){background:#3548D4;color:#fff;cursor:pointer}
#pwgate-form button:not(:disabled):hover{background:#2D3FBB}
#pwgate-form button:not(:disabled):active{background:#2838A5}
#pwgate-form button:focus-visible{outline:3px solid #fff;outline-offset:2px}
@media (prefers-reduced-motion:reduce){#pwgate-form button{transition:none}}
#pwgate-error{min-height:20px;font-size:14px;line-height:1.4;font-weight:600;color:#FF8A3D}
html.dsg-locked{overflow:hidden}
/* With scripting off nothing ever removes dsg-locked, so the gate holds and
   the scrim goes fully opaque rather than leaving the deck legible at 20%. */
/* Printing a locked page should yield the notice, not blank deck pages. */
@media print{
  html.dsg-locked #stage{display:none}
  html.dsg-locked #pwgate{position:static;background:none;color:#000}
  html.dsg-locked #pwgate-input,html.dsg-locked #pwgate-form button{display:none}
  html.dsg-locked #pwgate-title,html.dsg-locked .pwgate-sub{color:#000}
}
noscript #pwgate-noscript{position:fixed;inset:0;z-index:1002;background:#000;
  display:grid;place-items:center;color:#fff;font-size:16px;text-align:center;padding:24px}
/* Nothing of the deck sits behind the password scrim. Dimming the slides to
   20% still left them readable, and the wheel still walked through them. */
html.dsg-locked #counter,
html.dsg-locked #mute,
html.dsg-locked #rail,
html.dsg-locked #overview{display:none!important}
html.dsg-locked #stage{visibility:hidden}
/* The nav is the one piece that stays: it rides above the scrim rather than
   behind it, so someone without the password can still leave the page.
   app.js will not reveal it here on its own. Its show() bails while #gate is
   still in the document, and #gate only goes once the talk begins, which is
   now deferred until this screen clears. So the visible state is held here
   rather than waiting on a hover that can never arrive. */
html.dsg-locked #deckNav{z-index:1001;transform:none;opacity:1;pointer-events:auto}
@media (prefers-reduced-motion:reduce){
  #pwgate-input.shake{animation:none}
}
