/* ═══════════════════════════════════════════════════════════════════════
   A private gaming salon, not a casino floor.

   Lacquer black, real baize, AGED brass. The cliché here is black + shiny
   gold on flat panels; this goes the other way — dull rubbed metal, ivory
   card stock, hairline rules, and light that behaves like light. Every
   raised surface gets a top highlight and a grounded shadow, because that
   is what makes a screen feel like an object rather than a diagram.

   Type: Bodoni Moda — a didone, high-contrast, the typography of luxury
   goods — for the reveal and the brand. Archivo Narrow for numerals, which
   must stay legible at 11px inside a betting cell where a didone would
   fall apart. The contrast between the two IS the design.
   ═══════════════════════════════════════════════════════════════════════ */
:root{
  /* lacquer, with a green undertone so it sits under the baize */
  --void:#070A09;  --panel:#0D1110;  --panel-hi:#141917;
  --baize:#123328; --baize-lo:#0A1E18;
  --blood:#8E1D28; --blood-hi:#A62833;
  --coal:#17191B;  --coal-hi:#212427;
  --zero:#14603E;
  --ivory:#EFE9DC; --ivory-dim:rgba(239,233,220,.52);
  /* aged brass, never yellow gold */
  --brass:#B08D3F; --brass-hi:#E8CE86; --brass-lo:#6E5620;
  --rule:rgba(239,233,220,.11);
  --bar:54px;
  --ease:cubic-bezier(.22,1,.36,1);
  --snap:cubic-bezier(.34,1.4,.5,1);
  --shadow-lift:0 1px 0 rgba(255,255,255,.07) inset,
                0 8px 22px -8px rgba(0,0,0,.85),
                0 2px 4px rgba(0,0,0,.5);
}
*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{margin:0;height:100%;overscroll-behavior:none}
body{
  background:var(--void); color:var(--ivory);
  font-feature-settings:'tnum' 1;
  font-family:'Jost',system-ui,-apple-system,sans-serif;
  font-variant-numeric:tabular-nums;      /* money must not jitter */
  display:flex;flex-direction:column;overflow:hidden;
  padding-top:env(safe-area-inset-top);
  -webkit-user-select:none;user-select:none;
}
button{font:inherit;color:inherit;background:none;border:none;cursor:pointer}
:focus-visible{outline:1px solid var(--brass-hi);outline-offset:3px}

/* ── top bar ─────────────────────────────────────────────────────────── */
.bar{
  flex:0 0 auto;height:var(--bar);display:flex;align-items:center;gap:0;
  padding:0 6px;background:linear-gradient(var(--panel-hi),var(--panel));
  border-bottom:1px solid var(--rule);
  box-shadow:0 1px 0 rgba(255,255,255,.04) inset;
}
/* 44x44 minimum hit area (Apple HIG / Material). Measured at 36x36, which is
   under the floor. The visible glyph stays 18px; only the target grows. */
.icon{width:44px;height:44px;display:grid;place-items:center;border-radius:12px;
  transition-property:background,transform;transition-duration:.2s,.1s;
  transition-timing-function:var(--ease)}
.icon svg{width:18px;height:18px;fill:var(--ivory-dim);transition:fill .2s}
.icon:active{transform:scale(.88);background:rgba(239,233,220,.07)}
.icon[aria-pressed="false"] svg{fill:rgba(239,233,220,.22)}
@media(hover:hover){.icon:hover svg{fill:var(--brass-hi)}}
.title{
  flex:1 1 auto;text-align:center;
  font-family:'Bodoni Moda',serif;font-weight:400;font-size:12.5px;
  letter-spacing:.38em;text-indent:.38em;text-transform:uppercase;
  color:var(--brass-hi);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  text-shadow:0 1px 0 rgba(0,0,0,.7);
}
.reel{display:flex;gap:4px;max-width:34%;overflow:hidden;flex-direction:row-reverse}
.pip{flex:0 0 auto;width:23px;height:23px;border-radius:4px;display:grid;place-items:center;
  font-size:12.5px;font-weight:500;letter-spacing:0;
  box-shadow:0 1px 0 rgba(255,255,255,.12) inset,0 2px 4px rgba(0,0,0,.5);
  animation:pipIn .5s var(--snap) both}
