/* ═══════════════════════════════════════════════════════════════════════════
   Deck engine — presentation chrome.

   Slides are authored at a fixed 1600×900 and scaled to fit whatever screen
   they land on, so type and spacing keep their exact proportions on a venue
   projector, a laptop, or a 4K TV. Nothing reflows mid-talk.

   Theme-agnostic. Load this first and a theme stylesheet after it: the theme
   defines the tokens below — per [data-mode] when it has more than one
   register — and its own components, and the cascade lets it override anything
   here. Every token has a fallback, so the engine runs with no theme at all.

     --font                     body and chrome type
     --page / --ink             .is-page surface and ink
     --band-deep / --ink-dark   .slide surface and ink
     --band-alt                 .is-alt surface
     --accent                   progress bar, current-slide ring, key names
     --highlight                skipped marker, turn-your-phone glyph
   ═══════════════════════════════════════════════════════════════════════════ */

:root { --W:1600; --H:900; }

* { box-sizing:border-box; }
html, body { margin:0; padding:0; height:100%; background:#000; overflow:hidden; }
body { font-family:var(--font, system-ui, sans-serif); -webkit-font-smoothing:antialiased; }

#stage { position:fixed; inset:0; background:#000; }

/* The scaled 1600×900 canvas. JS sets --scale; nothing else moves.
   Centred by translate, not by the stage's grid alignment. Once the 1600px canvas
   is wider than the viewport — every phone — grid centring falls back to "safe"
   and pins the overflowing item to the start, which throws the slide off screen.
   translate(-50%,-50%) has no such fallback. */
#canvas {
  position:absolute; left:50%; top:50%;
  width:calc(var(--W) * 1px); height:calc(var(--H) * 1px);
  transform:translate(-50%, -50%) scale(var(--scale, 1));
  transform-origin:center center;
  overflow:hidden; background:var(--band-deep, #111);
}

/* Three surfaces: .slide is the dark band, .is-page the light page, .is-alt a
   second dark band. A theme with one surface sets them all alike. */
.slide {
  position:absolute; inset:0;
  display:flex; flex-direction:column;
  padding:80px;
  background:var(--band-deep, #111); color:var(--ink-dark, #fff);
  opacity:0; visibility:hidden;
  transition:opacity .32s cubic-bezier(0.22,1,0.36,1);
}
.slide[data-active] { opacity:1; visibility:visible; }
.slide.is-page { background:var(--page, #fff); color:var(--ink, #111); }
.slide.is-alt  { background:var(--band-alt, #222); color:var(--ink-dark, #fff); }

/* Slide number, bottom-left on every slide. One element in the canvas rather than
   markup repeated across fifty slides; deck.js writes the value and copies the
   active slide's colour so it reads on light and dark bands alike. Optional. */
#slideno {
  position:absolute; left:80px; bottom:26px; z-index:7;
  font:700 13px/1 var(--font, system-ui, sans-serif); letter-spacing:1.5px;
  opacity:.5; pointer-events:none;
}

/* Entrance animations — only on the active slide, and only once it lands */
@keyframes fadeUp { from { opacity:0; transform:translateY(16px); } to { opacity:1; transform:none; } }
@keyframes liftUp { from { opacity:0; transform:translateY(24px); } to { opacity:1; transform:none; } }
@keyframes ruleDraw { from { transform:scaleX(0); } to { transform:scaleX(1); } }
@keyframes slideL { from { opacity:0; transform:translateX(-32px); } to { opacity:1; transform:none; } }
@keyframes slideR { from { opacity:0; transform:translateX(32px); } to { opacity:1; transform:none; } }
@keyframes blurIn { from { opacity:0; filter:blur(3px); } to { opacity:1; filter:none; } }

.slide[data-active] [data-anim="fade"] { animation:fadeUp .55s cubic-bezier(0.22,1,0.36,1) both; }
.slide[data-active] [data-anim="lift"] { animation:liftUp .5s cubic-bezier(0.22,1,0.36,1) both; }
.slide[data-active] [data-anim="rule"] { animation:ruleDraw .45s cubic-bezier(0.22,1,0.36,1) both; transform-origin:left; }
.slide[data-active] [data-anim="left"] { animation:slideL .6s cubic-bezier(0.22,1,0.36,1) both; }
.slide[data-active] [data-anim="right"]{ animation:slideR .6s cubic-bezier(0.22,1,0.36,1) both; }
.slide[data-active] [data-anim="blur"] { animation:blurIn .65s cubic-bezier(0.22,1,0.36,1) both; }
.slide:not([data-active]) [data-anim] { animation:none; opacity:0; }

/* Stagger: data-delay="1".."8", 0.09s per step. */
.slide[data-active] [data-delay="1"] { animation-delay:0.09s; }
.slide[data-active] [data-delay="2"] { animation-delay:0.18s; }
.slide[data-active] [data-delay="3"] { animation-delay:0.27s; }
.slide[data-active] [data-delay="4"] { animation-delay:0.36s; }
.slide[data-active] [data-delay="5"] { animation-delay:0.45s; }
.slide[data-active] [data-delay="6"] { animation-delay:0.54s; }
.slide[data-active] [data-delay="7"] { animation-delay:0.63s; }
.slide[data-active] [data-delay="8"] { animation-delay:0.72s; }

/* ── Presenter chrome ─────────────────────────────────────────────────────
   deck.js builds all of this; nothing below needs to be in the markup. */

/* Progress bar — thin, unobtrusive, reads at the back of a room */
#progress { position:fixed; left:0; top:0; height:3px; background:var(--accent, #e33); z-index:60;
            width:0; transition:width .3s cubic-bezier(0.22,1,0.36,1); }

/* HUD: slide counter and the ? hint, bottom right. Both are buttons — the counter
   opens the overview, ? the key map — which is how a phone, with no keyboard,
   reaches every command. Fades when idle so it never photographs; a mouse move
   or a tap brings it back. Blends by difference so it reads on light and dark
   bands alike. */
#hud { position:fixed; bottom:16px; right:20px; z-index:60;
       display:flex; align-items:center; gap:14px;
       font:600 13px/1 var(--font, system-ui, sans-serif); letter-spacing:1px;
       color:#fff; opacity:.35; transition:opacity .4s; mix-blend-mode:difference; }
#hud:hover { opacity:.8; }
#hud button, #hud span { background:none; border:0; padding:0; margin:0; color:inherit;
                         font:inherit; letter-spacing:inherit; cursor:pointer; }
#hud .key { display:inline-flex; align-items:center; gap:6px; opacity:.6;
            font-size:11px; letter-spacing:.5px; text-transform:uppercase; }
#hud .key i { font-style:normal; font-size:13px; }
body.idle #hud { opacity:0; pointer-events:none; }
body.idle #navzones .arrow { opacity:0; }

/* On a touch screen there is no cursor to wake the HUD with, so it stays, and the
   ? becomes a round target the size of a fingertip. */
@media (pointer:coarse) {
  #hud, body.idle #hud { opacity:.55; pointer-events:auto; }
  #hud { gap:10px; }
  #hud #counter { padding:10px 0; }
  #hud .key span { display:none; }
  #hud .key i { display:grid; place-items:center; width:32px; height:32px; border-radius:50%;
                background:rgb(255 255 255 / .16); font-size:16px; font-weight:700; }
}

/* Click zones: left third back, right two-thirds forward. Arrows only appear
   on mouse movement, so a trackpad user gets affordance and a projector doesn't. */
#navzones { position:fixed; inset:0; z-index:55; display:flex; }
#navzones .zone { flex:1; cursor:pointer; display:flex; align-items:center; }
#navzones .zone.prev { flex:0 0 28%; justify-content:flex-start; padding-left:24px; }
#navzones .zone.next { justify-content:flex-end; padding-right:24px; }
#navzones .arrow { font-size:28px; color:#fff; opacity:.28; transition:opacity .3s;
                   user-select:none; text-shadow:0 2px 12px rgba(0,0,0,.6); }
