/* ==========================================================================
   VishnuFlow — a still, warm frame for continuous devotional playback.

   Performance contract for 24/7 display use:
   every layer below is painted once and never animates. The only transition
   in the sheet is the one-shot opening veil, which is removed from the
   compositor as soon as it finishes.
   ========================================================================== */

:root {
  /* Palette — warm, low-contrast, no pure white and no pure black. */
  --ivory:          #FBF6EC;
  --pearl:          #FDFBF6;
  --cream:          #F6EEDF;
  --sandalwood:     #EADFC8;
  --lotus-blush:    #F2E2DA;
  --ink:            #6B5B44;

  /* The golds — pale saffron #F0D9A8 and temple gold #C9A227 — are written
     inline as rgba() rather than declared here, because every use needs its
     own alpha and a custom property cannot carry one. Search those two hex
     values to find them all. */

  /* Composition */
  --stage-padding:  clamp(10px, 2.2vmin, 34px);
  --frame-radius:   clamp(4px, 0.9vmin, 14px);
  --footer-size:    clamp(10px, 1.35vmin, 17px);
  --footer-tracking: 0.34em;

  /* Timing */
  --veil-fade:      1100ms;

  --serif: "Noto Serif Devanagari", "Tiro Devanagari Sanskrit",
           "Kohinoor Devanagari", "Adobe Devanagari", Georgia,
           "Times New Roman", "Nirmala UI", serif;

  /* Declared once so the landscape watermark and the portrait altar medallion
     are literally the same artwork rather than two copies that can drift. */
  --mandala: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'%3E%3Cg fill='none' stroke='%23A8842A' stroke-width='0.5'%3E%3Ccircle cx='100' cy='100' r='99'/%3E%3Ccircle cx='100' cy='100' r='95'/%3E%3Ccircle cx='100' cy='100' r='72'/%3E%3Ccircle cx='100' cy='100' r='34'/%3E%3Ccircle cx='100' cy='100' r='11'/%3E%3Cg%3E%3Cellipse cx='100' cy='53' rx='13' ry='40'/%3E%3Cellipse cx='100' cy='53' rx='13' ry='40' transform='rotate(30 100 100)'/%3E%3Cellipse cx='100' cy='53' rx='13' ry='40' transform='rotate(60 100 100)'/%3E%3Cellipse cx='100' cy='53' rx='13' ry='40' transform='rotate(90 100 100)'/%3E%3Cellipse cx='100' cy='53' rx='13' ry='40' transform='rotate(120 100 100)'/%3E%3Cellipse cx='100' cy='53' rx='13' ry='40' transform='rotate(150 100 100)'/%3E%3C/g%3E%3Cg stroke-width='0.35'%3E%3Cellipse cx='100' cy='68' rx='7' ry='26' transform='rotate(15 100 100)'/%3E%3Cellipse cx='100' cy='68' rx='7' ry='26' transform='rotate(45 100 100)'/%3E%3Cellipse cx='100' cy='68' rx='7' ry='26' transform='rotate(75 100 100)'/%3E%3Cellipse cx='100' cy='68' rx='7' ry='26' transform='rotate(105 100 100)'/%3E%3Cellipse cx='100' cy='68' rx='7' ry='26' transform='rotate(135 100 100)'/%3E%3Cellipse cx='100' cy='68' rx='7' ry='26' transform='rotate(165 100 100)'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
}

* { box-sizing: border-box; }

html, body {
  height: 100%;
  margin: 0;
  overflow: hidden;
}

body {
  display: flex;
  flex-direction: column;
  background-color: var(--ivory);
  color: var(--ink);
  font-family: var(--serif);

  /* Kiosk hygiene: no selection, no callouts, no tap flash, no double-tap zoom. */
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
  touch-action: manipulation;
  cursor: none;
}

/* --------------------------------------------------------------------------
   Backdrop
   Three stacked layers, all fixed and non-interactive:
   1. .ambient  — the poster image, heavily blurred, as a soft light spill
   2. body::before — layered radial gradients (the base glow)
   3. .mandala / .vignette — static ornament and edge falloff
   -------------------------------------------------------------------------- */

