/* ==========================================================================
   GOVIND GATHA :: CREDIT ROLL v3
   Kothari International School

   v3 puts the Krishna Lila artwork on screen as looping video and keeps the
   centre of the frame for the children's names. Everything here serves one
   rule: the name is the content, the artwork is the frame around it. So the
   names sit on a soft cream panel that floats over the video, and they are
   large, heavy and dark. A parent at the back of the hall has to be able to
   read their child's name without effort.
   ========================================================================== */

:root {
  --stage-w: 1600px;
  --stage-h: 900px;

  /* Ink. Dark enough on cream to clear WCAG AAA at this size by a wide
     margin, which is the only contrast target that matters on a projector
     with the house lights half up. */
  --ink: #241a3d;
  --ink-soft: #4b3a63;
  --role: #8c2f14;
  --rule: rgba(140, 47, 20, .3);

  --panel: 255, 251, 241;
  --stagger: 1.35s;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

html, body {
  width: 100%; height: 100%;
  background: #0b0a12;
  overflow: hidden;
  font-family: "Trebuchet MS", "Segoe UI", system-ui, -apple-system, Roboto, sans-serif;
  -webkit-font-smoothing: antialiased;
  cursor: none;
}
body.showcursor, body.touch { cursor: default; }

#viewport { position: absolute; inset: 0; overflow: hidden; }

/* The stage is a fixed 1600x900 box pinned by its own centre and scaled to
   fit. Centring by translate rather than by a flex or grid parent, because a
   grid only centres an item that is smaller than its container and the stage
   is frequently larger than the window before scaling. */
#deck {
  position: absolute;
  left: 50%; top: 50%;
  width: var(--stage-w); height: var(--stage-h);
  transform: translate(-50%, -50%) scale(1);
  transform-origin: center center;
  overflow: hidden;
  background: #0b0a12;
  box-shadow: 0 0 140px rgba(0, 0, 0, .75);
}

/* ============================================================== slides === */

.slide {
  position: absolute; inset: 0;
  opacity: 0;
  visibility: hidden;
  transition: opacity .62s ease;
}
.slide.active { opacity: 1; visibility: visible; }

/* ------------------------------------------------------------- artwork --- */

