/* ============================================================================
   website2tv scene styles.

   Copied from RedmondGuide's public/channel/scene.css. Effects, transitions
   and type roles are unchanged. Removed: the weather panel and the hand-made
   community themes. Added: a separate ground layer, the brand-light and
   brand-dark backgrounds, and the photo, ident and contact layouts.

   Model unchanged and taken from the Scala tapes: a SCENE is a persistent
   chassis plus a sequence of BEATS. Elements fly in individually, hold, fly
   out. The chassis never blanks.

   All motion is CSS. No requestAnimationFrame: it halts in background tabs,
   which would break the 24-hour unattended requirement.
   ========================================================================= */

@property --slat { syntax: '<length>'; inherits: false; initial-value: 0px; }
@property --sweep { syntax: '<angle>'; inherits: false; initial-value: 0deg; }

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; height: 100%; background: #000; overflow: hidden; }

#screen { position: fixed; inset: 0; container-type: size; background: #000; overflow: hidden; }

/* ---------------------------------------------------------------------------
   Scene chassis
   ------------------------------------------------------------------------ */
.scene {
  position: absolute; inset: 0;
  visibility: hidden;
  --px: min(calc(100cqw / 1920), calc(100cqh / 1080));
  --safe: calc(84 * var(--px));
  font-family: var(--text);
  color: var(--ink);
}
.scene.is-live { visibility: visible; }

/* The brand background. Drawn by the theme blocks below. */
.ground { position: absolute; inset: 0; }

/* Photography. Two stacked copies of ONE file: the top one is desaturated and
   fades away, which is the grayscale-into-color dissolve from the tapes. One
   fetch, no second asset. The renderer sets image, position and size. */
.bg, .bg-grey {
  position: absolute; inset: 0;
  background-size: cover; background-position: center; background-repeat: no-repeat;
}
.bg-grey { filter: grayscale(1) contrast(1.08); opacity: 1; }
.scene.is-live .bg-grey { animation: colorize 4200ms 500ms cubic-bezier(0.4,0,0.2,1) forwards; }
@keyframes colorize { to { opacity: 0; } }

.scrim { position: absolute; inset: 0; background: var(--scrim); }

/* The default wash a photograph sits under, so type survives on top of it.
   A channel overrides it in its own CSS with the same selector, or per scene
   with [data-scene-id]; it is not set inline, or nothing could. */
.scene[data-layout="scrim"] {
  --scrim: linear-gradient(100deg, rgba(12,9,16,0.93) 0%, rgba(12,9,16,0.70) 52%, rgba(12,9,16,0.34) 100%);
}

/* Persistent chassis furniture: stays put while beats cycle underneath. */
.chassis { position: absolute; inset: var(--safe); pointer-events: none; z-index: 3; }
.mark { position: absolute; top: 0; left: 0; display: flex; align-items: center; gap: calc(22 * var(--px)); }
.mark img { height: calc(var(--logoh, 76) * var(--px)); width: auto; display: block; }
.mark-name { font-family: var(--display); font-size: calc(40 * var(--px)); line-height: 1; color: var(--accent); }
.mark-sub { position: absolute; top: calc(8 * var(--px)); right: 0; font-size: calc(26 * var(--px)); font-weight: 700; letter-spacing: 0.2em; text-transform: uppercase; color: var(--meta); }
.rule-top { position: absolute; left: 0; right: 0; top: calc(104 * var(--px)); border-top: calc(3 * var(--px)) solid var(--rule); }
.chassis-foot {
  position: absolute; left: 0; right: 0; bottom: 0;
  display: flex; justify-content: space-between; align-items: baseline;
  padding-top: calc(14 * var(--px)); border-top: calc(3 * var(--px)) solid var(--rule);
  font-size: calc(26 * var(--px)); font-weight: 600; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--meta);
}

/* ---------------------------------------------------------------------------
   Beats
   ------------------------------------------------------------------------ */