body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  background:
    radial-gradient(120% 90% at 50% 28%, var(--pearl) 0%, rgba(253, 251, 246, 0) 62%),
    radial-gradient(75% 60% at 50% 46%, rgba(240, 217, 168, 0.34) 0%, rgba(240, 217, 168, 0) 70%),
    radial-gradient(100% 100% at 12% 100%, var(--lotus-blush) 0%, rgba(242, 226, 218, 0) 55%),
    linear-gradient(168deg, var(--cream) 0%, var(--ivory) 44%, var(--sandalwood) 100%);
}

.ambient {
  position: fixed;
  /* Overscan so the blur radius never reveals a hard edge. */
  inset: -14%;
  z-index: 0;
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
  /* Desaturated hard, not enriched. Blurring a photograph that contains sky
     leaves a blue-grey bruise across the page, which on a warm ivory shrine
     reads as a rendering fault rather than as light. What is wanted is the
     picture's brightness, not its hues. */
  filter: blur(80px) saturate(0.28) brightness(1.16);
  opacity: 0;
  transition: opacity 1600ms ease;
  will-change: auto;
}

.ambient.is-lit { opacity: 0.32; }

/* --------------------------------------------------------------------------
   Painted backdrop
   When CONFIG.backdrop supplies real artwork, it is shown sharp and
   full-bleed instead of being blurred into light, and it takes over from the
   CSS ornament entirely — the painting carries its own mandala and Om, so
   drawing ours over the top would only fight it.
   -------------------------------------------------------------------------- */

.has-backdrop .ambient {
  inset: 0;
  filter: none;
  background-position: center;
}

.has-backdrop .ambient.is-lit { opacity: 1; }

/* The artwork supplies its own geometry; ours would collide with it. */
.has-backdrop .mandala,
.has-backdrop .footer::before { display: none; }

/* The painting is already luminous at the centre and soft at the edges, so
   the base gradients and the vignette have nothing left to do. */
body.has-backdrop::before { display: none; }
.has-backdrop .vignette {
  background: radial-gradient(130% 110% at 50% 42%, rgba(255, 255, 255, 0) 62%, rgba(150, 118, 66, 0.10) 100%);
}

.mandala {
  position: fixed;
  left: 50%;
  top: 50%;
  width: min(148vmin, 1500px);
  height: min(148vmin, 1500px);
  margin: calc(min(148vmin, 1500px) / -2) 0 0 calc(min(148vmin, 1500px) / -2);
  z-index: 0;
  opacity: 0.05;
  background: center / contain no-repeat var(--mandala);
}

/* --------------------------------------------------------------------------
   Falling gold

   Slow motes of temple gold drifting down the whole frame, so the shrine is
   visibly alive at any distance rather than only on close inspection.

   Every mote is a keyframed transform and opacity — compositor-only, so the
   GPU moves already-rasterised layers and no JavaScript runs after the motes
   are created. A canvas particle system would have looked much the same and
   burned a rAF loop sixty times a second, permanently, on a device with no
   fan and no sleep.

   Each mote gets its own column, size, drift, duration and start offset from
   custom properties set once at load, so nothing here repeats visibly.
   -------------------------------------------------------------------------- */

.sparkles {
  position: fixed;
  inset: 0;
  z-index: 1;
  overflow: hidden;
  pointer-events: none;
}

.sparkles i {
  position: absolute;
  top: -8vh;
  left: var(--x);
  width: var(--size);
  height: var(--size);
  border-radius: 50%;
  opacity: 0;
  background: radial-gradient(circle,
    rgba(255, 244, 214, 0.98) 0%,
    rgba(226, 184, 96, 0.85) 40%,
    rgba(198, 154, 62, 0) 72%);
  animation: fall var(--dur) linear var(--delay) infinite;
}