.art { position: absolute; inset: 0; overflow: hidden; background: #14101f; }

.art video,
.art img.poster {
  position: absolute;
  left: 50%; top: 50%;
  width: 100%; height: 100%;
  transform: translate(-50%, -50%) scale(1.02);
  object-fit: cover;
  display: block;
}

/* The poster sits under the video so the very first paint is artwork rather
   than a black rectangle, and so a device that refuses to autoplay still
   shows the scene instead of nothing. */
.art img.poster { z-index: 0; }
.art video { z-index: 1; }

/* A slow drift on the artwork. The source clips are only six seconds long,
   so the loop point is visible if the frame is perfectly still; a long
   counter-motion on top of it hides the seam. */
.slide.active .art video,
.slide.active .art img.poster { animation: drift 34s ease-in-out infinite alternate; }
@keyframes drift {
  from { transform: translate(-50%, -50%) scale(1.02); }
  to   { transform: translate(-50%, -50%) scale(1.075); }
}

/* Corner vignette. Pulls the eye to the middle and stops the brightest
   backgrounds from bleaching out towards the edges of the screen. */
.art::after {
  content: "";
  position: absolute; inset: 0; z-index: 2;
  pointer-events: none;
  background:
    radial-gradient(ellipse 88% 82% at 50% 48%,
      rgba(20, 12, 30, 0) 52%, rgba(20, 12, 30, .1) 76%,
      rgba(20, 12, 30, .28) 100%);
}

/* ---------------------------------------------------------- name panel --- */

/* Soft-edged cream panel behind the names. No hard border: the fade to
   transparent at the rim lets the artwork's flowers and pillars run right up to
   the text without a visible box.

   Its strength is driven by two variables rather than being baked into three
   separate gradients:

     --pb   per-background base weight, set by the clip registry. Some artwork
            has a busy middle and needs more cover than others.
     --pa   the operator's master multiplier, driven by the slider on the B
            panel and remembered between sessions. 0 removes the panel
            completely, so the artwork shows through untouched.

   One gradient with calc() in the alpha stops, so dragging the slider changes
   every screen at once and there is no set of hand-tuned variants to keep in
   step. */
.panel {
  --pb: 1;
  position: absolute;
  left: 50%; top: 50%;
  width: 1230px; height: 700px;
  transform: translate(-50%, -50%);
  z-index: 3;
  border-radius: 48px;
  background:
    radial-gradient(ellipse 70% 64% at 50% 50%,
      rgba(var(--panel), calc(.95 * var(--pb) * var(--pa))) 0%,
      rgba(var(--panel), calc(.93 * var(--pb) * var(--pa))) 46%,
      rgba(var(--panel), calc(.84 * var(--pb) * var(--pa))) 68%,
      rgba(var(--panel), calc(.52 * var(--pb) * var(--pa))) 84%,
      rgba(var(--panel), calc(.18 * var(--pb) * var(--pa))) 94%,
      rgba(var(--panel), 0) 100%);
  /* Scaled by --pa too, or the artwork would still look smeared at zero. */
  -webkit-backdrop-filter: blur(calc(2.6px * var(--pa))) saturate(calc(1 + .07 * var(--pa)));
  backdrop-filter: blur(calc(2.6px * var(--pa))) saturate(calc(1 + .07 * var(--pa)));
}

/* Per-background weight and placement. Placement still has to be per clip:
   a panel centred over Krishna's face is careless however light it is. */
.panel.soft   { --pb: .82; }
.panel.strong { --pb: 1.08; }
.panel.left   { left: 38%; width: 1050px; }
.panel.right  { left: 62%; width: 1050px; }
.panel.narrow { width: 1020px; }
.panel.wide   { width: 1330px; }
.panel.tall   { height: 780px; }

/* -------------------------------------------------------------- names ---- */

.credits {
  position: absolute;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  z-index: 4;
  width: 1130px;
  display: grid;
  /* Column flow, not the default row flow. The names have to read straight
     down the left column and then straight down the right one, because that is
     also the order the children are introduced in; filling across the rows
     would make the reveal hop side to side. --rows is set by the player from
     the cast count. */
  grid-auto-flow: column;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: repeat(var(--rows, 5), auto);
  column-gap: 46px;
  row-gap: 4px;
  align-items: center;
  justify-items: center;
}
.panel.left ~ .credits  { left: 38%; width: 960px; }
.panel.right ~ .credits { left: 62%; width: 960px; }
.panel.narrow ~ .credits { width: 930px; }
.panel.wide ~ .credits   { width: 1230px; }

/* One column when a slide carries five names or fewer, so a short class does
   not leave half the panel empty. */
.credits.single { grid-template-columns: 1fr; width: 820px; }

/* The class chip needs its own small plate. Several of the backgrounds have a
   flower garland exactly where it sits, and letter-spaced small caps over
   petals is the one piece of text on screen that was hard to read. */
.classchip span {
  display: inline-block;
  padding: 6px 26px;
  border-radius: 999px;
  background: rgba(255, 251, 241, .82);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
}

.credit {
  text-align: center;
  padding: 6px 10px 10px;
  opacity: 0;
  will-change: transform, opacity, filter;
}

.nm {
  display: block;
  font-size: 50px;
  font-weight: 800;
  letter-spacing: .004em;
  line-height: 1.04;
  text-wrap: balance;
  color: var(--ink);
  text-shadow: 0 1px 0 rgba(255, 255, 255, .8), 0 2px 14px rgba(255, 250, 235, .82);
}

/* The role, in Devanagari only. The small caps English transliteration under it
   was dropped: nobody in the hall reads "MAKHAN CHOR" when माखन चोर is right
   above it, and the line it occupied is better spent making the Devanagari big
   enough to read from the back. */
.rl {
  display: block;
  margin-top: 6px;
  font-size: 38px;
  font-weight: 700;
  line-height: 1.14;
  color: var(--role);
  font-family: "Noto Sans Devanagari", "Nirmala UI", "Kohinoor Devanagari",
               "Devanagari Sangam MN", system-ui, sans-serif;
}

/* The school's slides run 7 to 12 names, which is four, five or six rows of
   two. Fewer rows means each name can be bigger; six rows has to come down or
   the block runs past the bottom of the stage. */
.credits.n7 .nm,  .credits.n8 .nm  { font-size: 50px; }
.credits.n7 .rl,  .credits.n8 .rl  { font-size: calc(52px + var(--rolebump, 0px)); }
.credits.n9 .nm,  .credits.n10 .nm { font-size: 48px; }
.credits.n9 .rl,  .credits.n10 .rl { font-size: calc(50px + var(--rolebump, 0px)); }
.credits.n11 .nm, .credits.n12 .nm { font-size: 44px; }
.credits.n11 .rl, .credits.n12 .rl { font-size: calc(46px + var(--rolebump, 0px)); }
.credits.n11 .credit, .credits.n12 .credit { padding: 3px 10px 6px; }
.credits.single .nm { font-size: 64px; }
.credits.single .rl { font-size: 54px; }

/* The class line that used to sit above the names is not drawn in v4. It was
   one more thing competing with the names for attention, which is the same
   reason the mythological scene titles went. SHOW.showClassLabel brings it
   back if a class ever needs identifying on screen. */
.classchip {
  position: absolute;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  /* 30px from the top of the 900px stage: 450 - 30 - half the chip height. */
  margin-top: -382px;
  z-index: 4;
  display: flex;
  align-items: center;
  gap: 22px;
  font-size: 34px;
  font-weight: 700;
  letter-spacing: .3em;
  text-transform: uppercase;
  color: #6b4a2e;
  opacity: 0;
  white-space: nowrap;
}
.classchip span {
  display: inline-block;
  padding: 6px 26px;
  border-radius: 999px;
  background: rgba(255, 251, 241, .8);
}
.slide.active .classchip { animation: chipIn .9s .15s ease-out forwards; }
@keyframes chipIn {
  from { opacity: 0; transform: translate(-50%, -50%) translateY(-14px); }
  to   { opacity: .82; transform: translate(-50%, -50%) translateY(0); }
}

/* ========================================================== title card === */

.titlecard {
  position: absolute;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  z-index: 4;
  width: 1180px;
  text-align: center;
}
/* When the artwork already carries the show name, the words sit low so they
   do not land on top of the painted title. */
.titlecard.lower {
  top: auto; bottom: 74px;
  transform: translate(-50%, 0);
}
.titlecard.lower .school,
.titlecard.lower .tag {
  display: inline-block;
  padding: 9px 34px;
  border-radius: 999px;
  background: rgba(255, 251, 241, .9);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
}
.titlecard.lower .tag { margin-top: 14px; font-size: 26px; }

.titlecard .school {
  font-size: 27px; font-weight: 700; letter-spacing: .34em;
  text-transform: uppercase; color: #6b4a2e;
  opacity: 0;
}
.titlecard .devnag {
  margin-top: 16px;
  font-size: 128px; font-weight: 800; line-height: 1.04;
  color: var(--ink);
  font-family: "Noto Sans Devanagari", "Nirmala UI", "Kohinoor Devanagari",
               "Devanagari Sangam MN", system-ui, sans-serif;
  text-shadow: 0 2px 0 rgba(255, 255, 255, .8), 0 4px 26px rgba(255, 246, 224, .95);
  opacity: 0;
}
.titlecard .roman {
  margin-top: 2px;
  font-size: 54px; font-weight: 800; letter-spacing: .18em;
  text-transform: uppercase; color: var(--role);
  opacity: 0;
}
.titlecard .flourish {
  margin: 22px auto 18px;
  width: 340px; height: 2px;
  background: linear-gradient(90deg, transparent, var(--rule) 18%, var(--rule) 82%, transparent);
  opacity: 0;
}
.titlecard .tag {
  font-size: 30px; font-weight: 700; color: var(--ink-soft);
  opacity: 0;
}
.slide.active .titlecard .school   { animation: tIn 1.1s .20s ease-out forwards; }
.slide.active .titlecard .devnag   { animation: tPop 1.3s .55s cubic-bezier(.2,1.5,.4,1) forwards; }
.slide.active .titlecard .roman    { animation: tIn 1.1s 1.15s ease-out forwards; }
.slide.active .titlecard .flourish { animation: tIn 1.0s 1.50s ease-out forwards; }
.slide.active .titlecard .tag      { animation: tIn 1.1s 1.80s ease-out forwards; }
@keyframes tIn {
  from { opacity: 0; transform: translateY(18px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes tPop {
  0%   { opacity: 0; transform: scale(.72); }
  100% { opacity: 1; transform: scale(1); }
}

/* ==================================================== reveal animations === */
/* One per slide, assigned in the clip registry. The children walk on and wave
   one at a time, so the names arrive one at a time to match; the variety is
   what stops twenty screens of the same grid feeling like a spreadsheet. */

.slide.live .credit {
  animation-duration: .95s;
  animation-timing-function: cubic-bezier(.2, .9, .25, 1);
  animation-fill-mode: forwards;
  animation-delay: calc(var(--i) * var(--stagger));
}

.slide.live[data-reveal="pop"]      .credit { animation-name: rPop;
  animation-timing-function: cubic-bezier(.2, 1.6, .35, 1); }
.slide.live[data-reveal="rise"]     .credit { animation-name: rRise; }
.slide.live[data-reveal="drop"]     .credit { animation-name: rDrop; }
.slide.live[data-reveal="swing"]    .credit { animation-name: rSwing; }
.slide.live[data-reveal="flip"]     .credit { animation-name: rFlip; }
.slide.live[data-reveal="blur"]     .credit { animation-name: rBlur;
  animation-duration: 1.15s; }
.slide.live[data-reveal="spread"]   .credit { animation-name: rSpread;
  animation-duration: 1.2s; }
.slide.live[data-reveal="unfurl"]   .credit { animation-name: rUnfurl;
  animation-duration: 1.1s; }
.slide.live[data-reveal="zoom"]     .credit { animation-name: rZoom; }
.slide.live[data-reveal="bounce"]   .credit { animation-name: rBounce;
  animation-duration: 1.1s; animation-timing-function: cubic-bezier(.28,1.7,.4,1); }
.slide.live[data-reveal="petal"]    .credit { animation-name: rPetal;
  animation-duration: 1.25s; }
.slide.live[data-reveal="glow"]     .credit { animation-name: rGlow;
  animation-duration: 1.2s; }

/* Sideways reveals come in from whichever column the name sits in, so the two
   halves of the grid meet in the middle. */
.slide.live[data-reveal="wings"] .credit          { animation-name: rWingR; }
.slide.live[data-reveal="wings"] .credit.col0     { animation-name: rWingL; }

@keyframes rPop {
  0%   { opacity: 0; transform: scale(.42); }
  100% { opacity: 1; transform: scale(1); }
}
@keyframes rRise {
  0%   { opacity: 0; transform: translateY(56px); }
  100% { opacity: 1; transform: translateY(0); }
}
@keyframes rDrop {
  0%   { opacity: 0; transform: translateY(-56px); }
  100% { opacity: 1; transform: translateY(0); }
}
@keyframes rSwing {
  0%   { opacity: 0; transform: rotate(-11deg) translateY(34px) scale(.86); }
  60%  { opacity: 1; transform: rotate(3deg) translateY(0) scale(1.02); }
  100% { opacity: 1; transform: rotate(0) translateY(0) scale(1); }
}
@keyframes rFlip {
  0%   { opacity: 0; transform: perspective(700px) rotateX(-82deg); }
  100% { opacity: 1; transform: perspective(700px) rotateX(0); }
}
@keyframes rBlur {
  0%   { opacity: 0; filter: blur(16px); transform: scale(1.14); }
  100% { opacity: 1; filter: blur(0); transform: scale(1); }
}
@keyframes rSpread {
  0%   { opacity: 0; letter-spacing: .42em; filter: blur(5px); }
  100% { opacity: 1; letter-spacing: .004em; filter: blur(0); }
}
@keyframes rUnfurl {
  0%   { opacity: 0; clip-path: inset(0 50% 0 50%); transform: scale(.94); }
  100% { opacity: 1; clip-path: inset(0 0 0 0); transform: scale(1); }
}
@keyframes rZoom {
  0%   { opacity: 0; transform: scale(1.85); filter: blur(7px); }
  100% { opacity: 1; transform: scale(1); filter: blur(0); }
}
@keyframes rBounce {
  0%   { opacity: 0; transform: translateY(-70px) scale(.9); }
  62%  { opacity: 1; transform: translateY(6px) scale(1.03); }
  100% { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes rPetal {
  0%   { opacity: 0; transform: translateY(-64px) rotate(-14deg) scale(.8); }
  100% { opacity: 1; transform: translateY(0) rotate(0) scale(1); }
}
@keyframes rGlow {
  0%   { opacity: 0; transform: scale(.8);
         text-shadow: 0 0 46px rgba(255, 196, 92, 1); }
  100% { opacity: 1; transform: scale(1);
         text-shadow: 0 1px 0 rgba(255, 255, 255, .85), 0 2px 14px rgba(255, 250, 235, .9); }
}
@keyframes rWingL {
  0%   { opacity: 0; transform: translateX(-130px) scale(.9); }
  100% { opacity: 1; transform: translateX(0) scale(1); }
}
@keyframes rWingR {
  0%   { opacity: 0; transform: translateX(130px) scale(.9); }
  100% { opacity: 1; transform: translateX(0) scale(1); }
}

/* ================================================== name treatments ======= */
/* Three ways of keeping a name readable over the artwork. The player puts one
   of fx-glow / fx-panel / fx-none on <body>, defaulting to fx-glow.

   GLOW is the default. Every name carries its own small pool of light, sized to
   that name and fading out just past the letters. Ten small pools instead of
   one big plate, so the artwork stays visible between and around them. It
   measured more readable than the big panel at 20% on every background tried,
   while covering a good deal less of the picture.

   PANEL is the earlier treatment, one soft cream rectangle across the middle.
   Kept because it is the strongest option on the busiest artwork.

   NONE is the artwork untouched, which the pale backgrounds can carry.

   Both treatments are scaled by --pa, the strength slider, so one control tunes
   whichever is active. Their stored strengths are separate: a glow needs to be
   near full to work at all, where a panel that big only wants a fifth of it. */

/* --------------------------------------------------------------- glow ---- */

body.fx-glow .panel, body.fx-halo .panel { display: none; }
body.fx-none .panel { display: none; }

body.fx-glow .credit { position: relative; }
body.fx-glow .credit::before {
  content: "";
  position: absolute;
  /* Wider than tall: a name is a long low shape, so a circular pool would
     waste most of its cover above and below the letters.

     The box reaches well past the name on every side and the falloff runs long.
     An earlier, tighter version stopped just outside the letters, which was
     fine in the middle of a name but ran out near the ends of the long ones,
     and that edge is exactly where a dark background bites. Spreading it pushes
     that boundary outward and gives the light a tail instead of an edge. */
  left: -19%; right: -19%; top: -6%; bottom: -4%;
  z-index: -1;
  border-radius: 50%;
  background:
    radial-gradient(ellipse 47% 43% at 50% 47%,
      rgba(var(--panel), calc(.98 * var(--pa))) 0%,
      rgba(var(--panel), calc(.95 * var(--pa))) 28%,
      rgba(var(--panel), calc(.80 * var(--pa))) 45%,
      rgba(var(--panel), calc(.54 * var(--pa))) 60%,
      rgba(var(--panel), calc(.30 * var(--pa))) 73%,
      rgba(var(--panel), calc(.12 * var(--pa))) 85%,
      rgba(var(--panel), calc(.04 * var(--pa))) 93%,
      rgba(var(--panel), 0) 100%);
}

/* The pool arrives with its name, so a screen still opens as clean artwork.
   Only the ::before is animated: setting animation-name on .credit would
   clobber whichever entrance the screen picked, since that is what the
   [data-reveal] rules set. */
body.fx-glow .slide.live .credit::before {
  animation: glowIn .9s ease-out both;
  animation-delay: calc(var(--i) * var(--stagger));
}
@keyframes glowIn {
  from { opacity: 0; transform: scale(.72); }
  to   { opacity: 1; transform: scale(1); }
}

/* ============================================ background strength panel === */
/* Press B. Drag the slider to decide how much cream sits behind the names,
   from nothing at all to heavier than the default. The value is remembered, so
   whatever looks right in the hall is still there next time the deck opens.
   Fixed rather than inside #deck, so it stays a usable size whatever the stage
   is scaled to. */

#bgctl {
  position: fixed;
  right: 18px; bottom: 18px;
  z-index: 55;
  display: none;
  width: 322px;
  padding: 16px 18px 14px;
  border-radius: 14px;
  background: rgba(14, 11, 22, .9);
  color: #f2e7d2;
  font-size: 13px;
  box-shadow: 0 14px 40px rgba(0, 0, 0, .5);
}
body.bgctl #bgctl { display: block; }

#bgctl h4 {
  font-size: 11px; font-weight: 700; letter-spacing: .18em;
  text-transform: uppercase; color: #e8c25f;
  margin-bottom: 11px;
}
#bgctl .row { display: flex; align-items: center; gap: 10px; }
#bgctl input[type=range] {
  flex: 1; height: 22px; margin: 0;
  accent-color: #e8c25f;
  background: transparent; cursor: pointer;
}
#bgctl .val {
  min-width: 52px; text-align: right;
  font-variant-numeric: tabular-nums; font-weight: 700; color: #ffdf9a;
}
#bgctl .presets { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 11px; }
#bgctl #fxrow { margin-top: 0; margin-bottom: 12px; }
#bgctl input[type=range][disabled] { opacity: .35; cursor: not-allowed; }
#bgctl button {
  flex: 1; padding: 6px 0;
  border: 1px solid #4b4030; border-radius: 7px;
  background: #221c2e; color: #e8dcc4;
  font: inherit; font-size: 12px; cursor: pointer;
}
#bgctl button:hover { background: #302742; }
#bgctl button.on { border-color: #e8c25f; color: #ffdf9a; }
#bgctl .note { margin-top: 10px; font-size: 11.5px; line-height: 1.6; opacity: .62; }

/* Brief readout when the strength is nudged with the bracket keys and the
   control panel is closed, so the operator gets feedback either way. */
#bgflash {
  position: fixed;
  left: 50%; bottom: 34px;
  transform: translateX(-50%);
  z-index: 54;
  padding: 8px 20px;
  border-radius: 999px;
  background: rgba(14, 11, 22, .84);
  color: #ffdf9a;
  font-size: 14px; font-weight: 700;
  opacity: 0;
  pointer-events: none;
  transition: opacity .25s ease;
}
#bgflash.show { opacity: 1; }