#navzones .zone:hover .arrow { opacity:.75; }

/* Language switch, top right — authored markup, optional; see deck.js. */
#lang { position:fixed; top:14px; right:20px; z-index:60; display:flex; gap:4px;
        font:600 12px/1 var(--font, system-ui, sans-serif); letter-spacing:1px;
        opacity:.55; transition:opacity .4s;
        /* difference against the band: white on a dark band, ink on a light page, so the
           presenter can find it on either. */
        mix-blend-mode:difference; }
#lang:hover { opacity:1; }
body.idle #lang { opacity:0; }
#lang button { padding:7px 9px; border:0; border-radius:6px; background:transparent;
               color:#fff; font:inherit; letter-spacing:inherit; cursor:pointer;
               text-shadow:0 1px 6px rgba(0,0,0,.5); }
#lang button[aria-pressed="true"] { background:rgb(255 255 255 / .18); }
@media (pointer:coarse) { body.idle #lang { opacity:.55; } }

/* Turn-your-phone prompt (deck.js builds it; body.cramped shows it). A 16:9 slide
   on an upright phone is a strip a fifth of the screen tall, so say so rather than
   silently laying the canvas on its side. Above the nav zones so the dismiss button
   takes the tap, below #blank so B still covers everything. */
#turn { position:fixed; left:50%; bottom:20px; transform:translateX(-50%); z-index:70;
        display:none; align-items:center; gap:10px;
        max-width:calc(100vw - 28px); padding:11px 12px 11px 15px;
        border-radius:999px; background:rgba(12,24,50,.94); color:#fff;
        font:600 14px/1.25 var(--font, system-ui, sans-serif);
        box-shadow:0 6px 26px rgba(0,0,0,.5); }