@keyframes fall {
  0%   { transform: translate3d(0, 0, 0) scale(0.55); opacity: 0; }
  8%   { opacity: 0.95; }
  50%  { transform: translate3d(var(--sway), 60vh, 0) scale(1); opacity: 1; }
  88%  { opacity: 0.7; }
  100% { transform: translate3d(0, 122vh, 0) scale(0.6); opacity: 0; }
}

.vignette {
  position: fixed;
  inset: 0;
  z-index: 1;
  background:
    radial-gradient(128% 108% at 50% 44%, rgba(255, 255, 255, 0) 52%, rgba(150, 118, 66, 0.16) 100%);
}

/* Decorative layers never take pointer events — a tap anywhere reaches body. */
.ambient, .mandala, .vignette, .veil { pointer-events: none; }

/* --------------------------------------------------------------------------
   Stage and frame
   The video sizes itself from its own intrinsic aspect ratio; the frame
   shrink-wraps it. Portrait, landscape and square sources all centre
   correctly with no stretching and no cropping.
   -------------------------------------------------------------------------- */

/* Grid, not flex. A flex item takes its base size from the image's intrinsic
   width, so max-height clamps the height while the width stays at full size
   and the frame ends up wider than the picture. Grid sizes the replaced
   element by its own aspect ratio, which is what lets the gold rule sit tight
   against any aspect: portrait, landscape or square. */
.stage {
  position: relative;
  z-index: 2;
  flex: 1 1 auto;
  min-height: 0;
  display: grid;
  /* minmax(0, 1fr), not the default auto: an auto track sizes itself to the
     image, so the image's own max-height: 100% would resolve against its own
     height and never clamp anything. Fixed tracks give the percentage a real
     box to measure against. */
  grid-template-rows: minmax(0, 1fr);
  grid-template-columns: minmax(0, 1fr);
  place-items: center;
  padding: var(--stage-padding);
}

/* The picture is its own frame. Sizing it directly — rather than wrapping it
   in a box — is what keeps the gold rule tight against it: a wrapper would
   take its width from the intrinsic size while the picture took its height
   from the wrapper, and the two would disagree on any aspect ratio that did
   not match the screen. */
#murti {
  display: block;
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  border-radius: var(--frame-radius);
  background-color: var(--pearl);
  /* Hairline gold rule, a breath of pearl matting, then one soft ground shadow. */
  box-shadow:
    0 0 0 1px rgba(201, 162, 39, 0.30),
    0 0 0 7px rgba(253, 251, 246, 0.55),
    0 0 44px 4px rgba(240, 217, 168, 0.34),
    0 26px 60px -34px rgba(120, 92, 44, 0.42);
}

/* Belt and braces: the shrine video carries no controls attribute, so Silk
   shows no chrome — but some Chromium builds still reserve the overlay layer
   for the start-playback button. */
#murti::-webkit-media-controls,
#murti::-webkit-media-controls-enclosure,
#murti::-webkit-media-controls-panel,
#murti::-webkit-media-controls-start-playback-button {
  display: none !important;
  -webkit-appearance: none;
}

/* The recitation has no visual representation at all — no element, no chrome,
   no controls. It is sound only. */
audio {
  display: none;
}

/* --------------------------------------------------------------------------
   Footer
   -------------------------------------------------------------------------- */

.footer {
  position: relative;
  z-index: 2;
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5em;
  padding: clamp(8px, 1.5vmin, 22px) 1rem clamp(12px, 2vmin, 26px);
  text-align: center;
}

/* The English line is the one meant to be read from across the room, so it
   leads: largest, darkest, slightly weighted. The mantra below it is the
   constant the shrine is built around and does not need to compete — it
   recedes to a quiet inscription. The fade is the only thing in the app that
   moves after the opening, and it does so twice an hour. */
.affirmation {
  font-size: calc(var(--footer-size) * 1.2);
  letter-spacing: 0.16em;
  text-indent: 0.16em;
  line-height: 1.7;
  font-weight: 600;
  color: rgba(92, 66, 28, 1);
  opacity: 0;
  transition: opacity 1400ms ease;
}

.affirmation.is-shown { opacity: 1; }