/* ======================================================== chrome / hud === */

#progress {
  position: fixed; left: 0; bottom: 0; height: 3px; width: 0;
  background: linear-gradient(90deg, #d99b2e, #e8c25f);
  z-index: 40; transition: width .45s ease;
}

#hud {
  position: fixed; left: 0; right: 0; top: 0;
  display: none;
  gap: 20px;
  padding: 7px 16px;
  font-size: 12px; letter-spacing: .09em; text-transform: uppercase;
  color: #cbbfa6; background: rgba(8, 6, 14, .82);
  z-index: 41;
}
body.hud #hud { display: flex; }
#hud span:last-child { margin-left: auto; opacity: .6; }

#help {
  position: fixed; inset: 0; z-index: 60;
  display: none;
  place-items: center;
  background: rgba(6, 5, 12, .9);
  color: #f2e7d2;
}
body.help #help { display: grid; }
#help .box { max-width: 620px; font-size: 17px; line-height: 2; }
#help h3 {
  font-size: 22px; letter-spacing: .18em; text-transform: uppercase;
  margin-bottom: 14px; color: #e8c25f;
}
#help kbd {
  display: inline-block; min-width: 84px;
  padding: 2px 9px; margin-right: 12px;
  border: 1px solid #6a5c44; border-radius: 6px;
  font-family: inherit; font-size: 14px; text-align: center;
  background: #1b1626;
}