.beat {
  position: absolute; left: var(--safe); right: var(--safe);
  top: calc(150 * var(--px)); bottom: calc(104 * var(--px));
  display: flex; flex-direction: column; justify-content: center;
  gap: calc(var(--gap, 20) * var(--px));
  visibility: hidden;
}
.beat.is-active { visibility: visible; }

.el { opacity: 0; will-change: opacity, transform, clip-path; }

/* A video is the whole screen. It sits under the chassis and is never
   cropped: letterbox rather than cut off the frame. */
.beat.video-full {
  left: 0; right: 0; top: 0; bottom: 0;
  padding: 0; gap: 0;
}
.beat.video-full video {
  width: 100%; height: 100%; object-fit: contain; background: #000; display: block;
}
.beat.video-full::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(
    180deg,
    rgba(0,0,0,0.60) 0%, rgba(0,0,0,0.28) 12%, transparent 22%,
    transparent 78%, rgba(0,0,0,0.28) 88%, rgba(0,0,0,0.60) 100%
  );
}

/* ---------------------------------------------------------------------------
   LAYOUTS
   ------------------------------------------------------------------------ */

/* photo: the picture shown clearly in a panel on the right, text on the left.
   For listings, products and screenshots, where the picture IS the content.
   Type is sized for the narrower column by layout, never by text length. */
.photo-panel {
  position: absolute; z-index: 1; overflow: hidden;
  top: calc(218 * var(--px)); bottom: calc(160 * var(--px));
  right: var(--safe); width: calc(820 * var(--px));
  background: var(--panel, transparent);
}
.scene[data-layout="photo"] .beat:not(.video-full) { right: calc((84 + 820 + 72) * var(--px)); }
.scene[data-layout="photo"] .hero { font-size: calc(var(--hero,132) * 0.66 * var(--px)); }
.scene[data-layout="photo"] .lede { font-size: calc(40*var(--px)); }

/* stage: a widget that stays up for the whole scene. On the side it takes the
   photo panel's place; at the bottom it spans the frame under the text. The
   channel sizes it with --stage-w or --stage-h, in 1080p pixels. */
.stage-panel {
  position: absolute; z-index: 1;
  display: grid; place-items: center; align-content: center; overflow: hidden;
}
/* The widget is sized by the panel, never the other way round. */
.stage-panel > .widget { max-width: 100%; max-height: 100%; }
.scene[data-stage="side"] .stage-panel {
  top: calc(150 * var(--px)); bottom: calc(104 * var(--px));
  right: var(--safe); width: calc(var(--stage-w, 760) * var(--px));
}
.scene[data-stage="side"] .beat:not(.video-full) { right: calc((84 + var(--stage-w, 760) + 72) * var(--px)); }
.scene[data-stage="side"] .hero { font-size: calc(var(--hero,132) * 0.66 * var(--px)); }
.scene[data-stage="side"] .lede { font-size: calc(40*var(--px)); }

.scene[data-stage="bottom"] .stage-panel {
  left: var(--safe); right: var(--safe);
  bottom: calc(150 * var(--px)); height: calc(var(--stage-h, 300) * var(--px));
}
.scene[data-stage="bottom"] .beat:not(.video-full) { bottom: calc((150 + var(--stage-h, 300) + 40) * var(--px)); }
.scene[data-stage="bottom"] .hero { font-size: calc(var(--hero,132) * 0.72 * var(--px)); }

/* ident: the logo large and centered, the channel's station ID. */
.scene[data-layout="ident"] .mark { display: none; }
.scene[data-layout="ident"] .beat { align-items: center; text-align: center; gap: calc(44 * var(--px)); }
.ident-logo { height: calc(300 * var(--px)); width: auto; max-width: 80%; display: block; }
.scene[data-layout="ident"] .lede { font-size: calc(56*var(--px)); max-width: calc(1400 * var(--px)); }

