/* Manzar presenter — Phase 1.
   House style: pure black background, white English (the focus), muted Roman
   Urdu. Background is a CSS variable so non-Muharram themes can change it later
   without touching the renderers (PROJECT_BRAIN: "background color is a
   variable, not hardcoded"). */

:root {
  --bg: #000;
  --english: #fff;
  --roman: rgba(255, 255, 255, 0.45);
  --text-shadow-over-image: 0 1px 7px rgba(0, 0, 0, 0.9);
  /* Slide text scales with its CONTAINER (the stage), not the viewport, using
     container-query units. So the same renderer is faithful both fullscreen
     (container == viewport) and inside the small editor preview box. A first
     cut — full letterboxing + auto-shrink is the hardening pass. */
  --english-size: 4.6cqmin;
  --roman-size: 3cqmin;
  --crossfade: 850ms;
  /* The default recitation background ("shrine"). Any deck/slide without its own
     image renders on this; a deck overrides by setting --deck-img (render.js).
     url() is relative to this stylesheet (static/css/). */
  --lm-default-deck-img: url("../img/sample-shrine.png");
  /* Second house background ("flame"): a single candle flame on black. Selected
     per-deck (deck.background === "flame"); render.js points --deck-img here for
     imageless slides. Shares the shrine's treat-default + valign look. */
  --lm-flame-deck-img: url("../img/flame-background.png");
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--bg);
  color: var(--english);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  overflow: hidden;
  /* No reserved gutter on the edge-to-edge projection (base.css sets stable). */
  scrollbar-gutter: auto;
}

/* The fullscreen stage. Edge to edge, no chrome, no borders. */
.stage {
  position: relative;
  width: 100vw;
  height: 100vh;
  background: var(--bg);
  overflow: hidden;
  /* Centre the 16:9 frame; the surrounding black is the letterbox. */
  display: flex;
  align-items: center;
  justify-content: center;
  /* Size container so the editor preview (which has no .frame) still scales
     slide text via cqmin. The presenter scales to the .frame below. */
  container-type: size;
}

/* The presenter's fixed 16:9 frame — fits any screen, letterboxed by the black
   stage. Slide text uses cqmin against THIS, so the deck is identical on every
   projector regardless of the screen's true aspect ratio. */
.frame {
  position: relative;
  width: min(100vw, 177.7778vh); /* 16:9 that fits the viewport */
  aspect-ratio: 16 / 9;
  overflow: hidden;
  container-type: size;
}
/* In native fullscreen the stage owns the whole screen. */
.stage:fullscreen {
  width: 100vw;
  height: 100vh;
}

.slides {
  position: absolute;
  inset: 0;
}

/* Each slide is stacked; only .is-active is visible. Opacity gives the slow
   cross-fade — subtle, no hard cut (noha: restraint over flash). */
.slide {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  /* Spacing in container units (cqmin), like the text — so the small editor
     preview is a faithful scaled replica of fullscreen, not exaggerated. */
  padding: 6cqmin 8cqmin;
  opacity: 0;
  transition: opacity var(--crossfade) ease-in-out;
  pointer-events: none;
}
.slide.is-active {
  opacity: 1;
}

/* --- List transition / title slide (announces each piece in a list run) --- */
.title-slide .slide-body { align-items: center; text-align: center; }
.title-eyebrow {
  font-family: var(--font-ui);
  font-size: 1.7cqmin;
  letter-spacing: 0.32em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.42);
}
.title-rule { width: 8cqmin; height: 1px; background: var(--marker); margin: 2.4cqmin auto; }
.title-main {
  font-family: var(--font-serif);
  font-size: 7cqmin;
  line-height: 1.15;
  letter-spacing: -0.01em;
  color: #fff;
  max-width: 80cqw;
  text-wrap: balance;
}
.title-sub {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: 2.6cqmin;
  color: rgba(255, 255, 255, 0.5);
  margin-top: 2.2cqmin;
}

/* Line breaking (owner requirement):
   - ROMAN never wraps — a line pasted as one line stays one visual line. If it's
     long, fitSlide() scales the slide down so it still fits the 16:9 frame.
   - ENGLISH may wrap to preserve natural flow when a rendering is long; short
     renderings stay on one line. It wraps within a reading width near the frame
     edge, and fitSlide() scales the whole block to fit. */
/* Projected verse typeface (a deck-level choice; class set by the renderer).
   Default is the literary serif (Spectral), matching the Manzar presentation
   kit; .type-sans switches to the UI sans (Hanken Grotesk). The English
   translation and the muted Roman both carry the chosen face. Shared renderer,
   so the editor preview and the presenter stay identical. */