/* ------------------------------------------------------------- mobile ---- */

#rotate {
  position: fixed; inset: 0; z-index: 70;
  display: none; place-items: center;
  background: #0b0a12; color: #e9dcc2; text-align: center;
}
#rotate .ph {
  width: 66px; height: 110px; margin: 0 auto 26px;
  border: 3px solid #c9ab7a; border-radius: 12px;
}
#rotate p { font-size: 21px; font-weight: 700; }
#rotate small { display: block; margin-top: 10px; font-size: 14px; opacity: .62; }
@media (orientation: portrait) and (max-width: 900px) {
  #rotate { display: grid; }
}

#tapstart {
  position: fixed; inset: 0; z-index: 50;
  display: none; place-items: center; text-align: center;
  background: rgba(8, 6, 14, .58);
  color: #f4ead6;
}
body.touch.fresh #tapstart { display: grid; }
#tapstart div { font-size: 30px; font-weight: 800; letter-spacing: .04em; }
#tapstart small { display: block; margin-top: 10px; font-size: 15px; opacity: .7; }

/* Renders used by the PowerPoint and Google Slides builds. */
body.bare .credits, body.bare .classchip, body.bare .titlecard { display: none; }
body.namesonly .art, body.namesonly .panel { display: none; }

/* ?chiponly=1 renders the class chip alone on transparency. The MP4 build
   composites names one at a time through ?only=N, and that mode hides the chip
   so it is not stamped once per name. Without a pass of its own the class name
   would be on the website and missing from every video. */