/* contact: rows on the left, a QR code on the right. */
.scene[data-layout="contact"] .beat { right: calc((84 + 560) * var(--px)); }
.qr {
  position: absolute; right: calc(-560 * var(--px)); top: 50%;
  margin-top: calc(-270 * var(--px)); width: calc(460 * var(--px));
  display: flex; flex-direction: column; align-items: center; gap: calc(22 * var(--px));
}
.qr img { width: 100%; height: auto; display: block; background: #fff; padding: calc(18 * var(--px)); }
.qr-caption { font-size: calc(28*var(--px)); font-weight: 700; letter-spacing: 0.16em; text-transform: uppercase; color: var(--meta); text-align: center; }

/* ---------------------------------------------------------------------------
   ELEMENT ENTRY EFFECTS
   ------------------------------------------------------------------------ */
.beat.is-active .el[data-in="slat"] {
  animation: slat-in var(--dur, 1250ms) var(--delay, 0ms) cubic-bezier(0.33,0,0.2,1) both;
  -webkit-mask-image: repeating-linear-gradient(to bottom, #000 0 var(--slat), transparent var(--slat) 14px);
  mask-image: repeating-linear-gradient(to bottom, #000 0 var(--slat), transparent var(--slat) 14px);
}
@keyframes slat-in {
  0%   { opacity: 0; --slat: 0px; color: var(--flash); transform: translateX(calc(-9 * var(--px))); }
  25%  { opacity: 1; }
  60%  { --slat: 8px; color: var(--flash); transform: translateX(0); }
  100% { opacity: 1; --slat: 14px; color: var(--ink); transform: translateX(0); }
}

.beat.is-active .el[data-in="wipe"]     { animation: wipe-in var(--dur,1000ms) var(--delay,0ms) cubic-bezier(0.2,0,0.1,1) both; }
@keyframes wipe-in { from { opacity:1; clip-path: inset(0 100% 0 0); } to { opacity:1; clip-path: inset(0 0 0 0); } }

.beat.is-active .el[data-in="dissolve"] { animation: dissolve-in var(--dur,1400ms) var(--delay,0ms) ease both; }
@keyframes dissolve-in { from { opacity:0; } to { opacity:1; } }

.beat.is-active .el[data-in="fly"]      { animation: fly-in var(--dur,850ms) var(--delay,0ms) cubic-bezier(0.16,0.8,0.28,1) both; }
@keyframes fly-in { from { opacity:0; transform: translate(calc(-70*var(--px)), calc(26*var(--px))) scale(0.55);} to { opacity:1; transform:none; } }

.beat.is-active .el[data-in="rise"]     { animation: rise-in var(--dur,1000ms) var(--delay,0ms) cubic-bezier(0.16,0.8,0.28,1) both; }
@keyframes rise-in { from { opacity:0; transform: translateY(calc(36*var(--px))); } to { opacity:1; transform:none; } }

/* Image reveals: a shaped clip opening, which is how photographs arrive on the
   tapes rather than simply fading. */
.beat.is-active .el[data-in="iris"]     { animation: iris-in var(--dur,1200ms) var(--delay,0ms) cubic-bezier(0.3,0,0.2,1) both; }
@keyframes iris-in { from { opacity:1; clip-path: circle(0% at 50% 50%); } to { opacity:1; clip-path: circle(75% at 50% 50%); } }

.beat.is-active .el[data-in="barn"]     { animation: barn-in var(--dur,1100ms) var(--delay,0ms) cubic-bezier(0.3,0,0.2,1) both; }
@keyframes barn-in { from { opacity:1; clip-path: inset(0 50% 0 50%); } to { opacity:1; clip-path: inset(0 0 0 0); } }

/* ---------------------------------------------------------------------------
   ELEMENT EXIT EFFECTS
   ------------------------------------------------------------------------ */
.beat.is-out .el { animation: none; opacity: 1; }
.beat.is-out .el[data-out="slide"]   { animation: slide-out var(--odur,620ms) var(--odelay,0ms) cubic-bezier(0.6,0,0.9,0.4) both; }
/* All the way off, never part way: a slide of 125% of the element's own width
   left a short line parked, still opaque, on top of whatever stood beside it.
   A full screen width clears it from anywhere on the stage. */
@keyframes slide-out { from { transform: translateX(0);} to { transform: translateX(100cqw);} }

.beat.is-out .el[data-out="fade"]    { animation: fade-out var(--odur,700ms) var(--odelay,0ms) ease both; }
@keyframes fade-out { from { opacity:1; } to { opacity:0; } }

.beat.is-out .el[data-out="shatter"] {
  animation: shatter-out var(--odur,760ms) var(--odelay,0ms) cubic-bezier(0.7,0,0.9,0.3) both;
  -webkit-mask-image: repeating-linear-gradient(to bottom, #000 0 var(--slat), transparent var(--slat) 14px);
  mask-image: repeating-linear-gradient(to bottom, #000 0 var(--slat), transparent var(--slat) 14px);
}
@keyframes shatter-out { from { opacity:1; --slat:14px; transform: scaleY(1);} 60% { opacity:.9; --slat:3px; } to { opacity:0; --slat:0px; transform: scaleY(0.5);} }

.beat.is-out .el[data-out="irisout"] { animation: iris-out var(--odur,700ms) var(--odelay,0ms) ease-in both; }
@keyframes iris-out { from { clip-path: circle(75% at 50% 50%);} to { clip-path: circle(0% at 50% 50%);} }

/* ---------------------------------------------------------------------------
   SCREEN TRANSITIONS, from the Scala vocabulary.
   The incoming scene sits above the outgoing one and is revealed by a clip.
   One is chosen at random per screen change, never the same twice running, so
   a repeat of the loop looks different. Every one is transform / opacity /
   clip-path only, so they all composite on the GPU.
   ------------------------------------------------------------------------ */
.scene.is-entering { z-index: 5; }
.scene.is-leaving  { z-index: 4; }

/* Basic */
.scene.is-entering[data-tx="cut"]      { animation: none; }
.scene.is-entering[data-tx="dissolve"] { animation: tx-dissolve var(--txd,1100ms) ease both; }
@keyframes tx-dissolve { from { opacity:0; } to { opacity:1; } }

/* Linear / directional wipes: Straight, in four directions */
.scene.is-entering[data-tx="wipe-left"]  { animation: tx-wl var(--txd,1100ms) cubic-bezier(0.4,0,0.2,1) both; }
.scene.is-entering[data-tx="wipe-right"] { animation: tx-wr var(--txd,1100ms) cubic-bezier(0.4,0,0.2,1) both; }
.scene.is-entering[data-tx="wipe-up"]    { animation: tx-wu var(--txd,1100ms) cubic-bezier(0.4,0,0.2,1) both; }
.scene.is-entering[data-tx="wipe-down"]  { animation: tx-wd var(--txd,1100ms) cubic-bezier(0.4,0,0.2,1) both; }
@keyframes tx-wl { from { clip-path: inset(0 0 0 100%);} to { clip-path: inset(0 0 0 0);} }
@keyframes tx-wr { from { clip-path: inset(0 100% 0 0);} to { clip-path: inset(0 0 0 0);} }
@keyframes tx-wu { from { clip-path: inset(100% 0 0 0);} to { clip-path: inset(0 0 0 0);} }
@keyframes tx-wd { from { clip-path: inset(0 0 100% 0);} to { clip-path: inset(0 0 0 0);} }

/* Motion wipes: Scroll In (push) in four directions */
.scene.is-entering[data-tx="scroll-left"]  { animation: tx-sl var(--txd,1100ms) cubic-bezier(0.4,0,0.2,1) both; }
.scene.is-entering[data-tx="scroll-right"] { animation: tx-sr var(--txd,1100ms) cubic-bezier(0.4,0,0.2,1) both; }
.scene.is-entering[data-tx="scroll-up"]    { animation: tx-su var(--txd,1100ms) cubic-bezier(0.4,0,0.2,1) both; }
.scene.is-entering[data-tx="scroll-down"]  { animation: tx-sd var(--txd,1100ms) cubic-bezier(0.4,0,0.2,1) both; }
@keyframes tx-sl { from { transform: translateX(100%);} to { transform:none; } }
@keyframes tx-sr { from { transform: translateX(-100%);} to { transform:none; } }
@keyframes tx-su { from { transform: translateY(100%);} to { transform:none; } }
@keyframes tx-sd { from { transform: translateY(-100%);} to { transform:none; } }

/* Pattern / shaped: Box, Diamond, Circle, Cross */
.scene.is-entering[data-tx="box"]     { animation: tx-box var(--txd,1150ms) cubic-bezier(0.3,0,0.2,1) both; }
@keyframes tx-box { from { clip-path: inset(50% 50% 50% 50%);} to { clip-path: inset(0 0 0 0);} }

.scene.is-entering[data-tx="diamond"] { animation: tx-dia var(--txd,1200ms) cubic-bezier(0.3,0,0.2,1) both; }
@keyframes tx-dia {
  from { clip-path: polygon(50% 50%, 50% 50%, 50% 50%, 50% 50%); }
  to   { clip-path: polygon(50% -60%, 160% 50%, 50% 160%, -60% 50%); }
}

.scene.is-entering[data-tx="circle"]  { animation: tx-cir var(--txd,1150ms) cubic-bezier(0.3,0,0.2,1) both; }
@keyframes tx-cir { from { clip-path: circle(0% at 50% 50%);} to { clip-path: circle(75% at 50% 50%);} }

.scene.is-entering[data-tx="cross"]   { animation: tx-cross var(--txd,1200ms) cubic-bezier(0.3,0,0.2,1) both; }
@keyframes tx-cross {
  from { clip-path: polygon(50% 0,50% 0,50% 0,50% 0,50% 100%,50% 100%,50% 100%,50% 100%,0 50%,0 50%,100% 50%,100% 50%); }
  to   { clip-path: polygon(0 0,100% 0,100% 100%,0 100%,0 100%,0 100%,0 100%,0 100%,0 100%,0 100%,0 100%,0 100%); }
}

/* Corner: reveal sweeping from one corner */
.scene.is-entering[data-tx="corner"]  { animation: tx-corner var(--txd,1150ms) cubic-bezier(0.3,0,0.2,1) both; }
@keyframes tx-corner {
  from { clip-path: polygon(0 0, 0 0, 0 0); }
  to   { clip-path: polygon(0 0, 220% 0, 0 220%); }
}

/* Curtain: two halves opening outward from the center */
.scene.is-entering[data-tx="curtain"] { animation: tx-curtain var(--txd,1200ms) cubic-bezier(0.4,0,0.2,1) both; }
@keyframes tx-curtain { from { clip-path: inset(0 50% 0 50%);} to { clip-path: inset(0 0 0 0);} }

/* Center: a horizontal band opening vertically */
.scene.is-entering[data-tx="center"]  { animation: tx-center var(--txd,1150ms) cubic-bezier(0.4,0,0.2,1) both; }
@keyframes tx-center { from { clip-path: inset(50% 0 50% 0);} to { clip-path: inset(0 0 0 0);} }

/* Radial / clock, both directions, via an animated conic mask */
.scene.is-entering[data-tx="clock-cw"] {
  animation: tx-clock var(--txd,1250ms) linear both;
  -webkit-mask-image: conic-gradient(from 0deg, #000 var(--sweep), transparent 0);
  mask-image: conic-gradient(from 0deg, #000 var(--sweep), transparent 0);
}
.scene.is-entering[data-tx="clock-ccw"] {
  animation: tx-clock var(--txd,1250ms) linear both;
  -webkit-mask-image: conic-gradient(from 0deg, transparent calc(360deg - var(--sweep)), #000 0);
  mask-image: conic-gradient(from 0deg, transparent calc(360deg - var(--sweep)), #000 0);
}
@keyframes tx-clock { from { --sweep: 0deg; } to { --sweep: 360deg; } }

/* ---------------------------------------------------------------------------
   Type roles. On a screen read from across a room, large beats clever.
   A *starred* phrase in the text becomes .em: the site's split-weight
   headline, bold and in the accent color.
   ------------------------------------------------------------------------ */
.hero    { font-family: var(--display); font-size: calc(var(--hero,132)*var(--px)); font-weight: var(--hero-weight, 400); line-height: 1.02; letter-spacing: -0.018em; text-wrap: pretty; }
.kicker  { font-size: calc(32*var(--px)); font-weight: 800; letter-spacing: 0.24em; text-transform: uppercase; color: var(--accent); }
.lede    { font-size: calc(46*var(--px)); line-height: 1.36; color: var(--prose); text-wrap: pretty; }
.em      { font-weight: var(--em-weight, 700); color: var(--accent); }
.datarow { display: flex; align-items: baseline; gap: calc(26*var(--px)); font-size: calc(52*var(--px)); }
.datarow .k { font-size: calc(28*var(--px)); font-weight: 700; letter-spacing: 0.18em; text-transform: uppercase; color: var(--meta); width: calc(var(--kw, 300)*var(--px)); flex-shrink: 0; }

/* ---------------------------------------------------------------------------
   BACKGROUNDS. Every value arrives as theme data from the manifest; these are
   only the defaults. The app picks brand-light or brand-dark by how light the
   brand's ground color is.
   ------------------------------------------------------------------------ */

/* Light brands: paper-like, dark ink, a faint glow of the accent color.
   The route for most business sites, and the one RedmondGuide's "solid" got
   wrong (its darker second stop turned light colors muddy). */
.scene[data-theme="brand-light"] {
  --display: "Helvetica Neue", Arial, sans-serif;
  --text: "Helvetica Neue", Arial, sans-serif;
  --ink: #14181F; --prose: rgba(20,24,31,0.86); --meta: rgba(20,24,31,0.62);
  --accent: #2E6BC6; --rule: rgba(20,24,31,0.28); --flash: #2E6BC6;
  --ground-a: #F5F7FA; --ground-b: #DEE5EE;
  --scrim: none;
}
.scene[data-theme="brand-light"] .ground {
  background-image:
    radial-gradient(ellipse at 88% 6%, color-mix(in srgb, var(--accent) 16%, transparent) 0%, transparent 55%),
    radial-gradient(ellipse at 6% 96%, color-mix(in srgb, var(--accent) 8%, transparent) 0%, transparent 50%),
    linear-gradient(160deg, var(--ground-a) 0%, var(--ground-b) 100%);
}

/* Dark brands: the ground color running to a darker shade, with the accent
   glowing in one corner. */
.scene[data-theme="brand-dark"] {
  --display: "Helvetica Neue", Arial, sans-serif;
  --text: "Helvetica Neue", Arial, sans-serif;
  --ink: #F5F7FA; --prose: rgba(245,247,250,0.9); --meta: rgba(245,247,250,0.66);
  --accent: #7BB0E8; --rule: rgba(245,247,250,0.34); --flash: #7BB0E8;
  --ground-a: #1A2538; --ground-b: #0B111C;
  --scrim: none;
}
.scene[data-theme="brand-dark"] .ground {
  background-image:
    radial-gradient(ellipse at 86% 8%, color-mix(in srgb, var(--accent) 24%, transparent) 0%, transparent 58%),
    linear-gradient(165deg, var(--ground-a) 0%, var(--ground-b) 100%);
}

#hud {
  position: fixed; left: 14px; bottom: 12px; z-index: 50;
  font: 600 12px/1.5 ui-monospace, Menlo, monospace; letter-spacing: 0.06em;
  color: rgba(255,255,255,0.6); background: rgba(0,0,0,0.6);
  padding: 7px 11px; pointer-events: none;
}