.slide .roman,
.slide .english {
  font-family: var(--font-translation);
}
.slide.type-sans .roman,
.slide.type-sans .english {
  font-family: var(--font-ui);
}
.slide.type-source-serif .roman,
.slide.type-source-serif .english {
  font-family: var(--font-source-serif);
}
.slide.type-literata .roman,
.slide.type-literata .english {
  font-family: var(--font-literata);
}
.slide .roman {
  white-space: nowrap;
}
.slide .english {
  white-space: normal;
  max-width: 90cqw;
  /* Even out a wrapped rendering so a long line doesn't drop a sparse orphan
     row ("...your hands / somewhere else"); balance distributes the words
     across the rows. The model returns one English rendering per Roman line;
     this only shapes how that rendering wraps, never where the lines break. */
  text-wrap: balance;
}

/* All text sits in .slide-body so it can be scaled down to fit the frame
   (auto-shrink) without clipping. */
.slide-body {
  display: flex;
  flex-direction: column;
  align-items: center;
  /* No max-width: the body shrink-wraps the widest no-wrap line so fitSlide can
     measure its true width and scale the whole block to fit the frame. */
  transform-origin: center;
}

/* Alignment (deck default or per-slide). Center is the .slide default above;
   left pins content to the start edge and left-aligns the text. */
.slide.align-left {
  align-items: flex-start;
  text-align: left;
}
.slide.align-left .slide-body {
  align-items: flex-start;
  transform-origin: left center;
}
.slide.align-center {
  align-items: center;
  text-align: center;
}
.slide.align-center .slide-body {
  align-items: center;
  transform-origin: center;
}

/* Hide the mouse cursor after stillness in fullscreen (set by present.js). */
.stage.cursor-hidden { cursor: none; }

/* ============================================================
   Images — the treatment layer. Mirrors design/ui_kits/presentation/
   slides.css so background images read like broadcast graphics, never
   pasted rectangles. Text legibility ALWAYS wins. Used by both the
   presenter and the editor preview (one renderer) so there is no drift.
   The projected canvas stays pure #000 — treatments only shape the image.
   ============================================================ */
/* Lift the text above the image layers. */
.slide.has-image > *:not(.slide-img):not(.slide-img-blur):not(.slide-duotone) {
  position: relative;
  z-index: 2;
}
.slide-img,
.slide-img-blur,
.slide-duotone {
  position: absolute;
  pointer-events: none;
}
.slide-img {
  /* Explicit deck/slide images set --deck-img inline (render.js); imageless
     slides leave it unset and fall back to the default shrine token. */
  background-image: var(--deck-img, var(--lm-default-deck-img));
  background-position: center;
  background-repeat: no-repeat;
  background-size: cover;
  z-index: 1;
}
.slide-img-blur { /* legacy blur_fill underlay */
  inset: 0;
  background-position: center;
  background-repeat: no-repeat;
  background-size: cover;
  transform: scale(1.2);
  filter: blur(2.5cqmin) brightness(0.6);
  z-index: 0;
}

/* --- Positions --- */
.img-background .slide-img { inset: 0; }
.img-right .slide-img { top: 0; right: 0; bottom: 0; width: 46%; }
.slide.img-right { padding-right: 52%; } /* text in the open space */
.img-top_right .slide-img { top: 6cqmin; right: 6cqmin; width: 32%; height: 42%; }

/* === Treatments (applied as treat-<name> on the slide) === */

/* Scrim (DEFAULT, full colour) — no global darkening; a soft dark cloud is
   shaped to the text block and FOLLOWS it (the scrim is drawn on .slide-body,
   which the flex layout positions per format + alignment), so the photo stays
   vivid in the open areas while every line stays readable. */
.img-background.treat-scrim .slide-img { filter: none; }
.img-background.treat-scrim .slide-body::before,
.img-background.treat-scrim .slide-body::after {
  content: "";
  position: absolute;
  z-index: -1;
  pointer-events: none;
}
.img-background.treat-scrim .slide-body::before {
  inset: -13cqmin -16cqmin;
  background: radial-gradient(62% 60% at 50% 50%,
              rgba(0, 0, 0, 0.86) 0%, rgba(0, 0, 0, 0.58) 40%,
              rgba(0, 0, 0, 0.22) 64%, rgba(0, 0, 0, 0) 78%);
  filter: blur(7cqmin);
}
.img-background.treat-scrim .slide-body::after {
  inset: -8cqmin -12cqmin;
  background: radial-gradient(58% 56% at 50% 52%, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0) 70%);
  filter: blur(4cqmin);
}

/* Default shrine (DEFAULT for any deck/slide with NO image of its own). The
   shrine light shows in the upper half; the verse is anchored low over a
   full-width bottom scrim, so text stays legible and the light is never killed
   by a global darken. Set by render.js as treat-default + has-default-bg. */