html.chiponly, body.chiponly,
body.chiponly #viewport, body.chiponly #deck {
  background: transparent !important;
  box-shadow: none !important;
}
body.chiponly .art,
body.chiponly .panel,
body.chiponly .credits,
body.chiponly .titlecard { display: none !important; }
body.chiponly .classchip { display: flex !important; opacity: 1 !important; }

/* A names-only render is composited straight onto the artwork by the MP4
   build, so anything that is not a name becomes a permanent mark burnt into
   the video. Every piece of player chrome goes, including the fullscreen
   controls, which sit in the corner and are easy to miss on a check. */
body.namesonly #hud,      body.namesonly #help,     body.namesonly #progress,
body.namesonly #gofull,   body.namesonly #unfull,   body.namesonly #refull,
body.namesonly #daylabel, body.namesonly #tomenu,   body.namesonly #bgctl,
body.namesonly #bgflash,  body.namesonly #startcard, body.namesonly #tapstart,
body.namesonly #rotate,
body.chiponly #hud,       body.chiponly #help,      body.chiponly #progress,
body.chiponly #gofull,    body.chiponly #unfull,    body.chiponly #refull,
body.chiponly #daylabel,  body.chiponly #tomenu,    body.chiponly #bgctl,
body.chiponly #bgflash,   body.chiponly #startcard, body.chiponly #tapstart,
body.chiponly #rotate {
  display: none !important;
}