body.cramped #turn { display:flex; }
#turn svg { flex:0 0 auto; width:21px; height:21px; color:var(--highlight, #fc3);
            animation:turn-tip 2.6s ease-in-out infinite; }
#turn button { flex:0 0 auto; margin-left:2px; width:26px; height:26px; padding:0;
               border:0; border-radius:50%; background:rgb(255 255 255 / .12);
               color:#fff; font:400 17px/1 system-ui, sans-serif; cursor:pointer; }
@keyframes turn-tip { 0%, 62%, 100% { transform:rotate(0deg); }
                      78%, 90% { transform:rotate(-88deg); } }

/* Blank screen (B or . — the standard clicker blank key). A tap restores it. */
#blank { position:fixed; inset:0; background:#000; z-index:80; display:none; }
body.blanked #blank { display:block; }
body.blanked-white #blank { display:block; background:#fff; }

/* Close button on the full-screen overlays, for touch; Escape does the same. */
#overview .close, #help .close {
  position:fixed; top:12px; right:14px; z-index:2; width:44px; height:44px;
  border:0; border-radius:50%; background:rgb(255 255 255 / .1); color:#fff;
  font:400 26px/1 system-ui, sans-serif; cursor:pointer;
}
#overview .close:hover, #help .close:hover { background:rgb(255 255 255 / .22); }

/* Overview grid (O, or the counter) — jump to any slide mid-talk without scrubbing */
#overview { position:fixed; inset:0; background:rgba(8,12,22,.96); z-index:90;
            display:none; grid-template-columns:repeat(auto-fill, minmax(190px, 1fr)); gap:18px;
            padding:40px; overflow:auto; align-content:start; }
/* Row height is set from JS (sizeThumbs). Everything in a card is absolutely
   positioned, so its intrinsic height is 0 — and neither aspect-ratio nor
   percentage padding contributes to intrinsic track sizing. Left to itself the
   grid gave every row an equal slice of the container and the cards overflowed
   into each other. */
body.overview #overview { display:grid; }
#overview .card { border-radius:8px; overflow:hidden; cursor:pointer; background:#161b26;
                  aspect-ratio:16/9; position:relative; box-shadow:0 0 0 1px rgba(255,255,255,.08); }
/* Live slide clones, scaled to card width. Everything animated is pinned to its
   finished state — a thumbnail mid-entrance reads as a broken slide. A theme with
   its own animations pins them the same way, against #overview .thumb. */
#overview .thumb { position:absolute; inset:0; overflow:hidden; }
#overview .thumb .slide {
  position:absolute; top:0; left:0; width:1600px; height:900px;
  transform:scale(var(--thumb-scale, .2)); transform-origin:top left;
  opacity:1 !important; visibility:visible !important; transition:none !important;
}
#overview .thumb .slide, #overview .thumb .slide * {
  animation:none !important; clip-path:none !important;
}
#overview .thumb .slide [data-anim] { opacity:1 !important; }
#overview .card[data-current] { box-shadow:0 0 0 3px var(--accent, #e33); }
/* Skipped slides (S): dimmed in the grid, tagged, still clickable so they can be
   restored. On stage the HUD counter says so; the slide itself is untouched. */
