/* ==========================================================================
   ABC (Proposed Company Name) — Board Confirmation Deck

   PALETTE — anodised aluminium.
   The product is a black-anodised 16mm frame with a champagne-anodised pull, so
   the deck is built from the same two finishes: a cool graphite ground and a
   warm platinum accent. Warm-on-cool is what stops the grey reading clinical.

   Colour discipline:
     --alu / --alu-d  chrome only — rules, eyebrows, emphasis, highlights.
                      Chroma 0.018 means it reads as metal, not as a hue, which
                      is exactly wrong for a data series and exactly right here.
     --s1 --s2 --s3   the data series. Re-validated against this surface
                      (#13171a): all-pairs CVD ΔE 9.4, normal-vision ΔE 20.9,
                      every slot >= 3:1 contrast. Do not substitute without
                      re-running scripts/validate_palette.js.
   ========================================================================== */

:root {
  --ink:        #0a0c0e;   /* cool near-black — outside the canvas          */
  --charcoal:   #13171a;   /* graphite — the slide surface                  */
  --charcoal-2: #1b2126;   /* raised panel                                  */
  --charcoal-3: #242b31;   /* tooltip / deepest raised                      */
  --paper:      #eef1f3;
  --text:       #e9edef;
  --text-2:     #99a2a9;
  /* lifted from 6a737a — the source line sets at 11.5px and needs 4.5:1,
     which 6a737a missed on panels (3.36). This clears it on both surfaces. */
  --text-3:     #828b92;

  /* champagne-anodised aluminium — the accent, chrome only */
  --alu:        #cfc9bd;
  --alu-d:      #9a9488;
  /* kept as aliases so existing rules keep working */
  --champagne:  #cfc9bd;
  --champagne-d:#9a9488;

  --hair:       rgba(233,237,239,.12);
  --hair-2:     rgba(233,237,239,.06);
  --sheen:      rgba(255,255,255,.055);

  --s1: #3987e5;   /* ตลาดบน / premium      */
  --s2: #d95926;   /* ตลาดแมส / mass        */
  --s3: #199e70;   /* Window Asia / เรา     */

  --good: #199e70;
  --bad:  #e66767;

  --font: "Leelawadee UI","Noto Sans Thai","Segoe UI Variable Text","Segoe UI",
          -apple-system,"Helvetica Neue",Arial,sans-serif;
  --mono: "Cascadia Mono","Consolas",ui-monospace,monospace;

  /* ---- type scale -------------------------------------------------------
     Every font-size in the deck resolves to one of these. Sized for a board
     reading at distance, so the floor matters more than the ceiling: the
     smallest token is 15px, not the 12.5px this deck started with.

     These are canvas pixels — the whole 1600x900 stage is transform-scaled to
     fit, so they are only 1:1 on a 1600px-wide viewport. Below the 900px
     breakpoint the scaling is switched off and these are redeclared at real
     device sizes; see the media query at the end of this file. Keeping every
     size behind a token is what makes that override a single block. */
  --fs-display-xl: 72px;   /* .display--lg — slide 01 only          */
  --fs-display:    52px;   /* .display — every slide heading        */
  --fs-kpi:        46px;   /* .kpi b — the big figures              */
  --fs-xl:         26px;   /* showroom kicker, flow arrow, initials */
  --fs-lede:       23px;   /* .lede — the sentence under a heading  */
  --fs-lg:         21px;   /* tables, org position titles           */
  --fs-body:     19.5px;   /* running copy                          */
  --fs-sm:         18px;
  --fs-xs:         17px;
  --fs-2xs:        16px;   /* .src footer                           */
  --fs-3xs:        15px;   /* .tag — the tightest slot in the deck  */
}

* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; overflow: hidden; background: var(--ink); }
body {
  font-family: var(--font); color: var(--text);
  -webkit-font-smoothing: antialiased;
  /* Thai has no word spaces — tracking would separate the marks of one
     syllable, so it is zero everywhere and the leading carries the rhythm. */
  letter-spacing: 0;
}

/* ---- stage ---------------------------------------------------------------- */
#stage { position: fixed; inset: 0; display: grid; place-items: center; }
#canvas {
  position: absolute; top: 50%; left: 50%;
  width: 1600px; height: 900px;
  transform-origin: center center;
}
.slide {
  position: absolute; inset: 0; isolation: isolate;
  padding: 62px 74px 78px;
  display: flex; flex-direction: column;
  background: var(--charcoal);
  opacity: 0;
  /* display:none would block the transition below from ever playing (a
     hidden→visible jump has no prior frame to interpolate from), so
     visibility carries the show/hide instead — switching instantly on
     arrival, but only after the fade-out finishes on the way out. */
  visibility: hidden;
  /* A focus-pull arrival: soft-blurred and slightly lifted/zoomed, settling
     into place — a quieter cousin of a Ken Burns move, not a bounce. */
  transform: scale(1.045) translateY(16px);
  filter: blur(9px);
  transition: opacity .72s cubic-bezier(.22,.61,.36,1),
              transform .85s cubic-bezier(.22,.61,.36,1),
              filter .68s cubic-bezier(.22,.61,.36,1),
              visibility 0s linear .85s;
}
.slide.is-active {
  visibility: visible;
  opacity: 1;
  transform: scale(1) translateY(0);
  filter: blur(0px);
  transition: opacity .72s cubic-bezier(.22,.61,.36,1),
              transform .85s cubic-bezier(.22,.61,.36,1),
              filter .68s cubic-bezier(.22,.61,.36,1),
              visibility 0s linear 0s;
}
/* A faint interior photograph sits behind every slide — the product in use,
   reduced to line. The plate is duotoned to the deck's two finishes before it
   gets here, so this layer only sets how far forward it comes and keeps it away
   from the reading side. */