/* ?only=N renders ONE name, with its glow, on transparency and with the
   entrance already finished. The MP4 build shoots ten of these and fades them
   in one at a time over a looped background, which is how a sixty second video
   gets the same reveal as the live deck without filming a browser. */
html.onlyone, body.onlyone,
body.onlyone #viewport, body.onlyone #deck {
  background: transparent !important;
  box-shadow: none !important;
}
body.onlyone .art,
body.onlyone .panel,
body.onlyone .classchip,
body.onlyone #progress,
body.onlyone #hud { display: none; }
/* The others are hidden by opacity, NOT display:none. Removing them from the
   flow collapses the grid and the surviving name renders in the first cell
   instead of its own, so every isolated render came out stacked in one spot. */
body.onlyone .credit { opacity: 0 !important; }
/* Frozen: no animation, fully arrived. The video does the timing, not CSS. */
body.onlyone .credit.pick { opacity: 1 !important; }
body.onlyone .credit.pick,
body.onlyone .credit.pick::before {
  animation: none !important;
  transform: none !important;
}
/* ?still=1 hides the video so a screenshot always catches the poster frame
   rather than whatever the clip happened to be showing. */
body.still .art video { display: none; }
body.still .slide.active .art img.poster { animation: none; }

/* ?panelonly=1 renders the cream name panel alone on transparency. The
   PowerPoint build drops that PNG over the video, because a soft-edged radial
   gradient is the one thing PowerPoint's own shape fills cannot reproduce:
   a flat fill reads as a box pasted on the artwork, and its path gradient
   renders differently in PowerPoint, Keynote and LibreOffice.

   html has to be transparent as well as body. body alone leaves the page
   canvas painted and every render comes out on solid black. */