/* --------------------------------------------------------------------------
   Breath

   Roughly the pace of a slow, settled exhale — eight seconds in and out,
   never resting at either end. It runs on an inner element so it cannot
   collide with the crossfade: the outer span owns the opacity that swaps one
   affirmation for the next, this one owns the rhythm.

   Only opacity and transform are touched. Both are compositor-only — the GPU
   re-blends an already-rasterised layer, with no layout and no repaint — which
   is what makes a permanent animation defensible on a screen that never
   sleeps. Colour, filter and text-shadow would all repaint the glyphs on every
   frame; those are the ones to avoid, not transform.

   The swing is deliberately wide. A shallow fade over eight seconds is below
   the threshold the eye bothers to notice: it reads as a static page rather
   than as a calm one.
   -------------------------------------------------------------------------- */

@keyframes breathe {
  0%, 100% { opacity: 0.42; transform: scale(0.972); }
  50%      { opacity: 1;    transform: scale(1.028); }
}

.breath {
  display: inline-block;
  animation: breathe 7s ease-in-out infinite;
}

/* The mantra breathes with it rather than against it — one shrine, one
   breath. Shallower, since it is already a quiet inscription. */
@keyframes breathe-soft {
  0%, 100% { opacity: 0.5; transform: scale(0.984); }
  50%      { opacity: 1;   transform: scale(1.016); }
}

.mantra {
  display: inline-block;
  animation: breathe-soft 7s ease-in-out infinite;
}

.mantra {
  font-size: calc(var(--footer-size) * 0.88);
  letter-spacing: var(--footer-tracking);
  /* Tracking adds trailing space after the final glyph; pull the block back. */
  text-indent: var(--footer-tracking);
  line-height: 1.9;
  color: rgba(126, 102, 62, 0.42);
  font-weight: 400;
}

.hint {
  font-size: calc(var(--footer-size) * 0.82);
  letter-spacing: 0.2em;
  text-indent: 0.2em;
  color: rgba(122, 96, 52, 0.42);
  font-style: italic;
}

.hint[hidden] { display: none; }

/* --------------------------------------------------------------------------
   Status rail

   Date, time and weather along the very bottom edge, with the build marker
   pushed to the far right. It survives inside a shrine by staying strictly
   subordinate: the same serif and the same gold as everything else, small,
   evenly tracked, and low enough in contrast that it registers only when
   looked for. It sits at the edge of the frame rather than in it.
   -------------------------------------------------------------------------- */

.status {
  position: relative;
  z-index: 2;
  flex: 0 0 auto;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1em;
  padding: 0 clamp(12px, 2.6vw, 40px) clamp(10px, 1.8vh, 26px);
  font-size: calc(var(--footer-size) * 0.82);
  letter-spacing: 0.18em;
  color: rgba(126, 100, 58, 0.5);
  white-space: nowrap;
}

.status-centre {
  display: flex;
  align-items: baseline;
  gap: 0.9em;
}

/* The time is the one thing anyone actually looks for, so it alone is
   allowed a little more presence. */
.status-time {
  font-size: 1.22em;
  letter-spacing: 0.12em;
  color: rgba(112, 86, 42, 0.72);
}

.status-weather:not(:empty)::before {
  content: "·";
  margin-right: 0.9em;
  opacity: 0.5;
}

/* Deliberately near-invisible: useful when you go looking for which build is
   on the wall, absent the rest of the time. */
.status-version {
  font-size: 0.72em;
  letter-spacing: 0.1em;
  color: rgba(126, 100, 58, 0.26);
}

/* Empty until the first successful reading, and empty again if the network
   is gone and nothing was ever cached — the rail simply closes up. */
.status-date:empty,
.status-weather:empty,
.status-version:empty { display: none; }

/* --------------------------------------------------------------------------
   Opening veil — the single moving element, shown once per load.
   -------------------------------------------------------------------------- */

.veil {
  position: fixed;
  inset: 0;
  z-index: 3;
  background-color: var(--ivory);
  opacity: 1;
  transition: opacity var(--veil-fade) ease-out;
}