.pip.r{background:linear-gradient(var(--blood-hi),var(--blood))}
.pip.b{background:linear-gradient(var(--coal-hi),var(--coal))}
.pip.g{background:linear-gradient(#1B7A50,var(--zero))}
@keyframes pipIn{from{transform:translateX(12px) scale(.5);opacity:0}to{transform:none;opacity:1}}

/* ── stage ───────────────────────────────────────────────────────────── */
.stage{position:relative;flex:0 0 auto;width:100%;min-height:0;background:#000;
  /* The dealer footage is a 16:9 table master. A near-square stage kept less
     than half its width, which threw away the table and most of the room —
     the very things that make it read as a broadcast rather than a portrait
     of a person. 4:3 keeps about three quarters of the frame and gives the
     felt below more room at the same time. */
  aspect-ratio:4/3;max-height:46vh;
  overflow:hidden;
  box-shadow:0 10px 30px -12px rgba(0,0,0,.9)}
.stage[data-mode="dealer"]{aspect-ratio:16/9}
.stage video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  opacity:0;transition:opacity .34s cubic-bezier(.4,0,.2,1)}
.stage video.live{opacity:1}
.scrim{position:absolute;inset:0;pointer-events:none;
  transition:box-shadow .45s var(--ease);
  background:
    radial-gradient(120% 90% at 50% 40%,transparent 42%,rgba(0,0,0,.5) 100%),
    linear-gradient(rgba(0,0,0,.45) 0%,transparent 18%,transparent 58%,rgba(0,0,0,.42) 100%)}
/* hairline brass rule under the stage — the salon detail */
.stage::after{content:'';position:absolute;left:0;right:0;bottom:0;height:1px;
  background:linear-gradient(90deg,transparent,var(--brass) 22%,var(--brass-hi) 50%,var(--brass) 78%,transparent);
  opacity:.5}

/* ═══ the win moment ═════════════════════════════════════════════════════
   A PLAQUE, not a banner. The previous version bled gold rules edge to edge
   with 60px numerals — loud, but loose: nothing was contained, the fan was
   wide, and the type filled the frame. Premium in this category reads as a
   contained object with precise margins, restrained type, and one or two
   pieces of real metal detail. Everything here is tighter, smaller and more
   deliberate than before.                                                 */
.result{
  position:absolute;left:0;right:0;top:50%;bottom:auto;transform:translateY(-50%);
  display:flex;flex-direction:column;align-items:center;
  opacity:0;pointer-events:none;transition:opacity .3s var(--ease)}
.result.show{opacity:1}
.result::after{content:'';position:absolute;inset:-22% -14%;z-index:-1;pointer-events:none;
  background:radial-gradient(60% 54% at 50% 50%,rgba(3,5,4,.86) 0%,rgba(3,5,4,.52) 56%,transparent 82%)}

/* the cards straddle the top edge of the plaque — a classic plaque device,
   and it binds the two elements into one object instead of two stacked ones */
.cards{position:relative;display:grid;place-items:center;height:78px;
  margin-bottom:-30px;z-index:3}
.card{
  position:absolute;display:grid;place-items:center;border-radius:6px;
  font-family:'Bodoni Moda',serif;font-weight:700;line-height:1;
  background:linear-gradient(#FBF7EE 0%,#E7DFCE 55%,#CFC5B0 100%);
  color:#14161A;transform-origin:50% 150%;
  box-shadow:0 1px 0 rgba(255,255,255,.85) inset,0 8px 18px -6px rgba(0,0,0,.95);
  opacity:0}
.card::after{content:'';position:absolute;inset:-1px;border-radius:6px;padding:1px;
  pointer-events:none;
  background:linear-gradient(155deg,#F3E2B0,#B08D3F 44%,#6E5620 68%,#EBD79A);
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;mask-composite:exclude}
.card.c-r{background:linear-gradient(#A02430,#6C111A);color:#FBF7EE;text-shadow:0 1px 2px rgba(0,0,0,.6)}
.card.c-b{background:linear-gradient(#27292D,#101113);color:#FBF7EE;text-shadow:0 1px 2px rgba(0,0,0,.6)}
.card.c-g{background:linear-gradient(#1A7248,#0A452B);color:#FBF7EE;text-shadow:0 1px 2px rgba(0,0,0,.6)}
/* tighter fan: the neighbours OVERLAP the winner instead of standing off it */
.card.side{width:38px;height:50px;font-size:18px;filter:brightness(.8) saturate(.9)}
.card.side.l{transform:translate(-30px,7px) rotate(-11deg)}
.card.side.r{transform:translate( 30px,7px) rotate( 11deg)}
.card.main{width:54px;height:70px;font-size:34px;z-index:2;transform:translateY(-2px)}
.result.show .card{animation:cardIn .5s var(--snap) forwards}
.result.show .card.side.l{animation-name:cardInL}
.result.show .card.side.r{animation-name:cardInR}
.result.show .card.main{animation-delay:.05s}
@keyframes cardIn {from{opacity:0;transform:translateY(14px) scale(.72)}
                   to{opacity:1;transform:translateY(-2px)}}
@keyframes cardInL{from{opacity:0;transform:translate(0,7px) rotate(0) scale(.72)}
                   to{opacity:1;transform:translate(-30px,7px) rotate(-11deg)}}
@keyframes cardInR{from{opacity:0;transform:translate(0,7px) rotate(0) scale(.72)}
                   to{opacity:1;transform:translate( 30px,7px) rotate( 11deg)}}

/* the plaque itself */
.winblock{
  position:relative;display:flex;flex-direction:column;align-items:center;gap:2px;
  padding:32px 40px 17px;border-radius:13px;max-width:min(78vw,330px);
  background:
    linear-gradient(180deg,rgba(12,15,14,.95) 0%,rgba(4,6,5,.975) 100%);
  backdrop-filter:blur(20px) saturate(1.15);
  -webkit-backdrop-filter:blur(20px) saturate(1.15);
  box-shadow:0 1px 0 rgba(255,255,255,.10) inset,
             0 0 0 1px rgba(176,141,63,.55),
             0 26px 60px -22px rgba(0,0,0,1);
  opacity:0;transform:translateY(10px) scale(.97)}
.result.show .winblock{animation:winIn .55s var(--ease) .34s forwards}
@keyframes winIn{to{opacity:1;transform:none}}
/* engraved corner marks — the one piece of ornament, kept small */
.winblock::before,.winblock::after{content:'';position:absolute;width:13px;height:13px;
  border:1px solid rgba(232,206,134,.85);opacity:0}
.winblock::before{left:9px;bottom:9px;border-right:none;border-top:none;border-radius:0 0 0 4px}
.winblock::after{right:9px;bottom:9px;border-left:none;border-top:none;border-radius:0 0 4px 0}
/* upper pair, drawn on the rule element so we do not need extra markup */
.rule{position:relative}
.rule::before,.rule::after{content:'';position:absolute;top:-19px;width:13px;height:13px;
  border:1px solid rgba(232,206,134,.85);opacity:0}
.rule::before{left:-64px;border-right:none;border-bottom:none;border-radius:4px 0 0 0}
.rule::after{right:-64px;border-left:none;border-bottom:none;border-radius:0 4px 0 0}
.result.show .rule::before,.result.show .rule::after{animation:winIn .5s var(--ease) .62s forwards}
.result.show .winblock::before,.result.show .winblock::after{animation:winIn .5s var(--ease) .62s forwards}

/* type: smaller and more precise than before */
.rule{display:block;width:118px;height:1px;margin:2px 0 6px;
  background:linear-gradient(90deg,transparent,rgba(232,206,134,.75) 50%,transparent);
  transform:scaleX(.4);opacity:0}
.result.show .rule{animation:ruleIn .5s var(--ease) .5s forwards}
.rule:last-of-type{display:none}          /* one rule, under the label */
@keyframes ruleIn{to{transform:scaleX(1);opacity:1}}
.win{font-family:'Bodoni Moda',serif;font-weight:700;font-size:30px;line-height:1;
  letter-spacing:.005em;font-variant-numeric:tabular-nums;position:relative;
  background:linear-gradient(180deg,#FDF4D8 0%,#E8CE86 30%,#C29C44 66%,#8A6C22 100%);
  -webkit-background-clip:text;background-clip:text;color:transparent;
  filter:drop-shadow(0 1px 1px rgba(0,0,0,.8))}
.winlbl{font-family:'Bodoni Moda',serif;font-size:9.5px;letter-spacing:.52em;
  text-indent:.52em;color:rgba(243,236,220,.72);margin-top:7px}

/* tiers stay restrained — scale, not spectacle */
.result.t-big .win{font-size:34px}
.result.t-huge .win{font-size:39px}
.result.t-huge .winblock{box-shadow:0 1px 0 rgba(255,255,255,.12) inset,
  0 0 0 1px rgba(232,206,134,.85),0 0 34px rgba(212,166,42,.22),
  0 26px 60px -22px rgba(0,0,0,1)}
.result.t-huge .card.main{box-shadow:0 1px 0 rgba(255,255,255,.85) inset,
  0 0 22px rgba(232,206,134,.45),0 8px 18px -6px rgba(0,0,0,.95)}

/* ═══ celebration ════════════════════════════════════════════════════════
   Gold sparks thrown from behind the card. Count scales with the tier, so a
   small win gets a glint and a big one gets an event.                     */
.sparks{position:absolute;left:50%;top:40px;width:0;height:0;pointer-events:none}
.spark{position:absolute;width:6px;height:6px;border-radius:50%;
  background:radial-gradient(circle,#FFF3D0 0%,#E8CE86 45%,#B08D3F 100%);
  box-shadow:0 0 8px rgba(232,206,134,.8);
  animation:spark var(--d,1.5s) cubic-bezier(.15,.7,.3,1) forwards}
@keyframes spark{
  0%{opacity:0;transform:translate(0,0) scale(.4)}
  12%{opacity:1}
  100%{opacity:0;transform:translate(var(--tx),var(--ty)) scale(var(--s,.5))}}

.timer{position:absolute;left:12px;top:12px;width:34px;height:34px;border-radius:50%;
  display:grid;place-items:center;background:rgba(7,10,9,.72);
  border:1px solid var(--brass);font-size:15px;font-weight:700;color:var(--brass-hi);
  opacity:0;transition:opacity .25s var(--ease)}
.timer.show{opacity:1}

/* ── felt ────────────────────────────────────────────────────────────── */
.felt{
  /* The stage now has a FIXED 4:3 aspect, so it no longer absorbs spare
     height — the felt does. Without this the column stops short of the
     viewport, leaving dead black under the dock and letting the betting
     sheet (which is positioned off the dock) overlap it. */
  flex:1 1 auto;min-height:0;display:grid;place-items:center;padding:10px 10px 4px;
  background:
    /* woven baize: fine noise, then a soft key light from above */
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3'/><feColorMatrix type='saturate' values='0'/></filter><rect width='140' height='140' filter='url(%23n)' opacity='.05'/></svg>"),
    radial-gradient(130% 90% at 50% -10%, #1B4636 0%, var(--baize) 46%, var(--baize-lo) 100%);
  box-shadow:0 14px 40px -20px #000 inset;
  transition:filter .45s var(--ease)}
svg{width:100%;height:auto;display:block;touch-action:manipulation}
.cellface{stroke:rgba(239,233,220,.20);stroke-width:1}
.num{font-family:'Jost',sans-serif;font-weight:500;font-size:31px;letter-spacing:.01em;
  fill:var(--ivory);text-anchor:middle;dominant-baseline:central;pointer-events:none;
  paint-order:stroke;stroke:rgba(0,0,0,.5);stroke-width:.6}
.outlabel{font-family:'Jost',sans-serif;font-weight:400;font-size:17px;
  letter-spacing:.19em;fill:rgba(239,233,220,.82);text-anchor:middle;
  dominant-baseline:central;pointer-events:none}
.hit{fill:transparent;cursor:pointer;transition:fill .12s linear}
@media (hover:hover){.hit:hover{fill:rgba(232,206,134,.16)}}
.hit.armed{fill:rgba(232,206,134,.26);animation:armed 1.2s ease-in-out infinite}
@keyframes armed{50%{fill:rgba(232,206,134,.08)}}
.chip{pointer-events:none}
.chip.pop{animation:chipDrop .36s var(--snap)}
@keyframes chipDrop{from{transform:translateY(-18px) scale(.66);opacity:.15}to{transform:none;opacity:1}}

/* ── dock ────────────────────────────────────────────────────────────── */
.dock{flex:0 0 auto;background:linear-gradient(var(--panel-hi),var(--panel));
  border-top:1px solid var(--rule);box-shadow:0 -1px 0 rgba(255,255,255,.04) inset,
  0 -10px 26px -18px #000;
  padding:9px 12px calc(9px + env(safe-area-inset-bottom));
  display:flex;flex-direction:column;gap:9px}
.chips{display:flex;gap:9px;justify-content:center;align-items:flex-end;height:58px}
.chipbtn{width:48px;height:48px;border-radius:50%;padding:0;position:relative;
  filter:brightness(.58) saturate(.7) contrast(.95);
  transition:transform .26s var(--snap),filter .22s var(--ease)}
.chipbtn svg{width:100%;height:100%;display:block;pointer-events:none;
  filter:drop-shadow(0 3px 5px rgba(0,0,0,.6))}
.chipbtn[aria-checked="true"]{filter:none;transform:translateY(-9px) scale(1.1)}
.chipbtn[aria-checked="true"]::after{content:'';position:absolute;inset:-5px;border-radius:50%;
  border:1px solid var(--brass-hi);box-shadow:0 0 14px rgba(232,206,134,.35);
  animation:ring .45s var(--ease)}
@keyframes ring{from{transform:scale(.82);opacity:0}to{transform:none;opacity:1}}
.chipbtn:active{transform:translateY(-5px) scale(.95)}
.tools{display:flex;gap:9px;align-items:stretch}
.tool{flex:0 0 auto;width:54px;display:flex;flex-direction:column;align-items:center;
  justify-content:center;gap:3px;padding:8px 0;border-radius:11px;
  background:linear-gradient(rgba(239,233,220,.055),rgba(239,233,220,.02));
  border:1px solid var(--rule);
  box-shadow:0 1px 0 rgba(255,255,255,.05) inset;
  transition:transform .12s var(--ease),opacity .2s,border-color .2s}
.tool svg{width:16px;height:16px;fill:var(--ivory-dim)}
.tool span{font-size:9px;font-weight:400;letter-spacing:.24em;text-indent:.24em;
  text-transform:uppercase;color:var(--ivory-dim)}
.tool:active:not(:disabled){transform:scale(.92)}
@media(hover:hover){.tool:hover:not(:disabled){border-color:rgba(232,206,134,.4)}}
.tool:disabled{opacity:.26}
/* the spin button is the one piece of real metal on screen */
.spin{flex:1 1 auto;border-radius:11px;position:relative;overflow:hidden;
  font-family:'Bodoni Moda',serif;font-size:17px;font-weight:700;letter-spacing:.3em;
  text-indent:.3em;color:#171208;
  background:linear-gradient(180deg,#F0DCA0 0%,#D8B45C 26%,#B08D3F 62%,#8A6C22 100%);
  box-shadow:0 1px 0 rgba(255,255,255,.55) inset,0 3px 0 #5E4715,
             0 10px 24px -8px rgba(176,141,63,.5);
  transition:transform .1s var(--ease),box-shadow .1s var(--ease),filter .25s}
.spin::after{content:'';position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(105deg,transparent 38%,rgba(255,255,255,.5) 48%,transparent 58%);
  transform:translateX(-120%)}
.spin:not(:disabled)::after{animation:sheen 4.5s var(--ease) 1.5s infinite}
@keyframes sheen{0%{transform:translateX(-120%)}22%,100%{transform:translateX(120%)}}
.spin:active:not(:disabled){transform:translateY(3px);
  box-shadow:0 1px 0 rgba(255,255,255,.4) inset,0 0 0 #5E4715,0 4px 10px rgba(0,0,0,.5)}
.spin:disabled{filter:grayscale(.9) brightness(.5);box-shadow:none}
.ledger{display:flex;justify-content:space-between;font-size:10.5px;font-weight:400;
  letter-spacing:.26em;text-indent:.26em;color:var(--ivory-dim);text-transform:uppercase}
.ledger b{color:var(--ivory);font-weight:500;font-size:16px;letter-spacing:0;
  font-variant-numeric:tabular-nums;font-feature-settings:'tnum' 1}
.ledger b.bump{animation:bump .55s var(--snap)}
@keyframes bump{40%{transform:scale(1.18);color:var(--brass-hi)}}

.flyer{position:fixed;width:38px;height:38px;pointer-events:none;z-index:60;opacity:0;
  will-change:transform;filter:drop-shadow(0 6px 8px rgba(0,0,0,.6))}
.flyer.go{animation:fly .36s var(--ease) forwards}
@keyframes fly{from{opacity:1;transform:translate(var(--x0),var(--y0)) scale(1)}
  to{opacity:0;transform:translate(var(--x1),var(--y1)) scale(.5)}}

body[data-phase="betting"] .stage video.live{filter:saturate(.85) brightness(.62)}
body[data-phase="spinning"] .stage video.live{filter:none}
.stage video{transition:opacity .34s cubic-bezier(.4,0,.2,1), filter .5s var(--ease)}
body[data-phase="spinning"] .felt{filter:saturate(.6) brightness(.6)}
body[data-phase="spinning"] .hit{cursor:default}

/* ── landscape phone ─────────────────────────────────────────────────── */
@media (orientation:landscape) and (max-height:619px){
  /* Grid, not flex-wrap. Wrapping put the dock on a second row below the
     fold, because the stage claimed the full column height — chips and SPIN
     were simply unreachable. */
  body{display:grid;padding-top:0;
    grid-template-columns:44vw 56vw;
    grid-template-rows:var(--bar) 1fr auto;
    height:100vh}
  .bar{grid-column:1/-1;grid-row:1}
  .stage{grid-column:1;grid-row:2/4;width:100%;height:100%;aspect-ratio:auto;flex:none}
  .felt{grid-column:2;grid-row:2;width:100%;min-height:0;padding:8px 10px 2px;
    align-content:center}
  .dock{grid-column:2;grid-row:3;width:100%;padding:6px 10px calc(6px + env(safe-area-inset-bottom));
    gap:6px}
  .chips{height:44px;gap:7px}.chipbtn{width:38px;height:38px}
  .tools{gap:7px}.tool{width:44px;padding:5px 0}
  .tool svg{width:14px;height:14px}.tool span{font-size:8px}
  .spin{font-size:14px}
  .ledger{font-size:11px}.ledger b{font-size:13px}
  .card.main{width:46px;height:58px;font-size:28px}
  .card.side{width:30px;height:38px;font-size:15px}
  .win{font-size:26px}
  .result{bottom:5%;gap:8px}
  .winblock{padding:7px 20px 9px;min-width:min(46vw,260px)}
}
/* ── desktop ─────────────────────────────────────────────────────────── */
@media (min-width:900px) and (min-height:620px){
  body{align-items:center;
    background:radial-gradient(90% 60% at 50% 0%,#0D1412 0%,var(--void) 70%)}
  .bar,.stage,.felt,.dock{width:100%;max-width:1080px;margin:0 auto}
  .bar{border-radius:0 0 2px 2px}
  .stage{flex:0 0 auto;aspect-ratio:16/9;max-height:54vh}
  .felt{padding:18px 18px 0;border-left:1px solid var(--rule);border-right:1px solid var(--rule)}
  svg{max-width:880px}
  .card.main{width:70px;height:88px;font-size:44px}
  .card.side{width:44px;height:56px;font-size:22px}
  .win{font-size:48px}.winlbl{font-size:12px}
  .chips{height:68px}.chipbtn{width:56px;height:56px}
  .tool{width:66px}
  .dock{border-radius:2px 2px 0 0}
}
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}

/* ═══ portrait betting sheet ═════════════════════════════════════════════
   The compact board is a readout; this is where you actually bet. Standing
   the layout up gives each cell ~3x the tap area on a phone.              */
.sheet{
  /* stops ABOVE the dock: covering it meant you could not change chip value
     while betting, which is the one thing you do most in this view */
  position:fixed;left:0;right:0;top:0;bottom:var(--dock-h,150px);
  z-index:50;display:flex;flex-direction:column;
  background:linear-gradient(180deg,rgba(7,10,9,.72) 0%,rgba(7,10,9,.94) 22%);
  backdrop-filter:blur(14px) saturate(.9);
  -webkit-backdrop-filter:blur(14px) saturate(.9);
  /* Closed, it must clear the viewport ENTIRELY. translateY(100%) only moves
     it by its own height, and since it stops short of the bottom by --dock-h,
     that left its header parked on top of the chip rail. */
  transform:translateY(calc(100% + var(--dock-h,150px)));
  transition:transform .42s var(--ease);
}
body.sheet-open .sheet{transform:none}
.dock{position:relative;z-index:60}   /* always above the sheet */
.sheet[aria-hidden="true"]{pointer-events:none}
.sheet-grab{width:38px;height:4px;border-radius:2px;background:rgba(239,233,220,.28);
  margin:9px auto 4px;flex:0 0 auto}
.sheet-head{flex:0 0 auto;display:flex;align-items:center;gap:8px;
  padding:6px 16px 8px;font-size:10.5px;font-weight:400;letter-spacing:.26em;
  text-indent:.26em;text-transform:uppercase;color:var(--ivory-dim)}
.sheet-head span{color:var(--brass-hi);font-weight:700;font-size:16px;letter-spacing:.02em}
.sheet-done{margin-left:auto;min-height:44px;padding:11px 22px;border-radius:11px;
  font-family:'Bodoni Moda',serif;font-size:13px;letter-spacing:.22em;text-indent:.22em;
  color:#171208;background:linear-gradient(180deg,#F0DCA0,#C39C46);
  box-shadow:0 1px 0 rgba(255,255,255,.5) inset,0 2px 0 #6E5620;
  transition:transform .1s var(--ease)}
.sheet-done:active{transform:translateY(2px);box-shadow:0 1px 0 rgba(255,255,255,.4) inset}
.sheet-felt{flex:1 1 auto;min-height:0;display:grid;place-items:center;
  padding:8px 12px 14px;margin:0 10px 10px;border-radius:12px;
  /* the same baize as the table, so the sheet is the table standing up
     rather than a dialog that happens to contain a board */
  background:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3'/><feColorMatrix type='saturate' values='0'/></filter><rect width='140' height='140' filter='url(%23n)' opacity='.05'/></svg>"),
    radial-gradient(120% 80% at 50% 0%, #1B4636 0%, var(--baize) 44%, var(--baize-lo) 100%);
  box-shadow:0 1px 0 rgba(255,255,255,.06) inset, 0 18px 40px -18px #000;
  border:1px solid rgba(212,166,42,.22)}
.sheet-felt svg{width:auto;height:100%;max-width:100%}
body.sheet-open .felt{filter:blur(1px) brightness(.5)}

/* ═══ one chip, dragged ══════════════════════════════════════════════════ */
.chips{display:flex;justify-content:center;align-items:center;height:62px;position:relative}
.dial{position:relative;width:56px;height:56px;touch-action:none;cursor:grab}
.dial.dragging{cursor:grabbing}
.dial-face{width:56px;height:56px;filter:drop-shadow(0 5px 10px rgba(0,0,0,.6));
  transition:transform .18s var(--snap)}
.dial-face svg{width:100%;height:100%;display:block}
.dial.dragging .dial-face{transform:scale(1.06)}
.dial::after{content:'';position:absolute;inset:-5px;border-radius:50%;
  border:1px solid rgba(232,206,134,.55);box-shadow:0 0 16px rgba(232,206,134,.22);
  pointer-events:none;opacity:.9}
/* the stack only exists while the finger is down */
.dial-stack{position:absolute;left:0;bottom:0;width:56px;height:56px;overflow:visible;
  opacity:0;pointer-events:none;transition:opacity .16s var(--ease)}
.dial.dragging .dial-stack{opacity:1}
.dial-opt{position:absolute;left:8px;width:40px;height:40px;bottom:8px;
  transform:translateX(calc(var(--i) * 46px)) scale(.82);
  transition:transform .22s var(--ease),opacity .22s;opacity:.5;
  filter:drop-shadow(0 3px 6px rgba(0,0,0,.6))}
.dial-opt.on{opacity:0}
.dial-opt svg{width:100%;height:100%;display:block}
/* a hint that it is draggable, shown until first use */
.dial-hint{position:absolute;left:50%;transform:translateX(-50%);bottom:-2px;
  font-size:9px;letter-spacing:.18em;color:var(--ivory-dim);pointer-events:none}

/* ═══ playboard: deeper materials ════════════════════════════════════════ */
.cellface{stroke:rgba(239,233,220,.16);stroke-width:1}
.felt, .sheet-felt{position:relative}
/* inlaid brass border around the whole layout */
.felt svg, .sheet-felt svg{filter:drop-shadow(0 6px 14px rgba(0,0,0,.55))}

/* ═══ round clock ════════════════════════════════════════════════════════
   The countdown is the metronome of a live table. It has to be legible at a
   glance and get visibly urgent, or it is just a number.                  */
.timer{width:42px;height:42px;font-size:17px;font-weight:500;
  font-feature-settings:'tnum' 1;
  border:1.5px solid var(--brass);color:var(--brass-hi);
  background:radial-gradient(circle at 50% 35%,rgba(20,24,22,.92),rgba(7,10,9,.9));
  box-shadow:0 4px 14px rgba(0,0,0,.6), 0 0 0 4px rgba(7,10,9,.35)}
.timer.closing{border-color:#D6533C;color:#FFC9BC;
  animation:urgent .9s ease-in-out infinite}
@keyframes urgent{50%{box-shadow:0 4px 14px rgba(0,0,0,.6),0 0 0 4px rgba(214,83,60,.28);
  transform:scale(1.06)}}

/* ═══ payout: chips leave the felt ═══════════════════════════════════════ */
.paylayer{position:fixed;inset:0;pointer-events:none;z-index:55}
.pay{position:absolute;width:38px;height:38px;opacity:0;
  filter:drop-shadow(0 6px 10px rgba(0,0,0,.65))}
.pay svg{width:100%;height:100%;display:block}
.pay.win{animation:payWin .85s var(--ease) forwards}
.pay.lose{animation:payLose .55s cubic-bezier(.5,0,.9,.4) forwards}
@keyframes payWin{
  0%{opacity:1;transform:translate(var(--x0),var(--y0)) scale(1)}
  22%{transform:translate(var(--x0),calc(var(--y0) - 16px)) scale(1.16)}
  100%{opacity:0;transform:translate(var(--x1),var(--y1)) scale(.4)}}
@keyframes payLose{
  0%{opacity:1;transform:translate(var(--x0),var(--y0)) scale(1) rotate(0)}
  100%{opacity:0;transform:translate(var(--x1),var(--y1)) scale(.8) rotate(28deg)}}

/* ═══ win tiers ══════════════════════════════════════════════════════════ */
.result.t-big .win{font-size:44px}
.result.t-huge .win{font-size:56px}
.result.t-huge .winlbl{letter-spacing:.5em;text-indent:.5em}
.result.t-big .card.main,.result.t-huge .card.main{
  box-shadow:0 1px 0 rgba(255,255,255,.9) inset,0 0 0 1px rgba(232,206,134,.9),
             0 0 34px rgba(232,206,134,.5),0 10px 24px -8px rgba(0,0,0,.85)}
.result.t-huge::before{content:'';position:absolute;inset:-60px;pointer-events:none;
  background:radial-gradient(circle,rgba(232,206,134,.24) 0%,transparent 62%);
  animation:halo 1.4s var(--ease) forwards}
@keyframes halo{from{opacity:0;transform:scale(.6)}60%{opacity:1}to{opacity:.55;transform:scale(1)}}

/* ═══ dock buttons: hierarchy ════════════════════════════════════════════ */
.tool.ghost{opacity:.72;background:rgba(239,233,220,.03)}
.tool.key{background:linear-gradient(rgba(212,166,42,.20),rgba(212,166,42,.07));
  border-color:rgba(212,166,42,.42)}
.tool.key svg{fill:var(--brass-hi)}.tool.key span{color:var(--brass-hi)}
.tool.auto[aria-pressed="true"]{background:linear-gradient(#E8CE86,#B08D3F);
  border-color:#E8CE86}
.tool.auto[aria-pressed="true"] svg{fill:#171208}
.tool.auto[aria-pressed="true"] span{color:#171208}
.tools{gap:8px}

/* ═══ statistics sheet ═══════════════════════════════════════════════════ */
.stats{position:fixed;left:0;right:0;bottom:0;top:auto;max-height:76vh;z-index:70;
  display:flex;flex-direction:column;
  background:linear-gradient(180deg,rgba(13,17,16,.97),rgba(7,10,9,.99));
  backdrop-filter:blur(16px);border-top:1px solid rgba(212,166,42,.3);
  -webkit-backdrop-filter:blur(16px);border-top:1px solid rgba(212,166,42,.3);
  border-radius:16px 16px 0 0;transform:translateY(100%);
  transition:transform .4s var(--ease);padding-bottom:env(safe-area-inset-bottom)}
body.stats-open .stats{transform:none}
.stats-body{overflow-y:auto;padding:4px 16px 18px;display:flex;flex-direction:column;gap:14px}
.stat-row{display:flex;align-items:center;gap:10px}
.stat-row .lbl{width:70px;font-size:9.5px;font-weight:400;letter-spacing:.2em;
  text-transform:uppercase;color:var(--ivory-dim)}
.meter{flex:1;height:8px;border-radius:4px;background:rgba(239,233,220,.08);overflow:hidden;
  display:flex}
.meter i{display:block;height:100%}
.meter i.a{background:linear-gradient(90deg,#A32734,#75121C)}
.meter i.b{background:linear-gradient(90deg,#33363A,#191A1C)}
.meter i.c{background:linear-gradient(90deg,#E8CE86,#B08D3F)}
.stat-row .val{width:74px;text-align:right;font-size:12px;color:var(--ivory);
  font-variant-numeric:tabular-nums}
.stat-head{font-family:'Bodoni Moda',serif;font-size:12px;letter-spacing:.28em;
  text-transform:uppercase;color:var(--brass-hi);margin-top:4px}
.numrow{display:flex;gap:6px;flex-wrap:wrap}
.numrow .pipn{width:32px;height:32px;border-radius:6px;display:grid;place-items:center;
  font-size:14px;font-weight:700;box-shadow:0 2px 5px rgba(0,0,0,.5)}
.numrow .pipn small{display:block;font-size:8px;opacity:.75;font-weight:500}

/* ═══ racetrack ══════════════════════════════════════════════════════════ */
.track{position:relative;flex:0 0 auto;background:var(--panel);
  border-top:1px solid var(--rule);overflow:hidden;
  max-height:0;transition:max-height .4s var(--ease)}
body.track-open .track{max-height:200px}
.track-tab{position:absolute;left:50%;top:0;transform:translate(-50%,-100%);
  z-index:2;min-height:30px;padding:7px 22px 8px;border-radius:9px 9px 0 0;
  font-size:10px;letter-spacing:.24em;color:var(--brass-hi);
  background:linear-gradient(var(--panel-hi),var(--panel));
  border:1px solid var(--rule);border-bottom:none}
.track svg{width:100%;height:190px;display:block;padding:4px 6px}
body.track-open .track-tab{color:#171208;background:linear-gradient(#E8CE86,#B08D3F)}

/* replay: a subtle letterbox + label so it is unmistakably a replay and not
   the round running twice */
.stage video.replay{filter:contrast(1.06) saturate(1.05)}
.stage video.replay ~ .scrim{box-shadow:inset 0 0 0 3px rgba(212,166,42,.35)}
body[data-phase="spinning"] .stage video.replay{filter:contrast(1.06)}

/* The racetrack tab reads best as a slim ledge, so its hit area is extended
   downward with a pseudo-element rather than by making the tab taller. */
.track-tab{position:relative}
.track-tab::before{content:'';position:absolute;left:0;right:0;top:-7px;bottom:-7px}

/* macOS renders this stack heavy without it — principle 8 */
html{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}

/* Scale-on-press standardised to 0.96. Anything below ~0.95 reads as a
   cartoon bounce rather than a physical press. */
.tool:active:not(:disabled){transform:scale(.96)}
.icon:active{transform:scale(.96)}
.sheet-done:active{transform:translateY(1px) scale(.98)}

/* headings should not orphan a word */
.title,.sheet-head,.stat-head{text-wrap:balance}

/* ── no win ──────────────────────────────────────────────────────────────
   A loss still gets the plaque: the number is the result whether or not you
   backed it, and dropping the panel made the round feel like it had simply
   stopped. It is deliberately quieter — no gold figure, thinner border, no
   sparks — so a win still reads as an event by contrast. */
.result:not(.won) .win{display:none}
.result:not(.won) .winblock{padding:32px 44px 18px;
  background:linear-gradient(180deg,rgba(10,13,12,.94) 0%,rgba(3,5,4,.97) 100%);
  box-shadow:0 1px 0 rgba(255,255,255,.07) inset,
             0 0 0 1px rgba(176,141,63,.30),
             0 24px 54px -22px rgba(0,0,0,1)}
.result:not(.won) .winlbl{font-size:12px;letter-spacing:.44em;text-indent:.44em;
  color:rgba(243,236,220,.80);margin-top:2px}
.result:not(.won) .winblock::before,.result:not(.won) .winblock::after,
.result:not(.won) .rule::before,.result:not(.won) .rule::after{
  border-color:rgba(232,206,134,.45)}
.result:not(.won) .rule{width:96px}

/* When the result is up the footage steps back. A translucent plate over a
   full-brightness video always looks like a sticker; dimming the source is
   what makes it read as one composed frame. */
.stage.dim .scrim{box-shadow:inset 0 0 0 100vmax rgba(2,4,3,.52)}


/* ═══ broadcast furniture ════════════════════════════════════════════════
   Three beats play back to back — dealer, wide, result — and each is a
   separate render. What sells them as ONE continuous feed is the join:
   a vision-mixer dip plus a caption that carries across the cut, exactly
   like a live table. Without these the cuts read as three video files. */

/* the dip: a fast luminance swell under the crossfade. A real mixer never
   cuts on a black frame, it rides the two sources through each other, and
   the light lift is what your eye reads as "the camera changed". */
.dip{position:absolute;inset:0;pointer-events:none;opacity:0;
  background:radial-gradient(120% 80% at 50% 45%,rgba(255,247,230,.30),rgba(0,0,0,.55) 78%);
  mix-blend-mode:screen}
.dip.go{animation:dip .42s cubic-bezier(.3,0,.2,1)}
@keyframes dip{0%{opacity:0}22%{opacity:1}100%{opacity:0}}

/* lower-third: the caption the table's own graphics package would carry */
.lower{position:absolute;left:0;right:0;bottom:0;padding:0 clamp(14px,4.4vw,26px)
  calc(clamp(12px,3.6vw,20px) + env(safe-area-inset-bottom,0px));
  display:flex;align-items:center;gap:9px;pointer-events:none;
  opacity:0;transform:translateY(10px);
  transition:opacity .34s var(--ease),transform .34s var(--ease)}
.lower.show{opacity:1;transform:none}
.lower em{font-family:'Jost',system-ui,sans-serif;font-style:normal;font-weight:500;
  font-size:clamp(10px,2.9vw,12.5px);letter-spacing:.22em;text-transform:uppercase;
  color:rgba(255,252,246,.94);
  text-shadow:0 1px 10px rgba(0,0,0,.9),0 0 26px rgba(0,0,0,.6)}
.lower .dot{width:6px;height:6px;border-radius:50%;flex:none;
  background:#E5484D;box-shadow:0 0 0 3px rgba(229,72,77,.22),0 0 12px rgba(229,72,77,.75);
  animation:onair 1.9s var(--ease) infinite}
@keyframes onair{0%,100%{opacity:1}50%{opacity:.35}}
@media (prefers-reduced-motion:reduce){
  .lower .dot{animation:none}
  .dip.go{animation-duration:.01s}
}

/* ── dealer framing ──────────────────────────────────────────────────────
   The dealer clips are 16:9 masters. A portrait stage keeps only about 47%
   of that width, so WHICH 47% decides whether the dealer is in frame at all.
   Camera A (waiting / bets open / last chance) puts her centre-left, so the
   window sits left of centre and keeps both her and the wheel. betsclose
   frames her further right and needs its own offset — set per clip in JS. */
/* the longer fade used to hide the pose mismatch between two dealer takes */
.stage video.soft{transition:opacity .58s cubic-bezier(.4,0,.2,1)}
/* Dealer clips are cropped to 4:3 at build time with a per-take pan that puts
   her on the same mark in every one (tools/frame_dealer.py). The stage is 4:3
   too, so they fill it exactly — no object-position here, or the framing would
   be corrected twice. */

/* The dealer shot plays CLEAN — no caption, no scrim, no betting dim. It is
   the one image in the round that should read as untouched camera. */
/* The scrim is part of the RESULT MESSAGE, not part of the picture. While a
   clip is playing there is nothing over the footage at all — no vignette, no
   caption — so what you watch is the render, clean. It fades in only with the
   result and out again with it. */
.scrim{opacity:0;transition:box-shadow .45s var(--ease),opacity .34s var(--ease)}
.stage.dim .scrim{opacity:1}
body[data-phase="betting"] .stage #vidD.live{filter:none}

/* The wide beat's push-in is baked into the CLIP (crop 700x700 of the 1080
   master, re-encoded), not applied here. A CSS scale would crop differently
   at every viewport and soften on the ones that scale up; cropping the source
   gives one framing that is identical everywhere. Masters are kept under
   recordings/wide angle spins/_master/. */


/* ═══ SPIN ═══════════════════════════════════════════════════════════════
   The round waits for the player now, so this is the single primary action
   on the screen and has to read that way — full width, brass, and the only
   filled control in the dock. */
.spin{position:relative;width:100%;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:1px;
  min-height:56px;padding:9px 18px;border-radius:14px;cursor:pointer;
  border:1px solid rgba(232,206,134,.55);
  background:linear-gradient(180deg,#E8CE86 0%,#C9A44E 46%,#A8823A 100%);
  box-shadow:0 1px 0 rgba(255,255,255,.45) inset,
             0 -1px 0 rgba(0,0,0,.28) inset,
             0 10px 22px -12px rgba(201,164,78,.85);
  transition:transform .16s var(--ease),opacity .2s var(--ease),
             box-shadow .2s var(--ease),filter .2s var(--ease)}
.spin-lbl{font-family:'Bodoni Moda',Georgia,serif;font-weight:700;
  font-size:16px;letter-spacing:.3em;text-indent:.3em;text-transform:uppercase;
  color:#20170A}
.spin-sub{font-family:'Jost',system-ui,sans-serif;font-size:9px;font-weight:400;
  letter-spacing:.2em;text-indent:.2em;text-transform:uppercase;
  color:rgba(32,23,10,.62);font-variant-numeric:tabular-nums}
.spin:active:not(:disabled){transform:scale(.96)}
@media(hover:hover){.spin:hover:not(:disabled){filter:brightness(1.06)}}
.spin:disabled{cursor:default;opacity:.34;
  background:linear-gradient(180deg,#4A4436,#332F27);
  border-color:rgba(232,206,134,.16);box-shadow:none}
.spin:disabled .spin-lbl{color:rgba(255,250,240,.55)}
.spin:disabled .spin-sub{color:rgba(255,250,240,.34)}
/* while the wheel is running there is nothing to press */
body[data-phase="spinning"] .spin{opacity:.2;pointer-events:none}


/* diagnostics overlay (?debug=1) */
#diag{position:fixed;left:0;right:0;top:0;z-index:9999;margin:0;padding:8px 10px;
  font:10px/1.45 ui-monospace,Menlo,monospace;color:#9CFFC4;white-space:pre-wrap;
  background:rgba(0,0,0,.88);max-height:52vh;overflow:auto;pointer-events:auto}
#diag b{color:#FFD98A}
#diag .bad{color:#FF8A8A}

/* ═══════════════════════════════════════════════════════════════════════
   LIVE-TABLE LAYOUT
   Matched to the reference: the BOARD is the screen. A results reel runs
   edge to edge across the top, the feed is a shallow band under it with its
   controls floating on the picture, and everything sits on a deep crimson
   ground rather than baize — the felt colour lives in the board itself.
   ═══════════════════════════════════════════════════════════════════════ */
@media (orientation: portrait){

  body{
    background:
      radial-gradient(120% 70% at 50% 0%, #4A0F1E 0%, #2E0A14 42%, #1A050C 100%),
      #14040A;
  }

  /* ---- results reel: full bleed, no chrome around it ---- */
  .bar{
    display:block;padding:0;min-height:0;height:26px;
    background:#0B0406;border:0;box-shadow:none;
  }
  .bar .title{display:none}
  .reel{
    display:flex;gap:0;height:26px;width:100%;overflow:hidden;
    /* The base rule caps this at 34% — correct when the reel shared the header
       with a title and three icons, wrong now that the bar IS the reel. Left
       capped it filled a third of the width and left black across the rest. */
    max-width:none;
    /* newest first, reading left to right: pip() prepends, and row-reverse
       would push the live result to the far right where nobody looks */
    flex-direction:row;
    justify-content:flex-start;
  }
  .reel .pip{
    flex:1 1 0;min-width:0;height:26px;border-radius:0;margin:0;
    display:flex;align-items:center;justify-content:center;
    font-family:'Jost',system-ui,sans-serif;font-weight:500;
    font-size:11px;letter-spacing:0;color:#F3E9E2;
    font-variant-numeric:tabular-nums;box-shadow:none;
    /* the reference tints the NUMBER, not the cell */
    background:#0B0406 !important;
  }
  .reel .pip.r{color:#E8414C}
  .reel .pip.b{color:#F3E9E2}
  .reel .pip.g{color:#37B57E}
  /* newest result reads as the live one */
  .reel .pip:first-child{background:#B4141F !important;color:#fff}

  /* ---- the feed: a shallow band, full bleed ---- */
  .stage{
    aspect-ratio:auto;height:23vh;max-height:23vh;min-height:132px;
    flex:0 0 auto;border-radius:0;box-shadow:none;
  }
  .stage::after{opacity:.35}

  /* controls float ON the picture, stacked at the right */
  .stage-ctl{
    position:absolute;top:8px;right:8px;z-index:6;
    display:flex;flex-direction:column;gap:8px;
  }
  .stage-ctl .icon{
    width:34px;height:34px;min-width:34px;border-radius:50%;
    display:grid;place-items:center;padding:0;
    background:rgba(10,6,8,.62);border:1px solid rgba(255,255,255,.13);
    backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
    box-shadow:0 4px 12px -4px rgba(0,0,0,.7);
  }
  .stage-ctl .icon svg{width:16px;height:16px;fill:#F0E6DC}
  /* 34px is the visible size; the tap target is the full 44 */
  .stage-ctl .icon::after{content:'';position:absolute;inset:-5px}

  .stage-phase{
    position:absolute;left:10px;bottom:8px;z-index:6;pointer-events:none;
    font-family:'Jost',system-ui,sans-serif;font-size:9.5px;font-weight:400;
    letter-spacing:.24em;text-indent:.24em;text-transform:uppercase;
    color:rgba(255,250,244,.82);text-shadow:0 1px 8px rgba(0,0,0,.95);
  }

  /* ---- the board owns the rest of the screen ---- */
  .felt{
    flex:1 1 auto;min-height:0;background:none;border:0;box-shadow:none;
    padding:10px 12px 6px;place-items:center;
  }
  .felt::before,.felt::after{display:none}
  /* scale by HEIGHT and centre: the board has a fixed aspect and must not be
     stretched, and height is the scarce dimension on a phone */
  .felt svg{height:100%;width:auto;max-width:100%;
    filter:drop-shadow(0 8px 22px rgba(0,0,0,.55))}

  /* ---- floating actions, bottom right, over the felt ---- */
  .fabs{
    position:fixed;right:10px;z-index:55;
    bottom:calc(var(--dock-h,150px) + 10px + env(safe-area-inset-bottom,0px));
    display:flex;flex-direction:column;gap:10px;
  }
  .fab{
    width:40px;height:40px;border-radius:50%;display:grid;place-items:center;
    background:rgba(18,8,12,.78);border:1px solid rgba(255,255,255,.12);
    color:#EFE3D8;cursor:pointer;
    backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
    box-shadow:0 6px 16px -6px rgba(0,0,0,.8);
    transition:transform .16s var(--ease),background .2s var(--ease);
  }
  .fab svg{width:19px;height:19px;fill:currentColor}
  .fab:active{transform:scale(.96)}

  /* ---- dock slims down: the reference keeps status quiet at the base ---- */
  .dock{background:linear-gradient(180deg,rgba(20,6,12,.72),rgba(12,3,7,.96));
    border-top:1px solid rgba(255,255,255,.07);box-shadow:none}
  .ledger{font-size:10.5px;letter-spacing:.1em;color:rgba(245,235,228,.72)}
  .ledger b{color:#F2C46A}
}

/* the fabs are a portrait affordance only */
@media (orientation: landscape){ .fabs{display:none} }


/* The board is the screen, so the base must give way to it: the chip in hand,
   one row of tools, a slim commit bar and a status line — nothing more. */
@media (orientation: portrait){
  .dock{padding:7px 10px calc(7px + env(safe-area-inset-bottom,0px));gap:7px}
  .dial{width:46px;height:46px}
  .dial-face svg{width:46px;height:46px}
  .tool{width:44px;padding:4px 0}
  .tool svg{width:14px;height:14px}
  .tool span{font-size:7.5px;letter-spacing:.18em;text-indent:.18em}
  .spin{min-height:42px;padding:5px 16px;border-radius:11px}
  .spin-lbl{font-size:13px;letter-spacing:.26em;text-indent:.26em}
  .spin-sub{font-size:8px}
  .ledger{font-size:10px}
}


/* The countdown is a quiet status read, not a scoreboard. The reference keeps
   it small and out of the picture's way; a large disc over the dealer's face
   is the single most intrusive thing that was on the feed. */
@media (orientation: portrait){
  .timer{
    top:8px;left:8px;right:auto;bottom:auto;
    width:auto;min-width:0;height:22px;padding:0 9px;border-radius:11px;
    display:flex;align-items:center;justify-content:center;
    background:rgba(10,6,8,.62);border:1px solid rgba(255,255,255,.13);
    backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
    box-shadow:none;
  }
  .timer span{font-family:'Jost',system-ui,sans-serif;font-size:11px;
    font-weight:500;letter-spacing:.06em;color:#F2E7DC;
    font-variant-numeric:tabular-nums}
  .timer.closing span{color:#FF8A8A}
  .timer::before,.timer::after{display:none}
}

/* The chip in hand sat on its own row, which cost 60px of board for one
   token. Put it beside the tools — the board is the thing that needs the
   height, and the reference keeps its whole base to a couple of rows. */
@media (orientation: portrait){
  .dock{
    display:grid;align-items:center;gap:8px 10px;
    grid-template-columns:auto 1fr;
    grid-template-areas:"chip tools" "led led";
  }
  .chips{grid-area:chip;margin:0}
  .tools{grid-area:tools;justify-content:space-between}
  .spin{grid-area:spin}
  .ledger{grid-area:led}
  .dial{width:44px;height:44px}
  .dial-face svg{width:44px;height:44px}
  /* The dealer plates are 16:9. Match the band to them exactly and the whole
     frame is on screen — dealer, table, wheel — with nothing cropped away.
     (I had cropped these to 4:3 to normalise her position between takes; that
     normalisation is what was eating the table, so it goes.) */
  .stage{aspect-ratio:16/9;height:auto;max-height:34vh;min-height:0}
}

/* ═══ the feed meets the table ══════════════════════════════════════════
   Two things the reference does that a hard-edged video band does not.

   1. The crop is anchored to the TOP. The band is much wider than it is
      tall, so a centred crop of a 4:3 dealer plate lands on her torso and
      takes her head off. Top-anchored keeps the face and the room, and
      gives up the near edge of the table, which carries nothing.

   2. The bottom edge FADES into the felt rather than stopping. A cut edge
      reads as a video embedded in an app; a fade reads as one surface.   */
/* Nothing to anchor now for the dealer — the band matches the plate, so the
   whole frame shows. The square wheel/result renders still get cropped by the
   band, and centre is right for those: the wheel sits in the middle. */
.stage video{object-position:50% 50%}
#vidD,#vidC{object-position:50% 50%}

.seam{
  /* The fade kills the EDGE, nothing more. Every percent of height it takes
     is a percent of table and wheel the player cannot see — and those are
     what make the band read as a live table rather than a portrait. */
  position:absolute;left:0;right:0;bottom:0;height:20%;
  pointer-events:none;z-index:4;
  background:linear-gradient(to bottom,
    rgba(38,9,19,0) 0%,
    rgba(38,9,19,.06) 50%,
    rgba(41,10,20,.42) 80%,
    #2B0912 100%);
}
/* the hairline rule was the hard edge the fade replaces */
.stage::after{display:none}

@media (orientation: portrait){
  /* the felt starts in exactly the colour the fade lands on, so the seam is
     invisible and the two surfaces read as continuous */
  .felt{background:linear-gradient(180deg,#2B0912 0%,rgba(43,9,18,0) 26%)}
}

/* ═══ the feed dissolves, it does not stop ══════════════════════════════
   Previously a gradient was painted OVER the video, ending in a flat colour
   chosen to match the felt. Any mismatch between that colour and the page
   behind it lands as a visible line, and the ramp arriving at full opacity
   over a short distance reads as a cut however dark it gets.

   Fade the VIDEO'S OWN PIXELS instead. The picture thins out to nothing and
   whatever is behind shows through continuously, so there is no colour to
   match and no edge to land. The stops trace an ease curve rather than a
   straight line — a linear alpha ramp has a visible "shoulder" where it
   leaves opaque, and that shoulder is what reads as sharp.
   Only the video is masked, so the controls and the result card stay crisp. */
.stage video{
  -webkit-mask-image:linear-gradient(to bottom,
    #000 0%, #000 46%,
    rgba(0,0,0,.985) 54%, rgba(0,0,0,.95) 60%,
    rgba(0,0,0,.885) 65%, rgba(0,0,0,.79) 70%,
    rgba(0,0,0,.67) 75%, rgba(0,0,0,.53) 80%,
    rgba(0,0,0,.385) 85%, rgba(0,0,0,.245) 90%,
    rgba(0,0,0,.125) 95%, rgba(0,0,0,.04) 98%,
    transparent 100%);
          mask-image:linear-gradient(to bottom,
    #000 0%, #000 46%,
    rgba(0,0,0,.985) 54%, rgba(0,0,0,.95) 60%,
    rgba(0,0,0,.885) 65%, rgba(0,0,0,.79) 70%,
    rgba(0,0,0,.67) 75%, rgba(0,0,0,.53) 80%,
    rgba(0,0,0,.385) 85%, rgba(0,0,0,.245) 90%,
    rgba(0,0,0,.125) 95%, rgba(0,0,0,.04) 98%,
    transparent 100%);
}
/* the painted gradient is what made the edge; it is no longer needed */
.seam{display:none}
/* and the felt must not re-darken the join it now shares with the page */
@media (orientation: portrait){ .felt{background:none} }
/* the stage itself must be transparent, or its black shows under the fade */
.stage{background:transparent}

/* ═══ one control rail ══════════════════════════════════════════════════
   Settings, statistics and sound used to float on the picture while rebet
   and the racetrack sat on the felt — two stacks doing the same job in two
   places. They are now one list down the right edge, so there is a single
   place to look for a control. The video is left clean apart from the
   countdown, which is status rather than a control. */
@media (orientation: portrait){
  .stage-ctl{display:none}

  /* the moved buttons still carry .icon from the header they came from;
     neutralise it so the rail styles them, not the old rules */
  .fabs .icon{
    width:40px;height:40px;min-width:0;padding:0;border-radius:50%;
    background:rgba(18,8,12,.78);border:1px solid rgba(255,255,255,.12);
    backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
    box-shadow:0 6px 16px -6px rgba(0,0,0,.8);
  }
  .fabs .icon svg{width:18px;height:18px;fill:#EFE3D8}
  .fabs .icon::after{content:none}          /* the overlay's hit-area padding */

  /* five buttons is a tall rail, so tighten it and keep it off the board */
  .fabs{gap:8px}

  /* sound reads as OFF at a glance rather than needing a different glyph */
  .fabs #btnSound[aria-pressed="false"]{opacity:.45}
  .fabs #btnSound[aria-pressed="false"] svg{fill:#C9BDB2}
}

/* ═══ PORTRAIT LOCK ═════════════════════════════════════════════════════
   Three layers, because no single one covers every device:

     manifest orientation  — an installed app never rotates at all
     screen.orientation    — Chrome/Android honours it in fullscreen
     this gate             — iOS Safari honours NEITHER of the above, so a
                             phone turned sideways still needs catching

   The gate targets PHONES held sideways, not "landscape": a desktop is always
   landscape and must keep the wide layout. Coarse pointer plus a short
   viewport is what actually distinguishes the two.                        */
.rotate{
  position:fixed;inset:0;z-index:999;display:none;
  place-items:center;text-align:center;
  background:radial-gradient(120% 80% at 50% 40%,#3A0C18,#140309 70%);
  padding:24px;
}
.rotate-inner{display:grid;gap:10px;justify-items:center;max-width:320px}
.rotate svg{width:54px;height:54px;fill:var(--brass-hi,#E8CE86);
  animation:rotateHint 2.6s cubic-bezier(.4,0,.2,1) infinite}
.rotate p{margin:0;font-family:'Bodoni Moda',Georgia,serif;font-size:19px;
  letter-spacing:.14em;text-indent:.14em;text-transform:uppercase;color:#F6EDE2}
.rotate span{font-family:'Jost',system-ui,sans-serif;font-size:11.5px;
  letter-spacing:.2em;text-indent:.2em;text-transform:uppercase;
  color:rgba(246,237,226,.55)}
@keyframes rotateHint{
  0%,18%{transform:rotate(-90deg)} 46%,72%{transform:rotate(0)} 100%{transform:rotate(-90deg)}
}
@media (prefers-reduced-motion:reduce){ .rotate svg{animation:none} }

/* a sideways phone: coarse pointer AND not enough height to play */
@media (orientation:landscape) and (pointer:coarse) and (max-height:560px){
  .rotate{display:grid}
  /* stop the round being audible/played behind the gate */
  .stage,.felt,.dock,.bar,.fabs,.sheet,.track{visibility:hidden}
}

/* ═══ OPENING ═══════════════════════════════════════════════════════════
   A staged reveal rather than a static splash: ground, then light, then the
   mark, then the invitation. Each beat is ~140ms behind the last, which is
   what makes a title feel authored instead of dumped on screen.            */
.opening{
  position:fixed;inset:0;z-index:900;display:grid;
  grid-template-rows:1fr auto;place-items:center;
  padding:28px 22px calc(18px + env(safe-area-inset-bottom,0px));
  background:
    radial-gradient(120% 78% at 50% 34%, #52122333 0%, transparent 62%),
    radial-gradient(150% 100% at 50% 8%, #3A0C18 0%, #1A050C 52%, #0B0206 100%);
  opacity:1;transition:opacity .62s var(--ease), visibility .62s;
}
.opening.gone{opacity:0;visibility:hidden;pointer-events:none}

/* a slow breathing pool of light behind the mark — the room, not a gradient */
.op-glow{position:absolute;left:50%;top:34%;width:132vmin;height:132vmin;
  transform:translate(-50%,-50%);pointer-events:none;
  background:radial-gradient(circle,rgba(232,206,134,.16) 0%,rgba(176,141,63,.07) 34%,transparent 66%);
  animation:opBreathe 7.5s ease-in-out infinite;
  opacity:0;animation-delay:.1s}
@keyframes opBreathe{0%,100%{opacity:.75;transform:translate(-50%,-50%) scale(1)}
                     50%{opacity:1;transform:translate(-50%,-50%) scale(1.06)}}

/* faint radial shafts, like light off a wheel's spokes */
.op-rays{position:absolute;left:50%;top:34%;width:150vmin;height:150vmin;
  transform:translate(-50%,-50%);pointer-events:none;opacity:.20;
  background:repeating-conic-gradient(from 0deg at 50% 50%,
    rgba(232,206,134,.22) 0deg 2deg, transparent 2deg 22deg);
  mask-image:radial-gradient(circle,transparent 22%,#000 46%,transparent 74%);
  -webkit-mask-image:radial-gradient(circle,transparent 22%,#000 46%,transparent 74%);
  animation:opTurn 64s linear infinite}
@keyframes opTurn{to{transform:translate(-50%,-50%) rotate(360deg)}}

.op-stack{display:grid;justify-items:center;gap:20px;width:100%;max-width:520px;
  position:relative;z-index:2}

/* ---- the mark ---- */
.op-logo{position:relative;width:100%;display:grid;place-items:center;
  filter:drop-shadow(0 18px 40px rgba(0,0,0,.75));
  opacity:0;transform:translateY(16px) scale(.965);
  animation:opRise .95s var(--ease) .18s forwards}
/* The mark is 1448x1086 (4:3) with the wheel arcing above the wordmark — a
   tall composition, not the wide banner the layout first assumed. Cap by
   HEIGHT so it cannot crowd the CTA on a short phone, and let width follow. */
/* With the photograph gone the mark is the only image on screen, so it can
   carry more of it. Still capped by height so it never crowds the CTA. */
.op-logo img{width:auto;max-width:min(96%,470px);
  max-height:min(52vh,430px);height:auto;display:block;margin:0 auto}
.op-logo.missing img{display:none}
.op-logo .op-fallback{display:none}
.op-logo.missing .op-fallback{
  display:grid;justify-items:center;gap:6px;text-align:center;padding:8px 0}
.op-f1{font-family:'Bodoni Moda',Georgia,serif;font-weight:700;
  font-size:clamp(30px,10vw,52px);line-height:1;letter-spacing:.02em;
  background:linear-gradient(180deg,#FBEFC8 8%,#E8CE86 38%,#B08D3F 62%,#F3E2AE 100%);
  -webkit-background-clip:text;background-clip:text;color:transparent}
.op-f2{font-family:'Bodoni Moda',Georgia,serif;font-size:clamp(14px,4.4vw,19px);
  letter-spacing:.3em;text-indent:.3em;text-transform:uppercase;color:var(--ivory-dim)}
.op-f2 em{font-style:italic;text-transform:none;letter-spacing:.04em;
  text-indent:0;color:var(--brass-hi)}

/* a single sheen sweeping the gold once the mark has landed */
.op-sheen{
  /* Masked BY THE LOGO, so the highlight travels across the gold and stops at
     the artwork's edge. Unmasked it swept the whole image box — and a PNG is
     mostly transparent — so the sweep showed as a pale band over the burgundy
     beside the mark rather than a shine on it. */
  position:absolute;inset:0;pointer-events:none;overflow:hidden;
  width:min(96%,470px);margin:0 auto;left:0;right:0;
  -webkit-mask-image:url("art/logo.webp");mask-image:url("art/logo.webp");
  -webkit-mask-size:contain;mask-size:contain;
  -webkit-mask-position:center;mask-position:center;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  mix-blend-mode:screen}
.op-sheen::before{content:'';position:absolute;top:-30%;bottom:-30%;width:22%;
  left:-40%;transform:skewX(-18deg);
  background:linear-gradient(90deg,transparent,rgba(255,248,226,.55),transparent);
  animation:opSheen 2.4s var(--ease) 1.15s 2}
@keyframes opSheen{to{left:130%}}

@keyframes opRise{to{opacity:1;transform:none}}

/* ---- the invitation ---- */
.op-cta{
  grid-row:3;
  appearance:none;border:0;cursor:pointer;width:min(380px,90%);
  display:grid;gap:3px;justify-items:center;padding:15px 26px;border-radius:15px;
  background:linear-gradient(180deg,#F3E2AE 0%,#E8CE86 32%,#C9A44E 66%,#A8823A 100%);
  box-shadow:0 1px 0 rgba(255,255,255,.55) inset,0 -1px 0 rgba(0,0,0,.28) inset,
             0 14px 34px -14px rgba(201,164,78,.9);
  opacity:0;transform:translateY(12px);
  animation:opRise .8s var(--ease) .74s forwards;
  transition:transform .16s var(--ease),filter .2s var(--ease)}
.op-cta:active{transform:scale(.96)}
@media(hover:hover){.op-cta:hover{filter:brightness(1.05)}}
/* Jost rather than Bodoni. The logo is already doing the ornate work — a
   second decorative serif underneath it competes instead of supporting, and a
   clean geometric sans reads as the modern, tappable half of the pair.

   ONE LINE, guaranteed two ways: nowrap so it can never break, and a fluid
   size so a narrow phone shrinks the type instead of wrapping it. It fitted
   at 375px and broke on smaller screens, which is exactly the case a fixed
   size does not cover. */
.op-cta-lbl{
  font-family:'Jost',system-ui,-apple-system,sans-serif;
  font-weight:600;
  font-size:clamp(12.5px,4.1vw,16.5px);
  letter-spacing:.2em;text-indent:.2em;text-transform:uppercase;
  white-space:nowrap;color:#20170A}
.op-cta-sub{font-family:'Jost',system-ui,sans-serif;font-size:clamp(8px,2.6vw,10px);
  font-weight:400;letter-spacing:.18em;text-indent:.18em;text-transform:uppercase;
  white-space:nowrap;color:rgba(32,23,10,.6)}
.op-cta[disabled]{filter:grayscale(.45) brightness(.8);cursor:default}

/* loading hairline — only appears if the clips are genuinely still coming */
.op-load{width:min(360px,86%);height:2px;border-radius:2px;overflow:hidden;
  background:rgba(232,206,134,.16);opacity:0;transition:opacity .3s var(--ease)}
.op-load.show{opacity:1}
.op-load i{display:block;height:100%;width:38%;border-radius:2px;
  background:linear-gradient(90deg,transparent,var(--brass-hi),transparent);
  animation:opSlide 1.25s ease-in-out infinite}
@keyframes opSlide{0%{transform:translateX(-110%)}100%{transform:translateX(320%)}}

.op-legal{margin:0;font-family:'Jost',system-ui,sans-serif;font-size:9px;
  letter-spacing:.24em;text-indent:.24em;text-transform:uppercase;
  color:rgba(239,233,220,.3);z-index:2;
  opacity:0;animation:opRise .8s var(--ease) 1.1s forwards}

@media (prefers-reduced-motion:reduce){
  .op-glow,.op-rays,.op-sheen::before,.op-load i{animation:none}
  .op-logo,.op-cta,.op-legal{animation-duration:.01s}
}

/* The opening sits on Veronica's own studio rather than an abstract gradient —
   the same room the dealer plates were shot in, so the title and the table
   are visibly the same product. Heavily darkened and blurred: it is a ground
   for the mark, not a photograph to look at. */
.op-hero{display:none}

@keyframes opFade{to{opacity:1}}
/* the abstract shafts are redundant now — keep them, but barely */
.opening .op-rays{opacity:.08}
@media (prefers-reduced-motion:reduce){ .op-hero{animation-duration:.01s} }

/* ═══ OPENING — burgundy ════════════════════════════════════════════════
   Photograph dropped: the mark already carries Veronica and the wheel, so a
   picture behind it was a second image competing with the first. What is
   left is the mark, the invitation, and a ground built to look expensive.

   Depth comes from four stacked layers rather than one flat colour: a warm
   pool where the mark sits, a cool falloff at the edges, a fine vertical
   weave at very low opacity so the red reads as cloth instead of gradient,
   and a vignette to close the corners.                                   */
.opening{
  background:
    /* warm pool behind the mark */
    radial-gradient(78% 52% at 50% 33%, rgba(168,36,58,.42) 0%, rgba(120,20,38,.16) 46%, transparent 72%),
    /* the body of the burgundy */
    radial-gradient(130% 92% at 50% 26%, #6E1226 0%, #4A0C1A 34%, #2A0710 68%, #14030A 100%),
    #14030A;
}
.op-hero{display:none}

/* velvet weave — invisible as texture, present as richness */
.opening::before{
  content:'';position:absolute;inset:0;pointer-events:none;z-index:0;
  background:
    repeating-linear-gradient(90deg, rgba(255,255,255,.028) 0 1px, transparent 1px 3px),
    repeating-linear-gradient(0deg,  rgba(0,0,0,.05)       0 1px, transparent 1px 4px);
  mix-blend-mode:overlay;opacity:.55;
}
/* corner vignette, so the panel has edges */
.opening::after{
  content:'';position:absolute;inset:0;pointer-events:none;z-index:0;
  background:radial-gradient(112% 78% at 50% 40%, transparent 44%, rgba(9,1,5,.62) 100%);
}

/* the gold pool warms up against red, and the shafts read as light on cloth */
.op-glow{background:radial-gradient(circle,
  rgba(255,214,140,.20) 0%, rgba(201,164,78,.09) 36%, transparent 68%)}
.opening .op-rays{opacity:.10}

/* a hairline gold rule under the mark — the one piece of furniture */
.op-stack::after{
  content:'';grid-row:2;width:min(300px,72%);height:1px;margin:-6px 0 2px;
  background:linear-gradient(90deg,transparent,rgba(232,206,134,.5),transparent);
  opacity:0;animation:opRise .8s var(--ease) .62s forwards;
}
.op-legal{color:rgba(246,230,226,.34)}


/* The Spin button is gone: the dealer take is the betting window and the wheel
   goes when it ends, so there was nothing for it to do. Its dock row went with
   it — that height belongs to the board, which is the thing you actually touch. */
.spin{display:none}
@media (orientation: portrait){
  .dock{gap:6px}
  .ledger{padding-top:2px}
}