html.panelonly, body.panelonly,
body.panelonly #viewport, body.panelonly #deck {
  background: transparent !important;
  box-shadow: none !important;
}
body.panelonly .art,
body.panelonly .credits,
body.panelonly .classchip,
body.panelonly .titlecard,
body.panelonly #progress,
body.panelonly #hud { display: none; }
/* The blur has nothing behind it here and would only soften the gradient. */
body.panelonly .panel {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

@media (prefers-reduced-motion: reduce) {
  .slide.active .art video, .slide.active .art img.poster { animation: none; }
}


/* ================================================ offline school build === */
/* What the school runs in the hall: no internet, no server, no keyboard
   cheatsheet, nothing on screen but the artwork and the names. Everything the
   operator needs is one Present button, and after that it behaves like a
   slideshow. Switched on by "minimal" in show/config.json. */

body.minimal #hud,
body.minimal #help,
body.minimal #progress,
body.minimal #tapstart,
body.minimal #bgctl,
body.minimal #bgflash { display: none !important; }

#startcard {
  position: fixed; inset: 0; z-index: 90;
  display: none;
  place-items: center;
  background: radial-gradient(ellipse 70% 60% at 50% 45%, #fffdf6, #f6e7cb 60%, #e6d2ac);
  text-align: center;
}
body.minimal #startcard { display: grid; }
body.minimal.presenting #startcard { display: none; }

#startcard .ttl {
  font-size: clamp(52px, 9vw, 104px); font-weight: 800; color: #241a3d;
  font-family: "Noto Sans Devanagari", "Nirmala UI", "Kohinoor Devanagari",
               "Devanagari Sangam MN", system-ui, sans-serif;
}
#startcard button {
  margin-top: 30px;
  padding: 18px 62px;
  border: 0; border-radius: 999px;
  background: #8c2f14; color: #fff8ec;
  font-family: inherit; font-size: 25px; font-weight: 800; letter-spacing: .1em;
  cursor: pointer;
  box-shadow: 0 12px 30px rgba(140, 47, 20, .34);
}
#startcard button:hover { background: #a03718; }
#startcard p {
  margin-top: 24px; font-size: 15.5px; line-height: 1.8; color: #6b4a2e;
}

/* The cursor would otherwise sit over the artwork for the whole show. */
body.minimal.presenting { cursor: none; }
body.minimal.presenting.showcursor { cursor: default; }


/* --- the two-day landing ------------------------------------------------ */
/* Without a fixed day the card offers Saturday and Sunday and hides Present;
   with one baked in (the per-day school files) it is the plain Present card
   the school already knows. */
#startcard .days { display: flex; gap: 18px; justify-content: center; }
#startcard .days button {
  font: 600 24px/1 ui-sans-serif, system-ui, sans-serif;
  padding: 16px 42px; border-radius: 14px; cursor: pointer;
  color: #fff8ea; background: #a4552e; border: 2px solid #c98f4a;
}
#startcard .days button:hover { background: #b8653a; }
body:not(.dayfixed) #startcard #present { display: none; }
body.dayfixed #startcard .days { display: none; }


/* --- halo: the show's default treatment --------------------------------- */
/* No shape behind the text at all: every letter carries its own wide pool of
   light plus a fine ivory border. Pure text-shadow and text-stroke, so it
   renders identically for Latin and Devanagari and costs nothing to keep. */
body.fx-halo .nm {
  -webkit-text-stroke: 1.1px #fffdf6;
  paint-order: stroke fill;
  text-shadow: 0 0 10px #fffdf6, 0 0 22px #fffdf6, 0 0 40px #fff9ea,
               0 0 64px rgba(255,246,224,.85),
               0 3px 5px rgba(255,253,246,.95), 0 -3px 5px rgba(255,253,246,.95),
               3px 0 5px rgba(255,253,246,.95), -3px 0 5px rgba(255,253,246,.95);
}
body.fx-halo .rl {
  -webkit-text-stroke: .9px #fff6e8;
  paint-order: stroke fill;
  text-shadow: 0 0 8px #fffdf6, 0 0 18px #fffdf6, 0 0 34px #fff9ea,
               0 0 54px rgba(255,246,224,.8),
               0 2px 4px rgba(255,253,246,.95), 0 -2px 4px rgba(255,253,246,.95),
               2px 0 4px rgba(255,253,246,.95), -2px 0 4px rgba(255,253,246,.95);
}


/* The closing card sits on artwork with figures near the text, so its two
   lines carry the same halo the names do, whatever treatment is active. */
.titlecard .school, .titlecard .devnag {
  -webkit-text-stroke: 1.1px #fffdf6;
  paint-order: stroke fill;
  text-shadow: 0 0 12px #fffdf6, 0 0 26px #fffdf6, 0 0 48px #fff9ea,
               0 0 70px rgba(255,246,224,.85),
               0 3px 5px rgba(255,253,246,.95), 0 -3px 5px rgba(255,253,246,.95),
               3px 0 5px rgba(255,253,246,.95), -3px 0 5px rgba(255,253,246,.95);
}


/* --- the class heading: a hanging butter pot either side ------------------
   The pots are cut from the show's own artwork rather than drawn, so the
   heading always belongs to the picture behind it. Each swings from its own
   knot so the pair never moves as one rigid block. */