.slide__ground {
  position: absolute; inset: 0; overflow: hidden; pointer-events: none;
  z-index: 0;
}
/* An absolutely-positioned child paints above its static siblings, so once the
   ground carries an opaque wash it would cover the slide. Lifting the content
   one layer puts it back on top. */
.slide > *:not(.slide__ground) { position: relative; z-index: 1; }
/* …except the source line, which is pinned to the slide's bottom edge and must
   keep its own absolute positioning. */
.slide > .src { position: absolute; }
.slide__ground img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; opacity: .16;
}
/* the left two-thirds carry the headline and the tables, so the plate is
   washed out there and allowed to show on the right */
.slide__ground::after {
  content: ""; position: absolute; inset: 0;
  background:
    linear-gradient(100deg, var(--charcoal) 0%, rgba(19,23,26,.96) 28%,
                    rgba(19,23,26,.80) 56%, rgba(19,23,26,.55) 100%),
    radial-gradient(72% 62% at 88% 10%, rgba(207,201,189,.075) 0%, transparent 70%);
}

/* ---- rising animation ----------------------------------------------------- */
.rise { opacity: 0; transform: translateY(14px); }
.is-active .rise {
  opacity: 1; transform: none;
  transition: opacity .62s cubic-bezier(.22,.61,.36,1),
              transform .62s cubic-bezier(.22,.61,.36,1);
}
.is-active .d1 { transition-delay: .07s }
.is-active .d2 { transition-delay: .14s }
.is-active .d3 { transition-delay: .21s }
.is-active .d4 { transition-delay: .28s }
.is-active .d5 { transition-delay: .35s }
.is-active .d6 { transition-delay: .42s }

/* ---- typography ----------------------------------------------------------- */
.eyebrow {
  font-size: var(--fs-xs); font-weight: 700; color: var(--champagne-d);
  text-transform: uppercase; letter-spacing: .18em;
}
.display {
  font-size: var(--fs-display); font-weight: 600; line-height: 1.22; letter-spacing: -.005em;
}
.display--lg { font-size: var(--fs-display-xl); font-weight: 300; line-height: 1.14; }
.display__num {
  color: var(--champagne-d); font-weight: 300; margin-right: .42em;
}
.lede { font-size: var(--fs-lede); color: var(--text-2); line-height: 1.65; max-width: 92ch; }
.slide-head { margin-bottom: 26px; }
.slide-head .lede { margin-top: 10px; }

.hr { height: 1px; background: var(--hair); border: 0; }

/* ---- layout helpers ------------------------------------------------------- */
.row { display: flex; gap: 26px; }
.row.between { justify-content: space-between; }
.row.center  { align-items: center; }
.col { display: flex; flex-direction: column; min-width: 0; }
/* The fixed-width secondary column on slides 05 and 07. A class rather than an
   inline width so the mobile breakpoint can drop it without !important. */
.col--side { flex: 0 0 500px; }

/* Slide 01 runs wider than the rest: its strapline is one long unbroken line
   and its ownership table has a free-text column that was wrapping. Pulling
   the gutters in buys ~44px on each side for both. */
.slide[data-n="01"] { padding-left: 48px; padding-right: 48px; }
.slide[data-n="01"] > .src { left: 48px; right: 48px; }
/* 21.5px rather than the 23px --fs-lede: at 23 the line overshoots the canvas
   and wraps, which is the one thing this line must not do. Sized to leave
   ~40px spare so a different Thai font metric on another machine cannot tip
   it over — at this size the drop from 23 is not perceptible. */
.slide[data-n="01"] .lede { font-size: 21.5px; }
.grow { flex: 1; min-height: 0; }
.body { flex: 1; display: flex; flex-direction: column; min-height: 0; }
/* .body sets column and is declared after .row, so a slide that wants two
   columns must say so explicitly or the panels stack on top of the chart. */
.body.row { flex-direction: row; }

/* ---- panels --------------------------------------------------------------- */
.panel {
  background: var(--charcoal-2); border: 1px solid var(--hair);
  border-radius: 12px; padding: 22px 24px;
  /* a one-pixel highlight along the top edge — the way light catches a
     machined aluminium edge. Enough to read as metal, not enough to notice. */
  box-shadow: inset 0 1px 0 var(--sheen);
}
.panel--quiet { background: transparent; }
.panel__t {
  font-size: var(--fs-xs); font-weight: 700; letter-spacing: .16em;
  text-transform: uppercase; color: var(--text-3); margin-bottom: 12px;
}

/* ---- KPI ------------------------------------------------------------------ */
.kpis { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; }
.kpi {
  background: var(--charcoal-2); border: 1px solid var(--hair);
  border-radius: 12px; padding: 20px 22px;
  box-shadow: inset 0 1px 0 var(--sheen);
}
.kpi b {
  display: block; font-size: var(--fs-kpi); font-weight: 300; letter-spacing: -.02em;
  line-height: 1.05;
}
/* the label is the KPI's own child span; the unit sits inside <b> and must
   stay on the same line as the number, so scope the block rule to the child */