.img-background.treat-default .slide-img { filter: brightness(1.08) saturate(1.08); }

/* Vertical position of the verse block (deck-level; render.js sets valign-*).
   Bottom is the default (the shrine look); center pins it to the middle. */
.slide.valign-bottom { justify-content: flex-end; }
.slide.valign-center { justify-content: center; }
.slide.treat-default::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;            /* above .slide-img (painted earlier); below .slide-body (z 2) */
  pointer-events: none;
  background: linear-gradient(to top,
    rgba(8, 7, 6, 0.95) 5%, rgba(8, 7, 6, 0.74) 30%,
    rgba(8, 7, 6, 0.12) 54%, rgba(8, 7, 6, 0) 72%);
}

/* Feather — straight edges dissolve into the black */
.img-background.treat-feather .slide-img {
  -webkit-mask-image:
    linear-gradient(to right,  transparent 0, #000 14%, #000 86%, transparent 100%),
    linear-gradient(to bottom, transparent 0, #000 16%, #000 84%, transparent 100%);
  -webkit-mask-composite: source-in;
  mask-image:
    linear-gradient(to right,  transparent 0, #000 14%, #000 86%, transparent 100%),
    linear-gradient(to bottom, transparent 0, #000 16%, #000 84%, transparent 100%);
  mask-composite: intersect;
}

/* Vignette — edges softly blurred and faded inward */
.img-background.treat-vignette .slide-img {
  filter: blur(1.2cqmin) brightness(0.92);
  -webkit-mask-image: radial-gradient(108% 108% at 50% 47%, #000 26%, transparent 80%);
  mask-image: radial-gradient(108% 108% at 50% 47%, #000 26%, transparent 80%);
}

/* Darken — push the image back behind the words */
.img-background.treat-darken .slide-img { filter: brightness(0.4) contrast(1.02); }

/* Faint underlay — a whisper of colour over black (the opacity slider, when
   lowered, overrides this via an inline style). */
.img-background.treat-faint .slide-img {
  opacity: 0.24;
  -webkit-mask-image:
    linear-gradient(to right,  transparent 0, #000 16%, #000 84%, transparent 100%),
    linear-gradient(to bottom, transparent 0, #000 18%, #000 82%, transparent 100%);
  -webkit-mask-composite: source-in;
  mask-image:
    linear-gradient(to right,  transparent 0, #000 16%, #000 84%, transparent 100%),
    linear-gradient(to bottom, transparent 0, #000 18%, #000 82%, transparent 100%);
  mask-composite: intersect;
}

/* Duotone — desaturate, then a palette tint on a SEPARATE layer (so the
   grayscale filter on the image doesn't also drain the tint's colour). */
.treat-duotone .slide-img { filter: grayscale(1) brightness(0.6) contrast(1.06); }
.slide-duotone {
  inset: 0;
  z-index: 1;
  background: linear-gradient(155deg, rgba(63, 72, 98, 0.55) 0%, rgba(140, 92, 70, 0.42) 100%);
  mix-blend-mode: overlay;
}

/* blur_fill (legacy): show the whole image; the blurred copy fills the frame. */
.img-background.treat-blur_fill .slide-img { background-size: contain; }

/* === Side panel — image to one side, feathered into the black === */
.img-right .slide-img {
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 42%);
  mask-image: linear-gradient(to right, transparent 0, #000 42%);
}
.img-top_right .slide-img {
  -webkit-mask-image:
    linear-gradient(to left, transparent 0, #000 46%),
    linear-gradient(to bottom, transparent 0, #000 46%);
  -webkit-mask-composite: source-in;
  mask-image:
    linear-gradient(to left, transparent 0, #000 46%),
    linear-gradient(to bottom, transparent 0, #000 46%);
  mask-composite: intersect;
}

/* Legibility insurance: white English + muted Roman keep a soft shadow over
   any image, in every treatment. */
.slide.has-image .english { text-shadow: var(--text-shadow-over-image); }
.slide.has-image .roman { text-shadow: 0 1px 6px rgba(0, 0, 0, 0.85); }

/* --- line_by_line: Roman line muted, English directly beneath, repeating. --- */
.layout-line_by_line .pair {
  margin: 1.6cqmin 0;
}
.layout-line_by_line .roman {
  font-size: var(--roman-size);
  color: var(--roman);
  font-style: italic;
  line-height: 1.4;
}
.layout-line_by_line .english {
  font-size: var(--english-size);
  color: var(--english);
  font-weight: 500;
  line-height: 1.35;
  margin-top: 0.4cqmin;
}

/* --- stacked: the whole Roman block, then the English block beneath. --- */
.layout-stacked .roman-block {
  margin-bottom: 3.2cqmin;
}
.layout-stacked .roman-block .roman {
  font-size: var(--roman-size);
  color: var(--roman);
  font-style: italic;
  line-height: 1.5;
}
.layout-stacked .english-block .english {
  font-size: var(--english-size);
  color: var(--english);
  font-weight: 500;
  line-height: 1.45;
}

/* --- Present button (pre-fullscreen only) --- */
.present-btn {
  position: absolute;
  bottom: 6vmin;
  left: 50%;
  transform: translateX(-50%);
  padding: 0.85rem 2.4rem;
  font-family: var(--font-ui);
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-label);
  color: #fff;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: var(--radius-control);
  cursor: pointer;
  transition: background var(--dur) var(--ease-calm), opacity var(--dur) var(--ease-calm);
  backdrop-filter: blur(2px);
}
.present-btn:hover {
  background: rgba(255, 255, 255, 0.14);
}
.present-btn:active {
  transform: translateX(-50%) translateY(0.5px);
}
/* Hidden once we're in fullscreen. */
.stage.is-fullscreen .present-btn {
  display: none;
}

/* --- Resume button (offered only after an accidental exit; sits above Present) --- */
.resume-btn {
  position: absolute;
  bottom: calc(6vmin + 3.6rem);
  left: 50%;
  transform: translateX(-50%);
  padding: 0.55rem 1.6rem;
  font-family: var(--font-ui);
  font-size: var(--fs-body-sm);
  letter-spacing: var(--ls-label);
  color: rgba(255, 255, 255, 0.85);
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.2);
  border-radius: var(--radius-control);
  cursor: pointer;
  transition: background var(--dur) var(--ease-calm);
  backdrop-filter: blur(2px);
}
.resume-btn:hover { background: rgba(255, 255, 255, 0.12); }
/* Explicit display above overrides the [hidden] UA rule — restore it. */
.resume-btn[hidden] { display: none; }
.stage.is-fullscreen .resume-btn { display: none; }

/* --- Pre-fullscreen exit (back to the library) --- */
.exit-link {
  position: absolute;
  top: 4vmin;
  left: 4vmin;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.5rem 0.9rem 0.5rem 0.7rem;
  font-family: var(--font-ui);
  font-size: clamp(0.8rem, 1.5vmin, 1rem);
  letter-spacing: var(--ls-label);
  color: rgba(255, 255, 255, 0.6);
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: var(--radius-control);
  text-decoration: none;
  transition: background var(--dur) var(--ease-calm), color var(--dur) var(--ease-calm);
}
.exit-link:hover { color: #fff; background: rgba(255, 255, 255, 0.12); }
.exit-link svg { width: 1em; height: 1em; }
/* Hidden during the projection, like the Present button. */
.stage.is-fullscreen .exit-link {
  display: none;
}

/* --- Faint fallback arrows --- */
.navarrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 9vmin;
  height: 9vmin;
  min-width: 44px;
  min-height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 6vmin;
  line-height: 1;
  color: rgba(255, 255, 255, 0.55);
  background: rgba(0, 0, 0, 0.001); /* keeps it clickable without a visible box */
  border: none;
  cursor: pointer;
  opacity: 0;
  transition: opacity 350ms ease;
  pointer-events: none;
  user-select: none;
}
.navarrow--left { left: 1.5vmin; }
.navarrow--right { right: 1.5vmin; }
/* Lucide chevron icons replace the old unicode glyphs; they inherit the
   arrow's translucent-white color via currentColor. */
.navarrow svg { width: 5.2vmin; height: 5.2vmin; }
/* Revealed when the controls layer is shown (set by JS on mouse movement). */
.stage.show-controls .navarrow {
  opacity: 1;
  pointer-events: auto;
}

/* --- Entry hint --- */
/* Scoped to #hint (not the generic .hint class) so these absolute/faded
   presenter styles never leak onto the editor's .hint captions, which load
   present.css too. */
#hint {
  position: absolute;
  bottom: 4vmin;
  left: 50%;
  transform: translateX(-50%);
  padding: 0.6rem 1.2rem;
  font-family: var(--font-ui);
  font-size: clamp(0.8rem, 1.6vmin, 1.1rem);
  letter-spacing: var(--ls-label);
  color: rgba(255, 255, 255, 0.72);
  background: rgba(0, 0, 0, 0.45);
  border-radius: var(--radius-control);
  opacity: 0;
  pointer-events: none;
  transition: opacity 600ms ease;
  white-space: nowrap;
}
#hint.is-visible {
  opacity: 1;
}