.veil.is-lifted { opacity: 0; }

/* Removed from the layer tree entirely once faded, so nothing composites
   above the video for the remaining lifetime of the page. */
.veil.is-gone { display: none; }

@media (prefers-reduced-motion: reduce) {
  .veil, .ambient { transition-duration: 1ms; }
  .breath, .mantra { animation: none; }
  .sparkles { display: none; }
}

/* Wider gutters on desktop-sized viewports, where a full-bleed frame reads
   as a video player rather than a shrine. */
@media (min-width: 1100px) and (min-height: 700px) {
  :root { --stage-padding: clamp(24px, 4vmin, 72px); }
}

/* --------------------------------------------------------------------------
   Portrait — the wall-mounted case.

   An Echo Show 15 hung in a picture frame is 1080 x 1920, and 16:9 artwork
   fills barely a third of that. Rather than float the picture in the middle
   of a tall empty rectangle, the layout becomes a devotional poster: the
   murti above, and beneath it an altar panel where the mandala — a faint
   background ornament in landscape — comes forward as the composition's
   second element, with the mantra resting under it.
   -------------------------------------------------------------------------- */

@media (orientation: portrait) {
  /* Narrow gutters: the murti is width-constrained in portrait, so every
     pixel of padding is a pixel off its height too. */
  :root { --stage-padding: clamp(6px, 1.2vw, 14px); }

  /* The stage now takes only the height the picture needs, instead of
     claiming everything the footer does not. */
  /* The murti sits well down from the top edge rather than tucked under it.
     On the wall-mounted device Silk keeps its address bar, so anything near
     the top edge reads as crowded by browser furniture. */
  .stage {
    flex: 0 0 auto;
    grid-template-rows: auto;
    padding-top: clamp(32px, 24vh, 480px);
    padding-bottom: clamp(10px, 2vh, 28px);
  }

  /* An explicit cap, because the grid row is now content-sized: a percentage
     max-height would resolve against the picture's own height and never bite. */
  #murti { max-height: 42vh; }

  /* The footer stops being a strip and becomes the altar panel, occupying
     everything below the picture with the mantra centred in it. */
  .footer {
    flex: 1 1 auto;
    justify-content: center;
    gap: 1.4em;
    padding-bottom: clamp(20px, 5vh, 70px);
  }

  /* Sized for a wall, not a desk. Everything below was tuned at arm's length
     on a monitor and vanished entirely at across-the-room distance. */
  .mantra {
    font-size: clamp(14px, 2.9vw, 36px);
    line-height: 1.9;
    letter-spacing: 0.22em;
    text-indent: 0.22em;
    color: rgba(126, 102, 62, 0.44);
  }

  .affirmation {
    font-size: clamp(21px, 5.4vw, 68px);
    letter-spacing: 0.13em;
    text-indent: 0.13em;
    color: rgba(84, 58, 20, 1);
  }

  .hint { font-size: clamp(13px, 2.6vw, 30px); }

  .status {
    font-size: clamp(11px, 2.1vw, 26px);
    padding-bottom: clamp(14px, 2.4vh, 34px);
  }

  /* The mandala moves out of the viewport background and into the altar panel
     itself. Anchoring it to the footer rather than to the screen means it
     centres on the mantra automatically at any height — the text comes to
     rest in the open heart of the yantra instead of colliding with its rings,
     which is what happens if you position the two independently. */
  .mandala { display: none; }

  .footer::before {
    content: "";
    position: absolute;
    left: 50%;
    top: 50%;
    width: min(70vw, 720px);
    height: min(70vw, 720px);
    transform: translate(-50%, -50%);
    background: center / contain no-repeat var(--mandala);
    opacity: 0.34;
    pointer-events: none;
  }

  .mantra, .hint { position: relative; }

  /* Recentre the ambient glow on the picture rather than the whole screen. */
  .vignette {
    background:
      radial-gradient(118% 62% at 50% 30%, rgba(255, 255, 255, 0) 46%, rgba(150, 118, 66, 0.18) 100%);
  }
}