.kpi > span {
  display: block; margin-top: 9px; font-size: var(--fs-xs); font-weight: 600;
  color: var(--text-3); text-transform: uppercase; letter-spacing: .14em;
}
.kpi b .u {
  display: inline; font-size: var(--fs-lede); font-weight: 500;
  color: var(--text-2); margin-left: 6px;
}
.kpi em { font-style: normal; color: var(--champagne); }

/* ---- tables --------------------------------------------------------------- */
table { width: 100%; border-collapse: collapse; font-size: var(--fs-lg); }
th {
  text-align: left; font-size: var(--fs-2xs); font-weight: 700; color: var(--text-3);
  text-transform: uppercase; letter-spacing: .13em;
  padding: 0 12px 10px; border-bottom: 1px solid var(--hair);
}
td { padding: 11px 12px; border-bottom: 1px solid var(--hair-2); vertical-align: middle; }
tr:last-child td { border-bottom: 0; }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
tr.hl td { background: rgba(201,168,124,.07); }
tr.hl td:first-child { box-shadow: inset 3px 0 0 var(--champagne); }
.pos { color: var(--good); font-weight: 600; }
.neg { color: var(--bad);  font-weight: 600; }
.dim { color: var(--text-3); }


/* ---- dots that carry series identity beside a label ----------------------- */
.dot { display:inline-block; width:9px; height:9px; border-radius:50%; margin-right:8px;
       vertical-align: middle; }
.dot--1 { background: var(--s1) } .dot--2 { background: var(--s2) } .dot--3 { background: var(--s3) }

.legend { display: flex; gap: 22px; align-items: center; font-size: var(--fs-sm); color: var(--text-2); }

/* ---- name chip — a small pill for a list of names, deliberately distinct from
   the Evidence-level .tag so a board member never reads "verified name" as
   "verified source" ---- */
.chip {
  display: inline-block; margin: 3px 6px 3px 0; padding: 5px 12px;
  font-size: var(--fs-xs); color: var(--text-2); background: var(--charcoal-3);
  border: 1px solid var(--hair); border-radius: 20px;
}
.chip--dim { color: var(--text-3); border-style: dashed; }
a.chip {
  text-decoration: none; cursor: pointer;
  transition: border-color .15s ease, color .15s ease, background .15s ease;
}
a.chip:hover { border-color: var(--champagne-d); color: var(--text); background: var(--charcoal-2); }

/* Tinted chip groups. Each customer segment on slide 03 already owns a colour
   in the charts below it, so the supplier lists borrow the same one — the eye
   ties list to segment without a legend. Tints only: a saturated fill at this
   size would shout louder than the figures it sits next to. */