#overview .card[data-skip] .thumb { opacity:.28; filter:grayscale(1); }
#overview .card[data-skip] > span::after { content:'skipped'; float:right; font-size:10px; letter-spacing:1px;
                                           text-transform:uppercase; color:var(--highlight, #fc3); }
body.on-skipped #hud { opacity:.9; color:var(--highlight, #fc3); }
#overview .card > span { position:absolute; inset:auto 0 0 0; z-index:2; padding:10px 12px;
                       font:600 12px/1.3 var(--font, system-ui, sans-serif); color:#fff;
                       background:linear-gradient(to top, rgba(0,0,0,.92) 0%, rgba(0,0,0,.72) 60%, transparent 100%); }
#overview .card > b { position:absolute; z-index:2; top:8px; left:10px; font:700 11px/1 system-ui; color:#8b93a0; }
@media (pointer:coarse) { #overview { padding:64px 16px 24px; gap:14px; } }

/* Key map (?, or the HUD's ? button). Rows with an action are buttons, so on a
   phone this is the menu; on a laptop it is the same table, and clicking works too. */
#help { position:fixed; inset:0; background:rgba(8,12,22,.96); z-index:95; display:none;
        place-items:center; color:#fff; font-family:var(--font, system-ui, sans-serif); }
body.help #help { display:grid; }
#help > div { max-height:100vh; overflow:auto; padding:24px; }
#help table { border-collapse:collapse; font-size:15px; }
#help td { padding:7px 18px 7px 0; }
#help td:first-child { font-weight:700; color:var(--accent, #e33); white-space:nowrap; }
#help h3 { font-size:24px; margin:0 0 18px; }
#help button { background:none; border:0; padding:0; color:inherit; font:inherit;
               text-align:left; cursor:pointer; }
#help button:hover { text-decoration:underline; }
@media (pointer:coarse) {
  #help td { padding:11px 14px 11px 0; font-size:16px; }
  #help td:first-child { font-size:13px; }
}

/* Video slides: deck.js sets data-video-playing / data-video-missing on the slide. */
.slide video { width:100%; height:100%; object-fit:contain; background:#000; }
.video-wrap { position:absolute; inset:0; background:#000; display:grid; place-items:center; }
.video-hint { position:absolute; bottom:38px; left:50%; transform:translateX(-50%);
              font:600 14px/1 var(--font, system-ui, sans-serif); letter-spacing:1px;
              color:#fff; opacity:.55; text-transform:uppercase; pointer-events:none; }
.slide[data-video-playing] .video-hint { opacity:0; }
/* Missing media shouldn't be discovered on stage: the poster says so plainly. */
.video-missing { display:none; position:absolute; inset:0; place-items:center; text-align:center;
                 padding:80px; color:#fff; background:var(--band-deep, #111); }
/* Overlaid, not swapped in: hiding the <video> stops it loading, so a slow file
   could never clear the flag once the readiness deadline had set it. */
.slide[data-video-missing] .video-missing { display:grid; z-index:6; }

@media (prefers-reduced-motion: reduce) {
  .slide, #progress { transition:none; }
  .slide[data-active] [data-anim] { animation:none; }
  #turn svg { animation:none; }
}

/* Print / PDF export: every slide on its own page, chrome removed */
@media print {
  @page { size:1600px 900px; margin:0; }
  html, body { height:auto; overflow:visible; background:#fff; }
  #stage { position:static; display:block; }
  #canvas { position:static; transform:none; width:1600px; height:900px; }
  #progress, #hud, #navzones, #overview, #help, #blank, #turn, #lang { display:none !important; }
  .slide[data-skip] { display:none !important; }
  .slide { position:relative; opacity:1 !important; visibility:visible !important;
           page-break-after:always; break-after:page; }
  .slide [data-anim] { animation:none !important; opacity:1 !important; transform:none !important; }
}