.classchip .pot {
  display: block;
  height: 96px;
  width: auto;
  transform-origin: 50% -18px;
  filter: drop-shadow(0 4px 9px rgba(90, 55, 15, .3));
  animation: potSwing 3.8s ease-in-out infinite alternate;
}
.classchip .pot.r { animation-delay: -1.7s; }
@keyframes potSwing {
  from { transform: rotate(-6deg) }
  to   { transform: rotate(6deg) }
}
.classchip span {
  position: relative;
  white-space: nowrap;
}
.classchip span::before {
  content: '';
  position: absolute;
  inset: -14px -30px;
  z-index: -1;
  border-radius: 60px;
  background: rgba(255, 253, 247, .93);
  filter: blur(11px);
}


/* Full screen is a desktop idea. On a phone the browser handles it on
   rotation and the button only adds a step that does nothing useful, so it is
   not shown there. */
body.touch #gofull { display: none; }

/* --- the two-step start ---------------------------------------------------
   Full screen is taken first, on its own, so the browser's "press Esc" notice
   appears over the landing card rather than over the first slide's names. */
#startcard .acts { display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; }
#startcard .acts button {
  font: 600 21px/1 ui-sans-serif, system-ui, sans-serif;
  padding: 15px 34px; border-radius: 13px; cursor: pointer;
  color: #fff8ea; background: #a4552e; border: 2px solid #c98f4a;
}
#startcard .acts button:hover { background: #b8653a; }
#gofull { background: #6b5545; border-color: #8a755c; }
/* once full screen is taken the first button has done its job */
body.isfull #gofull { opacity: .4; pointer-events: none; }
body.isfull #gofull::after { content: ' \2713'; }

/* The rotate card must sit above everything, including the landing card, or a
   phone held upright can start the show behind it. */
#rotate { z-index: 90; }


/* --- the corner controls --------------------------------------------------
   Deliberately quiet. This card is projected in front of an audience while the
   operator gets ready, so anything loud here is loud on a hall wall. The date
   doubles as the way back: one small target says where you are and fixes it if
   you are wrong. */
#startcard #tomenu, #startcard #gofull {
  position: absolute; top: 18px; z-index: 2;
  display: flex; align-items: center; gap: 7px;
  font: 500 13px/1 ui-sans-serif, system-ui, sans-serif;
  color: #8a7256; background: transparent;
  border: 1px solid rgba(150, 120, 84, .25);
  border-radius: 7px; padding: 7px 11px; cursor: pointer;
  opacity: .55; transition: opacity .15s ease, border-color .15s ease;
}
#startcard #tomenu { left: 18px; }
#startcard #gofull { right: 18px; font-size: 15px; padding: 7px 10px; }
#startcard #tomenu:hover, #startcard #gofull:hover {
  opacity: 1; border-color: rgba(150, 120, 84, .55);
  background: rgba(255, 255, 255, .5);
}
#startcard #tomenu span { font-size: 14px; line-height: 1; }
#startcard #tomenu b { font-weight: 600; }

/* nothing to go back to until a day is chosen */
#startcard #tomenu { display: none; }
body.hasday #startcard #tomenu { display: flex; }
/* full screen is a desktop idea, and pointless once already full screen */
body.touch #startcard #gofull, body.isfull #startcard #gofull { display: none; }

/* the single remaining centre button */
#startcard .acts { display: flex; justify-content: center; }
#startcard .acts button {
  font: 600 22px/1 ui-sans-serif, system-ui, sans-serif;
  padding: 16px 46px; border-radius: 13px; cursor: pointer;
  color: #fff8ea; background: #a4552e; border: 2px solid #c98f4a;
}
#startcard .acts button:hover { background: #b8653a; }

/* --- back into full screen, if it was lost mid-show ----------------------
   Same quiet corner treatment. Only while presenting and only when full
   screen has actually been dropped, so it is invisible in the normal case. */
#refull {
  position: fixed; top: 16px; right: 18px; z-index: 65;
  display: none; font-size: 15px; line-height: 1;
  color: #8a7256; background: rgba(255, 252, 244, .6);
  border: 1px solid rgba(150, 120, 84, .32);
  border-radius: 7px; padding: 7px 10px; cursor: pointer;
  opacity: .5; transition: opacity .15s ease;
}
body.presenting:not(.isfull):not(.touch) #refull { display: block; }
#refull:hover { opacity: 1; }

/* --- leave full screen, on the closing card only ------------------------- */
#unfull {
  position: fixed; top: 16px; right: 18px; z-index: 65;
  display: none; font-size: 17px; line-height: 1;
  color: #8a7256; background: rgba(255, 252, 244, .55);
  border: 1px solid rgba(150, 120, 84, .3);
  border-radius: 8px; padding: 7px 11px; cursor: pointer;
  opacity: .5; transition: opacity .15s ease;
}
body.presenting.onclosing.isfull #unfull { display: block; }
#unfull:hover { opacity: 1; }