.chip--s1 { color: #9fc4f2; border-color: rgba(57,135,229,.5);  background: rgba(57,135,229,.12); }
.chip--s2 { color: #f0a98c; border-color: rgba(217,89,38,.5);   background: rgba(217,89,38,.12); }
.chip--s3 { color: #7fd3b4; border-color: rgba(25,158,112,.5);  background: rgba(25,158,112,.12); }
.chip--al { color: var(--alu); border-color: rgba(201,168,124,.5); background: rgba(201,168,124,.12); }
a.chip--s1:hover { border-color: var(--s1); background: rgba(57,135,229,.22); color: #cfe2fa; }
a.chip--s2:hover { border-color: var(--s2); background: rgba(217,89,38,.22);  color: #f8cdb9; }
a.chip--s3:hover { border-color: var(--s3); background: rgba(25,158,112,.22); color: #a9e5cd; }
a.chip--al:hover { border-color: var(--alu); background: rgba(201,168,124,.22); color: #eae4d8; }

/* ---- inline mini-bar — a name + a bar + a number, for a small scale
   comparison that doesn't earn a full chart ---- */
.mbar { display: flex; align-items: center; gap: 10px; font-size: var(--fs-sm); }
.mbar__n { flex: 0 0 92px; color: var(--text-2); }
.mbar__track { flex: 1; height: 8px; background: var(--hair-2); border-radius: 4px; overflow: hidden; }
.mbar__fill { height: 100%; background: var(--s1); border-radius: 4px; }
.mbar__v { flex: 0 0 56px; text-align: right; color: var(--text-3); font-variant-numeric: tabular-nums; }

/* ---- chart ---------------------------------------------------------------- */
/* An <svg> with a viewBox and width:100% derives its height from its width, so
   inside a flex row it grows without limit and rides over its neighbours. Both
   charts therefore get a fixed height and letterbox inside it. */
.chart { position: relative; display: flex; flex-direction: column; min-width: 0; }
.chart svg { display: block; overflow: visible; width: 100%; }
#chart-revenue svg     { height: 210px; }
.ax    { stroke: var(--hair); stroke-width: 1; }
.grid  { stroke: var(--hair-2); stroke-width: 1; }
.axlbl { fill: var(--text-3); font-size: var(--fs-xs); }
.ptlbl { fill: var(--text); font-size: var(--fs-sm); font-weight: 600; }
.ptsub { fill: var(--text-3); font-size: var(--fs-2xs); }
.mark  { stroke: var(--charcoal); stroke-width: 2; cursor: default; }
.mark:hover { stroke: var(--text); }
.zoneline { stroke: var(--champagne-d); stroke-width: 1; stroke-dasharray: 3 4; opacity: .55; }
.zonelbl  { fill: var(--champagne-d); font-size: var(--fs-2xs); letter-spacing: .1em;
            text-transform: uppercase; font-weight: 700; }

#tip {
  position: fixed; z-index: 70; pointer-events: none; opacity: 0;
  background: var(--charcoal-3); border: 1px solid var(--hair);
  border-radius: 8px; padding: 10px 13px; font-size: 15.5px; line-height: 1.5;
  box-shadow: 0 18px 40px -22px #000; transition: opacity .12s; max-width: 260px;
}
#tip b { display: block; margin-bottom: 4px; }
#tip .r { color: var(--text-2); font-variant-numeric: tabular-nums; }

/* data table under a chart — the accessible view of the same numbers */
.datatable { display: none; margin-top: 14px; }
body.show-table .datatable { display: block; }
.datatable table { font-size: var(--fs-sm); }
.datatable td, .datatable th { padding: 6px 10px; }

/* ---- flow (business model) ------------------------------------------------ */
.flow { display: flex; align-items: stretch; gap: 0; }
.flow__step { flex: 1; display: flex; flex-direction: column; gap: 9px; }
.flow__box {
  background: var(--charcoal-2); border: 1px solid var(--hair);
  border-radius: 11px; padding: 16px 18px; flex: 1;
}
.flow__box--accent { border-color: var(--champagne-d); background: rgba(201,168,124,.09); }
/* The box heading used to be the smallest, dimmest thing in the box while the
   names under it were near-white — so the eye read the data first and had to
   hunt for the label. The heading is now larger, champagne and rule-separated;
   the body steps back to --text-2 and its bold names to plain --text. */
.flow__k {
  font-size: var(--fs-xs); font-weight: 700; letter-spacing: .11em; text-transform: uppercase;
  color: var(--champagne); line-height: 1.35;
  padding-bottom: 11px; margin-bottom: 13px;
  border-bottom: 1px solid var(--hair);
}
.flow__v { font-size: var(--fs-body); line-height: 1.55; color: var(--text-2); }
.flow__v b { font-weight: 600; color: var(--text); }
.flow__arrow {
  flex: 0 0 46px; display: grid; place-items: center;
  color: var(--champagne-d); font-size: var(--fs-xl);
}

/* ---- capability matrix ---------------------------------------------------- */
.mx td, .mx th { text-align: center; }
.mx td { padding: 7px 6px; line-height: 1.35; }
.mx td:first-child, .mx th:first-child { text-align: left; }
.yes { color: var(--good); font-weight: 700; }
.no  { color: var(--text-3); }

/* ---- decision list -------------------------------------------------------- */
/* auto-fit rather than a hard 1fr 1fr: the slide has carried either one panel
   (approve only) or two (approve + acknowledge) at different times, and a fixed
   two-column grid leaves a lone panel stranded in the left half. */
.dec { display: grid; grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); gap: 26px; }
.dec__h {
  display: flex; align-items: center; gap: 10px; margin-bottom: 14px;
  font-size: var(--fs-sm); font-weight: 700; letter-spacing: .15em; text-transform: uppercase;
}
.dec__h--a { color: var(--champagne); }
.dec__h--b { color: var(--text-3); }
/* The list carries the whole slide, so it stretches to fill the panel rather
   than bunching at the top — the rows space themselves out down the page. */
.dec ol {
  list-style: none; counter-reset: d;
  display: flex; flex-direction: column; justify-content: space-around;
  height: 100%;
}
.dec .panel { display: flex; flex-direction: column; }
.dec li {
  counter-increment: d; position: relative; padding: 14px 0 14px 38px;
  font-size: var(--fs-body); line-height: 1.5; border-bottom: 1px solid var(--hair-2);
}
.dec li:last-child { border-bottom: 0; }
.dec li::before {
  content: counter(d, decimal-leading-zero); position: absolute; left: 0; top: 10px;
  font-size: var(--fs-xs); font-weight: 700; color: var(--champagne-d);
  font-variant-numeric: tabular-nums;
}

/* ---- source + status ------------------------------------------------------ */
.src {
  position: absolute; left: 74px; right: 74px; bottom: 30px;
  display: flex; justify-content: space-between; align-items: flex-end; gap: 30px;
  font-size: var(--fs-2xs); color: var(--text-3); line-height: 1.5;
}
.src b { color: var(--text-2); font-weight: 600; }
.tag {
  display: inline-block; font-size: var(--fs-3xs); font-weight: 700; letter-spacing: .1em;
  padding: 2px 7px; border-radius: 4px; border: 1px solid currentColor;
  white-space: nowrap;
}
.tag--a  { color: #7fb4e8 }
.tag--b  { color: var(--champagne) }
.tag--c  { color: #c2954a }
.tag--as { color: #d99a6c }

/* ---- chrome --------------------------------------------------------------- */
#nav {
  position: fixed; right: 26px; top: 18px; z-index: 60;
  display: flex; align-items: center; gap: 14px;
  font-size: 14.5px; color: var(--text-3);
  transition: opacity .4s, transform .4s;
}
#nav.idle { opacity: 0; transform: translateY(6px); pointer-events: none; }
#nav button {
  background: none; border: 1px solid var(--hair); color: var(--text-2);
  width: 30px; height: 30px; border-radius: 7px; cursor: pointer; font-size: 17px;
  font-family: inherit;
}
#nav button:hover { color: var(--champagne); border-color: var(--champagne-d); }
#count { font-variant-numeric: tabular-nums; letter-spacing: .1em; }
#count b { color: var(--text); font-weight: 600; }

#progress {
  position: fixed; left: 0; top: 0; height: 2px; z-index: 61;
  background: var(--champagne); transition: width .45s cubic-bezier(.22,.61,.36,1);
}


/* ---- speaker notes -------------------------------------------------------- */
#notes {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 65;
  background: var(--ink); border-top: 1px solid var(--champagne-d);
  padding: 20px 30px 22px; max-height: 42vh; overflow: auto;
  transform: translateY(101%); transition: transform .34s cubic-bezier(.22,.61,.36,1);
}
body.show-notes #notes { transform: none; }
#notes h4 {
  font-size: 14px; font-weight: 700; letter-spacing: .16em; text-transform: uppercase;
  color: var(--champagne-d); margin-bottom: 10px;
}
#notes p  { font-size: 17px; line-height: 1.7; color: var(--text-2); max-width: 110ch; }
#notes p + p { margin-top: 9px; }
#notes .warn { color: #d99a6c; }

/* ---- pending banner ------------------------------------------------------- */
#pending {
  position: fixed; left: 50%; top: 16px; transform: translateX(-50%); z-index: 62;
  font-size: 14px; color: var(--text-3); background: var(--charcoal-2);
  border: 1px solid var(--hair); border-radius: 20px; padding: 5px 15px;
  transition: opacity .4s;
}
#pending.idle { opacity: 0 }
#pending b { color: var(--champagne) }

@media (prefers-reduced-motion: reduce) {
  .slide, .rise, #progress, #notes { transition-duration: 1ms !important; }
}

/* ---- 07 organization ------------------------------------------------------
   The three roles are named but not yet filled, so the card carries a visible
   photo slot rather than hiding it — the gap is part of what the board is being
   asked about, not something to disguise. */
.orgtop {
  background: rgba(201,168,124,.09); border: 1px solid var(--champagne-d);
  border-radius: 10px; padding: 12px 18px;
}
.orgtop__t {
  font-size: var(--fs-3xs); font-weight: 700; letter-spacing: .16em;
  text-transform: uppercase; color: var(--champagne-d);
}
.orgtop__v { font-size: var(--fs-body); margin-top: 4px; }
.orgline { height: 14px; border-left: 1px solid var(--hair); margin-left: 42px; }
/* One tier of the org chart — as many equal cards as the tier holds. */
.orgrow { display: flex; gap: 14px; align-items: stretch; }
.orgrow > .orgcard { flex: 1; }   /* flex:1 sets width here; height comes
                                       from align-items:stretch and the shared
                                       min-height on .orgcard — do NOT reset
                                       min-height to 0 here, it cancels it. */

/* min-height matches the CEO card — a 96px portrait plus its 15px padding —
   so the tiers line up whether or not a card carries a photo. */
.orgcard {
  display: flex; gap: 18px; align-items: center;
  background: var(--charcoal-2); border: 1px solid var(--hair);
  border-radius: 11px; padding: 15px 18px; flex: 1;
  /* Every tier the same height, whether the card carries a 96px portrait, an
     initials placeholder, or a description that wraps to two lines. Sized to
     the tallest case so the three rows read as one aligned column. */
  min-height: 150px;
}
/* A seat with nobody in it yet: no photo slot to leave empty, dashed edge to
   read as provisional, and centred because there is no portrait to align to. */
.orgcard--vacant {
  border-style: dashed; border-color: rgba(201,168,124,.4);
  background: rgba(201,168,124,.04);
  justify-content: center; text-align: center;
  padding: 13px 16px;
}
.orgcard--vacant .orgcard__pos { color: var(--champagne-d); letter-spacing: .06em; }
.orgcard--vacant .orgcard__resp { margin-top: 3px; color: var(--text-3); }
/* Enlarged 24 Aug once real photos of K. Tanin / SUN / MILK existed to fill
   it — was 66px sized for the placeholder-only state. */
.orgcard__ph {
  flex: 0 0 96px; height: 96px; border-radius: 10px;
  border: 1px dashed rgba(201,168,124,.45); background: rgba(201,168,124,.05);
  display: grid; place-items: center; overflow: hidden;
}
.orgcard__ph span { font-size: var(--fs-xl); font-weight: 700; color: var(--champagne-d); letter-spacing: .08em; }
.orgcard__ph img { width: 100%; height: 100%; object-fit: cover; border-radius: 9px; }
.orgcard__b { min-width: 0; }
.orgcard__pos { font-size: var(--fs-lg); font-weight: 600; }
.orgcard__name {
  font-size: var(--fs-xs); color: var(--champagne-d); margin-top: 2px;
  font-style: italic;
}
.orgcard__resp { font-size: var(--fs-sm); color: var(--text-2); line-height: 1.5; margin-top: 5px; }

/* ---- competitor cards (slide 04) ----
   Category label and the names under it used to be near-identical in weight,
   so the eye had to read to tell which was which. A hairline splits them: the
   label is small, tracked and tinted; the data below is large and quiet. */
.cmp { display: flex; flex-direction: column; }
.cmp__k {
  font-size: var(--fs-xs); font-weight: 700; letter-spacing: .13em;
  text-transform: uppercase; color: var(--champagne-d); line-height: 1.4;
  padding-bottom: 13px; border-bottom: 1px solid var(--hair);
  /* These labels run to one, two and three lines. Reserving the tallest case
     puts every rule on the same baseline across the row, so the four cards
     read as one band instead of a ragged staircase. box-sizing is border-box
     deck-wide, so the padding has to be inside this figure or the 3-line label
     grows past it and drops its own rule 14px lower than the rest. */
  /* 3 lines (3 x 23.8) plus the 13px padding, rounded up to a whole pixel:
     the fractional calc left the 3-line card 1px below the others. */
  min-height: 86px;
}
.cmp__k--a { color: #9fc4f2; }   /* group A — the premium/international end */
.cmp__k--c { color: #f0a98c; }   /* Chinese brands — the price end          */
.cmp__v {
  font-size: var(--fs-lede); color: var(--text-2); line-height: 1.5;
  padding-top: 15px;
}
.cmp--us { border-color: var(--champagne-d); background: rgba(201,168,124,.08); }
.cmp--us .cmp__k { color: var(--champagne); }
.cmp--us .cmp__v { color: var(--text); font-weight: 600; }

.pgrid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
.pcard {
  background: var(--charcoal-2); border: 1px solid var(--hair);
  border-radius: 11px; padding: 15px 16px;
  display: flex; flex-direction: column; gap: 11px;
}
.pcard__top { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
.pcard__geo { font-size: var(--fs-3xs); color: var(--text-3); white-space: nowrap; }
.pcard__who { display: flex; gap: 11px; align-items: center; }
.pcard__who img {
  width: 84px; height: 84px; border-radius: 10px; object-fit: cover;
  flex: none;
}
/* Each portrait frames its subject differently — Donald Ho stands in the lower
   half beneath the company sign, so a top crop would show the sign, not him. */
.pcard__who img[src*="austscreen"] { object-position: 50% 55%; }
.pcard__who img[src*="gili"]       { object-position: 50% 26%; }
.pcard__who img[src*="hdsafe"]     { object-position: 50% 22%; }
.pcard__info b { font-size: var(--fs-body); font-weight: 600; }
.pcard__info span { font-size: var(--fs-2xs); color: var(--text-3); }
.pcard__does {
  font-size: var(--fs-sm); line-height: 1.55; flex: 1;
  padding-top: 10px; border-top: 1px solid var(--hair);
}
.pcard__ev { font-size: var(--fs-3xs); color: var(--text-3); line-height: 1.4; }

.brand { display: block; width: auto; }
.brand--invert { filter: brightness(0) invert(1); }
.brand--aus { opacity: .92; }

/* ---- 03 stat tiles --------------------------------------------------------
   Four numbers with a direction each — a stat tile reads faster than a chart
   would, and there is no series here to plot. */
.kpi__k {
  font-size: var(--fs-3xs); font-weight: 700; letter-spacing: .14em; text-transform: uppercase;
  color: var(--champagne-d); margin-bottom: 8px;
}

/* ---- 08 live 3D showroom ---------------------------------------------------
   Replaces the five "awaiting render" frames that stood here. The iframe holds
   no src until the presenter asks for it, so arriving on this slide starts no
   WebGL context and the deck opens exactly as fast as it did before.

   Three states, all driven from deck.js:
     .is-live    src assigned, scene loading
     .is-ready   showroom:ready arrived — poster steps aside
     .is-failed  showroom:error arrived — offer the recorded walkthrough        */
.showroom {
  position: relative; flex: 1; min-height: 0;
  border: 1px solid var(--hair); border-radius: 11px;
  background: var(--charcoal-2); overflow: hidden;
}
.showroom__poster {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 16px; text-align: center; padding: 0 40px;
  background:
    radial-gradient(120% 90% at 50% 0%, rgba(201,168,124,.07), transparent 62%),
    var(--charcoal-2);
  transition: opacity .45s ease;
}

/* The 3D floor-plan render, framed inside the poster instead of behind the
   whole slide — a radial mask dissolves its rectangular edge into the panel
   rather than leaving a hard border. */
.showroom__bgshot {
  position: absolute; inset: 0;
  /* Being position:absolute, this would otherwise paint (and hit-test) above
     the poster's static-flow flex items — kicker, line, and critically the
     enter button — silently eating clicks meant for them. */
  pointer-events: none;
  display: flex; align-items: center; justify-content: center;
  padding: 30px;
  opacity: .5;
  -webkit-mask-image: radial-gradient(66% 62% at 50% 48%, #000 55%, transparent 92%);
          mask-image: radial-gradient(66% 62% at 50% 48%, #000 55%, transparent 92%);
}
.showroom__bgshot img { width: 100%; height: 100%; object-fit: contain; }

.showroom__kicker {
  position: relative;
  font-size: var(--fs-xl); font-weight: 300; letter-spacing: .1em; text-transform: uppercase;
  color: var(--champagne-d);
  text-shadow: 0 2px 16px rgba(10,9,8,.85);
}
.showroom__line { position: relative; font-size: var(--fs-lede); line-height: 1.72; color: var(--text-2); text-shadow: 0 2px 16px rgba(10,9,8,.85); }
.showroom__line b { color: var(--text); font-weight: 600; }

.showroom__btn {
  margin-top: 4px; padding: 12px 30px;
  font-family: inherit; font-size: var(--fs-lg); letter-spacing: .02em;
  color: var(--ink); background: var(--champagne);
  border: 0; border-radius: 6px; cursor: pointer;
  transition: transform .18s var(--ease-out, ease), background .18s ease;
}
.showroom__btn:hover { background: #ded8cc; transform: translateY(-1px); }
.showroom__btn:active { transform: translateY(0); }

/* Loading tell — the button stops inviting clicks until the scene reports in. */
.showroom.is-live:not(.is-ready) .showroom__btn {
  background: var(--charcoal-3); color: var(--text-3); pointer-events: none;
}
.showroom.is-live:not(.is-ready) .showroom__btn::after { content: ' · กำลังโหลด'; }
/* While the modal holds the showroom, say so rather than inviting a second click. */
.showroom.is-open .showroom__btn { background: var(--charcoal-3); color: var(--text-2); }

.showroom__fallback {
  position: absolute; inset: 0; display: none;
  flex-direction: column; align-items: center; justify-content: center;
  gap: 10px; background: var(--charcoal-2);
}
.showroom__fallbackimg {
  max-width: 100%; max-height: calc(100% - 76px);
  object-fit: contain; border-radius: 6px;
}
.showroom__fallbackbar {
  display: flex; align-items: center; gap: 14px;
  font-size: var(--fs-xs); color: var(--text-2); font-variant-numeric: tabular-nums;
}
.showroom__fbbtn {
  width: 30px; height: 30px; border-radius: 50%;
  border: 1px solid var(--hair); background: transparent;
  color: var(--text-2); font-size: var(--fs-lg); cursor: pointer; line-height: 1;
}
.showroom__fbbtn:hover { border-color: var(--champagne); color: var(--champagne); }
.showroom__fallbacknote { font-size: var(--fs-xs); color: var(--text-3); }
.showroom.is-failed .showroom__fallback { display: flex; }
.showroom.is-failed .showroom__poster { display: none; }


/* ---- showroom modal --------------------------------------------------------
   Fixed to the viewport, not to the slide canvas. #canvas is transform-scaled,
   so anything inside it renders at 1600px and is then resized; a modal at body
   level gets real pixels at real size, which is both larger and sharper.       */
/* Above everything the deck puts on the viewport — the progress bar, the
   pending banner, the hint line and the slide nav all sit on top of the stage
   and would otherwise print straight through the modal's title bar. */
#showroomModal {
  position: fixed; inset: 0; z-index: 200;
  display: none; place-items: center;
  padding: 2.2vh 2vw;
  background: rgba(6, 8, 9, .82);
  backdrop-filter: blur(7px);
}
#showroomModal.is-open { display: grid; }

#showroomModalPanel {
  width: 100%; height: 100%;
  display: flex; flex-direction: column;
  background: var(--charcoal);
  border: 1px solid var(--hair);
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 40px 90px -30px rgba(0, 0, 0, .9);
}
/* Fullscreen removes the frame entirely — the chrome is only there to get out of. */
#showroomModalPanel:fullscreen { border: 0; border-radius: 0; }

.srm__bar {
  flex: 0 0 auto;
  display: flex; align-items: center; justify-content: space-between;
  padding: 11px 16px;
  border-bottom: 1px solid var(--hair);
  background: var(--charcoal-2);
}
.srm__title {
  font-size: 14px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--champagne-d);
}
.srm__actions { display: flex; gap: 8px; }
.srm__btn {
  padding: 7px 15px; font-family: inherit; font-size: 15px;
  color: var(--text-2); background: transparent;
  border: 1px solid var(--hair); border-radius: 6px; cursor: pointer;
  transition: border-color .18s ease, color .18s ease;
}
.srm__btn:hover { border-color: var(--champagne); color: var(--champagne); }
.srm__btn--close:hover { border-color: var(--bad); color: var(--bad); }

.srm__frame {
  flex: 1; width: 100%; border: 0;
  opacity: 0; transition: opacity .5s var(--ease-out, ease);
}
#showroomModal.is-ready .srm__frame { opacity: 1; }

/* Held until the scene reports in, so the panel never flashes an empty white
   rectangle on a projector while the chunk loads. */
#showroomModal:not(.is-ready) .srm__frame { background: var(--charcoal); }


/* The deck's own controls step aside while the showroom window is up. They
   cannot be used from here — arrow keys and the wheel are intercepted — so
   leaving them visible only crowds the title bar and suggests otherwise. */
body.modal-open #nav,
body.modal-open #pending,
body.modal-open #progress {
  opacity: 0;
  pointer-events: none;
  transition: opacity .2s ease;
}

/* ==========================================================================
   MOBILE / NARROW — reflow mode

   Above this width the deck is a fixed 1600x900 stage scaled to fit, which is
   right for a projector and wrong for a phone: at 390px the scale factor is
   0.24 and body copy lands under 5 real pixels. Below the breakpoint the
   transform is switched off in deck.js and everything here takes over —
   real device pixels, one column, one slide at a time with swipe.

   The type tokens are redeclared rather than scaled. These are real pixels,
   not canvas pixels, so they are deliberately smaller numbers than the
   desktop scale while reading considerably larger on the device.
   ========================================================================== */
@media (max-width: 900px) {
  :root {
    --fs-display-xl: 34px;
    --fs-display:    26px;
    --fs-kpi:        30px;
    --fs-xl:         20px;
    --fs-lede:     16.5px;
    --fs-lg:         17px;
    --fs-body:       16px;
    --fs-sm:         15px;
    --fs-xs:         14px;
    --fs-2xs:      13.5px;
    --fs-3xs:        13px;
  }

  /* The stage stops being a scaled box and becomes the viewport. #canvas must
     also lose the 50%/50% offsets, since deck.js drops the -50%,-50% translate
     that used to cancel them. */
  #stage { display: block; }
  #canvas {
    top: 0; left: 0;
    width: 100%; height: 100%;
    transform: none;
  }

  /* Slides still page one at a time, but a slide may now be taller than the
     screen, so each scrolls within itself. */
  .slide {
    padding: 18px 16px 76px;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    /* the desktop arrival blur is expensive on mobile GPUs and reads as jank */
    filter: none;
    transform: translateY(10px);
  }
  .slide.is-active { transform: none; filter: none; }

  .slide-head { margin-bottom: 16px; }
  .display__num { margin-right: .3em; }
  /* slide 01 sets nowrap inline to keep its strapline on one projector line —
     on a phone that would run straight off the side of the screen. */
  .lede { max-width: none; white-space: normal !important; }

  /* ---- stack everything ---- */
  .row, .body.row { flex-direction: column; gap: 12px; }

  /* Nothing shrinks in reflow mode. On the fixed stage every block is sized to
     divide up a known 900px height, so panels carry flex:1 and shrink to fit.
     Here the slide scrolls instead, so each block must take its natural height
     — otherwise they compress into each other and overlap. Several of those
     flex:1 declarations are inline in index.html, which is why this needs
     !important; it is scoped to this breakpoint only. */
  .body, .body > *, .row > *, .col > *,
  .flow__step, .flow__box, .orgrow > .orgcard {
    flex: 0 0 auto !important;
  }
  .col--side { flex: 0 0 auto; }
  /* .showroom's poster is position:absolute, so without a height of its own
     the box collapses to nothing once it stops being a flex:1 filler. */
  .showroom { min-height: 58vh; }
  .kpis { grid-template-columns: 1fr 1fr; gap: 10px; }
  .kpi { padding: 12px 14px; }
  .kpi > span { margin-top: 5px; }
  .orggrid, .pgrid, .dec { grid-template-columns: 1fr; gap: 12px; }
  .orgrow { flex-direction: column; gap: 12px; }
  .panel { padding: 15px 16px; border-radius: 10px; }
  .panel__t { margin-bottom: 9px; }

  /* ---- the five-step flow on slide 02 becomes a vertical chain ---- */
  .flow { flex-direction: column; gap: 0; }
  .flow__arrow { flex: 0 0 26px; transform: rotate(90deg); }
  .flow__box { padding: 13px 15px; }

  /* ---- org / partner cards ---- */
  .orgcard { gap: 13px; padding: 12px 14px; }
  .orgcard__ph { flex: 0 0 64px; height: 64px; }
  .pcard { padding: 12px 14px; }
  .pcard__who img { width: 64px; height: 64px; }

  .mbar__n { flex: 0 0 76px; }
  .mbar__v { flex: 0 0 52px; }

  .dec li { padding: 11px 0 11px 32px; }
  .dec ol { display: block; height: auto; }
  /* stacked one per row, so there is no sibling rule to line up with */
  .cmp__k { min-height: 0; }

  /* ---- tables reflow to stacked rows ----
     A 4-column table cannot be squeezed to 390px, and a horizontal scroller
     would fight the swipe gesture. Each row becomes its own block instead, so
     nothing is truncated and no column is dropped. */
  table, tbody, tr, td { display: block; width: auto; }
  thead { display: none; }
  tr {
    padding: 9px 0;
    border-bottom: 1px solid var(--hair-2);
  }
  tr:last-child { border-bottom: 0; }
  /* Block, not flex: several cells hold <br>-separated lines, and as a flex
     container each inline fragment would become its own column. .num keeps the
     right alignment it already has from the base rule. */
  td { padding: 1px 0; border: 0; display: block; }
  tr.hl td { box-shadow: none; }
  tr.hl { box-shadow: inset 3px 0 0 var(--champagne-d); padding-left: 10px; }

  /* ---- chart ---- */
  #chart-revenue svg { height: auto; }
  /* The phone form of the chart already prints each channel's name, share,
     derivation and amount, so the breakdown panel beside it on desktop would
     just say everything twice. */
  #revenue-breakdown { display: none; }
  /* the derivation is hover-only on desktop; on touch there is no hover, so
     the table that carries it is always on */
  .datatable { display: block; }

  /* ---- chrome: nav to the bottom, thumb-sized ---- */
  #nav {
    right: auto; left: 50%; top: auto; bottom: 14px;
    transform: translateX(-50%);
    gap: 18px;
    background: rgba(19,23,26,.92);
    border: 1px solid var(--hair);
    border-radius: 999px;
    padding: 6px 10px;
    backdrop-filter: blur(6px);
  }
  #nav.idle { opacity: 1; transform: translateX(-50%); pointer-events: auto; }
  #nav button { width: 48px; height: 48px; font-size: 22px; border-radius: 999px; }
  #pending { display: none; }
  #tip { display: none; }

  /* Pinning the footer to the slide's bottom edge only works when the slide is
     exactly one screen tall. Now that it scrolls, the tag flows after the
     content instead — and .slide > .src has to be unpinned to match. */
  .src, .slide > .src {
    position: static;
    margin-top: 18px;
    justify-content: flex-end;
    gap: 12px;
  }

  /* ---- showroom: full-bleed modal, tappable controls ---- */
  #showroomModal { padding: 0; }
  #showroomModalPanel { border-radius: 0; }
  .srm__btn { padding: 12px 16px; }
  .showroom__poster { padding: 0 20px; gap: 12px; }
  .showroom__btn { padding: 14px 28px; }
  #notes { max-height: 60vh; padding: 16px 18px 18px; }
}
