/* Dojan scenes: stage, skies, weather layers, parallax, HUD. Used by the player, the CRT sets and dojan/. */
.scene { position: absolute; inset: 0; overflow: hidden; container-type: size; } /* cq units below scale to the stage, not the viewport */
.scene .rig { position: absolute; left: 0; top: 0; will-change: transform; overflow: visible; }
.scene__shadow { position: absolute; height: 3.5%; border-radius: 50%; background: rgba(0,0,0,0.65); filter: blur(3px); transform: translate(-50%, -50%); }

.scene--wander { background: radial-gradient(ellipse at 50% 85%, #2a2a2f 0%, #121214 45%, #070708 100%); }

/* weather */
.sky { position: absolute; inset: 0; transition: background 2s; }
.sky__stars { position: absolute; inset: 0; transition: opacity 2s; }
.orb { position: absolute; width: 10cqmin; height: 10cqmin; border-radius: 50%; transform: translate(-50%, -50%); transition: left 2s, top 2s, opacity 1s; }
.orb--sun { background: #fedb00; box-shadow: 0 0 40px 10px rgba(254,219,0,0.45); }
.orb--moon { background: #efefef; box-shadow: inset -12px -6px 0 0 #141414, 0 0 30px rgba(255,255,255,0.25); }
.clouds { position: absolute; inset: 0; }
.cloud { position: absolute; left: 0; top: 0; width: 26cqmin; transition: opacity 2s; will-change: transform; }
.cloud svg { width: 100%; height: auto; display: block; }
.haze { position: absolute; inset: 0; background: linear-gradient(rgba(226,190,120,0.25), rgba(226,190,120,0.55)); transition: opacity 2s; }
.rain { position: absolute; inset: 0; width: 100%; height: 100%; }
.flash { position: absolute; inset: 0; background: #fff; opacity: 0; transition: opacity 0.08s; pointer-events: none; }
.ground { position: absolute; left: 0; right: 0; bottom: 0; height: 12%; background: linear-gradient(#1c1c1e, #0e0e10); }
.hud { position: absolute; inset: 0; padding: 7% 8%; display: flex; justify-content: space-between; align-items: flex-start; pointer-events: none; text-shadow: 0 2px 12px rgba(0,0,0,0.5); }
.hud__time { font-size: 15cqmin; letter-spacing: -0.04em; }
.hud__temp { font-size: 8cqmin; text-align: right; }
.hud__date, .hud__cond { color: rgba(255,255,255,0.8); margin-top: 1cqmin; }
.hud__r { text-align: right; }

/* faces */
.scene--faces { background: #1b2a6b; }
.rays { position: absolute; left: 50%; top: 50%; width: 200cqmax; height: 200cqmax; opacity: 0.16; animation: rays 120s linear infinite; }
.rays svg { width: 100%; height: 100%; display: block; }
@keyframes rays { from { transform: translate(-50%, -50%) rotate(0); } to { transform: translate(-50%, -50%) rotate(360deg); } }

/* journey */
.scene__layer { position: absolute; left: 0; bottom: 0; display: flex; will-change: transform; }
.scene__layer--far { bottom: 14%; }
.scene__layer--mid { bottom: 12%; }
.scene .static { width: 100%; height: 100%; z-index: 5; } /* burst canvas is drawn 96×54; stretch it over the stage */
.scene__layer svg { flex: 0 0 auto; display: block; overflow: visible; }
.scene__layer svg + svg { margin-left: -1px; } /* overlap the tile join so no hairline shows */
.scene--journey .orb { left: 78%; top: 26%; }

/* Dojan Chase (shared/chase.js). --t is the tile size the script picks; the box keeps clear of the stand's frame.
   touch-action:none so a swipe steers instead of scrolling the page (or Safari's bar). */
.chase {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; gap: calc(var(--t) * 0.5);
  padding: calc(6px + var(--safe-t)) calc(6px + var(--safe-r)) calc(6px + var(--safe-b)) calc(6px + var(--safe-l));
  background: #000; touch-action: none; user-select: none; -webkit-user-select: none;
}
.chase canvas { display: block; }
.chase__wrap { position: relative; }
.chase__hud { width: calc(var(--t) * 2.5); display: grid; gap: calc(var(--t) * 0.7); align-content: center; }
.chase__hud p { margin: 0; }
.chase__hud .eyebrow { display: block; font-size: max(10px, calc(var(--t) * 0.42)); margin-bottom: 2px; }
.chase__hud b { display: block; font-size: calc(var(--t) * 0.8); color: #fff; }
.chase__lives { display: flex; gap: calc(var(--t) * 0.2); }
.chase__lives i { width: calc(var(--t) * 0.62); height: calc(var(--t) * 0.62); border-radius: 50%; background: conic-gradient(var(--yellow) 0 60deg, transparent 60deg 120deg, var(--yellow) 120deg); }
/* message pill shared by the games (chase.js, hero.js); sized off the --t each game sets */
.game__msg {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); margin: 0; white-space: nowrap; pointer-events: none;
  padding: 0.3em 0.8em; border-radius: 999px; background: rgba(0, 0, 0, 0.78); color: var(--yellow); font-size: calc(var(--t) * 0.9);
}
.game__msg:empty { display: none; }
.game__msg.pulse { animation: game-pulse 1.2s ease-in-out infinite; }
@keyframes game-pulse { 50% { opacity: 0.35; } }

/* Flappy Dojan (shared/hero.js): full-bleed sky; the score and the TV-controls knob stay inside the stand-safe
   insets. --t (H/13) is set by the script. touch-action:none so taps fly instead of scrolling or zooming. */
.hero { position: absolute; inset: 0; overflow: hidden; background: #1b2a6b; touch-action: none; user-select: none; -webkit-user-select: none; }
.hero canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.hero__score { position: absolute; left: 0; right: 0; top: calc(6px + var(--safe-t)); margin: 0; text-align: center; font-size: calc(var(--t) * 2); color: #fff; text-shadow: 0 3px 0 rgba(0, 0, 0, 0.3); pointer-events: none; }
.hero__menu { position: absolute; top: calc(8px + var(--safe-t)); right: calc(8px + var(--safe-r)); }
.hero .game__msg { top: 70%; }

/* Earthrise, was Dojan on the Moon (shared/earth.js): the photo covers the stage; the live globe canvas is placed over its painted-out Earth.
   Caption top-right in the empty sky: top-left ran into the globe on a phone (the cover crop pulls it up-left). */
.scene--moon { background: #050505; }
.moon__bg { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.moon__clip { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }   /* sky-event clips over the still (earth.js SKY) */
.moon__idle { position: absolute; object-fit: fill; pointer-events: none; }   /* Dojan's idle loop over his box, masked (earth.js IDLE) */
.moon__earth { position: absolute; }
.moon__front { position: absolute; pointer-events: none; }   /* a clip's object where it crosses the globe (earth.js front) */
.moon__orbit { position: absolute; pointer-events: none; }   /* the satellite circling the globe (earth.js ORBIT) */
/* Earthrise broadcast graphics (earth.js HUD, 7 Oct 2026): NASA TV's look on purpose, that's the joke ("SACC SPACE").
   Picture graphics like Dojan News, so they carry colour (NASA red dot, blue bar, the badge). Sizes are cq units of the
   stage (one layout for a phone in the stand and a 16:9 desktop), small text has an 11px floor, --safe-* clears the stand.
   NOT mirrored in Arabic (direction: ltr): the photo decides where graphics can sit (Earth top left, Dojan bottom right);
   mirrored, the badge crowded the Earth and the lower third crowded Dojan. Only the Arabic lines run right to left
   (dir=auto). Hidden while the TV's own controls are up, as Dojan News does. */
.moon__hud {
  position: absolute; inset: 0; display: flex; flex-direction: column; justify-content: space-between; pointer-events: none;
  padding: calc(var(--safe-t) + 4.5cqh) calc(var(--safe-r) + 3cqw) calc(var(--safe-b) + 4cqh) calc(var(--safe-l) + 3cqw);
  direction: ltr; color: #fff; font-family: var(--font); font-stretch: 100%; font-style: normal; text-shadow: 0 1px 6px rgba(0,0,0,0.55);
  transition: opacity 0.25s var(--ease);
}
.player:has(.osd.show) .moon__hud { opacity: 0; }
.moon__hud p { margin: 0; }
.moon__top, .moon__bottom { display: flex; justify-content: space-between; align-items: flex-start; gap: 3cqw; }
.moon__bottom { align-items: flex-end; }
.moon__onair { display: flex; align-items: center; gap: 1.4cqh; font-size: max(12px, 3cqh); font-weight: 500; letter-spacing: 0.08em; }
.moon__onair i { width: 1.8cqh; height: 1.8cqh; min-width: 7px; min-height: 7px; border-radius: 50%; background: #fc3d21; box-shadow: 0 0 1.2cqh rgba(252,61,33,0.7); animation: news-pulse 1.4s ease-in-out infinite; }
.moon__onair b { font-weight: 700; letter-spacing: 0.06em; }
.moon__onair span { font-weight: 300; opacity: 0.7; }
.moon__agency { display: flex; align-items: center; gap: 1.4cqh; }
.moon__agency svg { width: 8cqh; height: 8cqh; min-width: 30px; min-height: 30px; filter: drop-shadow(0 1px 4px rgba(0,0,0,0.5)); }
.moon__agency b { display: block; font-size: max(14px, 4.4cqh); font-weight: 800; letter-spacing: 0.02em; line-height: 1; }
.moon__agency em { display: block; margin-top: 0.7cqh; font-style: normal; font-size: max(10px, 1.9cqh); font-weight: 500; letter-spacing: 0.55em; line-height: 1; }
.moon__right { display: flex; flex-direction: column; align-items: flex-end; gap: 1.6cqh; }   /* the credit sits in the dark sky: bottom right ran into Dojan's feet on a phone */
/* a soft dark band behind the lower third, as broadcast lower thirds have: bare, the status was lost on the bright ground */
.moon__lower { padding: 1.3cqh 9cqh 1.3cqh 1.8cqh; padding-inline: 1.8cqh 9cqh; border-inline-start: 0.7cqh solid #2f6bff;
  background: linear-gradient(to right, rgba(6,8,14,0.66), rgba(6,8,14,0.4) 65%, transparent); }
.moon__lower b { display: block; font-size: max(14px, 4.2cqh); font-weight: 700; letter-spacing: 0.04em; line-height: 1.05; }
.moon__lower span { display: block; margin-top: 0.8cqh; font-size: max(11px, 2.5cqh); font-weight: 500; letter-spacing: 0.1em; line-height: 1.1; opacity: 0.92; }
.moon__status.in { animation: news-fade 0.6s var(--ease) both; }
.moon__credit { font-size: 11px; font-weight: 500; letter-spacing: 0.04em; color: rgba(255,255,255,0.45); }

/* Dojan News (CH 05, shared/news.js): live broadcast graphics over the flight loop, after the user's mockup (5 Oct 2026).
   This is part of the channel's picture, like a film, so it carries the brand red and yellow; the black/white/grey rule
   is for the app's own interface. Sizes are cq units of the stage, so one graphic fits a phone in the stand (~2:1) and a
   16:9 desktop; small text has an 11px floor (tokens.css). The frame pads by --safe-* to clear the stand's edges. */
.news { position: absolute; inset: 0; container-type: size; pointer-events: none; color: #fff; font-family: var(--font); }
.news p, .news h2 { margin: 0; }
.news__frame {
  position: absolute; inset: 0; display: flex; flex-direction: column; justify-content: space-between;
  padding: calc(var(--safe-t) + 4cqh) calc(var(--safe-r) + 2.6cqw) calc(var(--safe-b) + 3cqh) calc(var(--safe-l) + 2.6cqw);
}
.news__frame { transition: opacity 0.25s var(--ease); }
.player:has(.osd.show) .news__frame { opacity: 0; }   /* a tap brings up the TV's own number, knob and buttons right where these sit */
.news__top { display: flex; justify-content: space-between; align-items: flex-start; gap: 2cqw; }
.news__live { display: flex; font-weight: 800; letter-spacing: 0.1em; animation: news-in 0.5s calc(var(--in, 0s) + 0.5s) var(--ease) both; }
.news__live b { display: flex; align-items: center; gap: 1.2cqh; padding: 1cqh 2cqh; background: var(--red); font-size: max(12px, 3.6cqh); line-height: 1; }
.news__live i { width: 1.2cqh; height: 1.2cqh; min-width: 5px; min-height: 5px; border-radius: 50%; background: #fff; animation: news-pulse 1.2s ease-in-out infinite; }
.news__live span { display: flex; align-items: center; padding: 0 2.4cqh; background: rgba(20,20,22,0.92); font-size: max(11px, 2.6cqh); }
.news__bug { display: flex; align-items: center; gap: 3cqh; padding: 1.4cqh 2.4cqh; background: rgba(20,20,22,0.92); border-inline-start: 0.8cqh solid var(--yellow); animation: news-in 0.5s calc(var(--in, 0s) + 0.6s) var(--ease) both; }
.news__bug strong { display: block; font-size: max(13px, 3.8cqh); font-weight: 900; letter-spacing: 0.1em; line-height: 1; }
.news__bug em { display: block; margin-top: 0.9cqh; font-style: normal; font-size: max(11px, 2.1cqh); font-weight: 800; letter-spacing: 0.45em; line-height: 1; color: var(--yellow); }
.news__right { display: flex; flex-direction: column; align-items: flex-end; gap: 1.2cqh; }
/* the sky chip (news.js): a small weather-style badge; its swatches are the five skies' own top/bottom colours */
.news__sky {
  display: flex; align-items: center; gap: 1.2cqh; padding: 1cqh 1.6cqh; border: 0; border-radius: 0; cursor: pointer;
  background: rgba(20,20,22,0.92); color: #fff; font: 800 max(11px, 2.1cqh)/1 var(--font); letter-spacing: 0.1em;
  animation: news-in 0.5s calc(var(--in, 0s) + 0.7s) var(--ease) both;
}
.news__sky:focus-visible { outline: 2px solid var(--yellow); outline-offset: 2px; }
.news__sky span b { margin-inline-end: 0.6em; color: var(--yellow); }
.news__sky small { padding: 0.3em 0.5em; border: 1px solid rgba(255,255,255,0.35); font-size: 0.75em; letter-spacing: 0.12em; color: rgba(255,255,255,0.8); }
.news__sky svg { width: max(12px, 2.2cqh); height: max(12px, 2.2cqh); fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; opacity: 0.8; }
.news__swatch { width: max(12px, 2.4cqh); height: max(12px, 2.4cqh); border-radius: 50%; box-shadow: 0 0 0 1px rgba(255,255,255,0.6); }
.news__swatch--dawn { background: linear-gradient(#7f94ef, #fdb19a); }
.news__swatch--noon { background: linear-gradient(#1aaffd, #8fd6ff); }
.news__swatch--afternoon { background: linear-gradient(#127bec, #78c3f0); }
.news__swatch--evening { background: linear-gradient(#68346b, #fda032); }
.news__swatch--night { background: linear-gradient(#041e62, #0c45ac); }
.news__still { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; transition: opacity 0.5s var(--ease); }
.news__still.out { opacity: 0; }
.news__bars { display: flex; align-items: flex-end; gap: 0.5cqh; height: 3.8cqh; }
.news__bars b { width: 0.9cqh; min-width: 3px; background: var(--yellow); }
.news__bars b:nth-child(1) { height: 35%; } .news__bars b:nth-child(2) { height: 60%; }
.news__bars b:nth-child(3) { height: 85%; animation: news-signal 3.1s steps(1) infinite; }
.news__bars b:nth-child(4) { height: 100%; animation: news-signal 2.3s 0.7s steps(1) infinite; }

.news__lower { display: grid; grid-template-columns: minmax(0, 1fr); }   /* else the ticker's long track widens the column */
.news__tab {
  justify-self: start; padding: 0.9cqh 2.2cqh; padding-inline-end: 4.4cqh; background: var(--yellow); color: #141414;
  font-size: max(11px, 3.2cqh); font-weight: 900; letter-spacing: 0.08em; line-height: 1.1;
  clip-path: polygon(0 0, calc(100% - 2.4cqh) 0, 100% 100%, 0 100%); animation: news-in 0.45s calc(var(--in, 0s) + 0.55s) var(--ease) both;
}
.news__bar {
  display: flex; align-items: center; gap: 3cqh; min-height: 8.8cqh; padding: 0.8cqh 2.8cqh;
  background: rgba(20,20,22,0.94); border-inline-start: 0.8cqh solid var(--yellow); animation: news-wipe 0.6s calc(var(--in, 0s) + 0.65s) var(--ease) both;
}
.news__head { flex: 1; min-width: 0; overflow: hidden; white-space: nowrap; font-size: max(14px, 5.2cqh); font-weight: 900; font-stretch: 110%; letter-spacing: 0.03em; line-height: 1.1; }
.news__head span { display: inline-block; animation: news-wipe 0.55s var(--ease) both; }
.news__info { flex: none; padding-inline-start: 2.6cqh; border-inline-start: 1px solid rgba(255,255,255,0.25); line-height: 1.25; }
.news__info > * { animation: news-fade 0.5s var(--ease) both; }   /* children are new on every swap */
.news__info b { display: block; font-size: max(11px, 2.5cqh); font-weight: 800; letter-spacing: 0.12em; color: var(--yellow); }
.news__info span { display: block; font-size: max(11px, 2cqh); letter-spacing: 0.08em; color: rgba(255,255,255,0.85); }

.news__ticker { display: flex; margin-top: 0.8cqh; background: #f1f2f4; color: #141414; animation: news-in 0.5s calc(var(--in, 0s) + 0.8s) var(--ease) both; }
.news__tag { display: flex; align-items: center; padding: 0 2.4cqh; background: var(--yellow); font-size: max(11px, 2.4cqh); font-weight: 900; letter-spacing: 0.12em; }
.news__crawl { flex: 1; min-width: 0; overflow: hidden; display: flex; align-items: center; padding-block: 1.1cqh; font-size: max(11px, 2.4cqh); font-weight: 700; letter-spacing: 0.05em; line-height: 1.2; }
.news__track { display: flex; width: max-content; animation: news-crawl var(--dur, 90s) linear infinite; }
.news__track p, .news__one { display: flex; white-space: nowrap; }
.news__track span::after { content: "•"; margin: 0 2.6cqh; color: #9a9ca4; }
.news__one { padding-inline-start: 2.4cqh; animation: news-fade 0.6s var(--ease) both; }
.news .is-sacc { color: var(--blue); font-weight: 800; }
.news__clock { display: flex; align-items: center; gap: 0.2em; padding: 0 2.2cqh; background: #141414; color: #fff; font-size: max(12px, 2.7cqh); font-weight: 800; font-variant-numeric: tabular-nums; letter-spacing: 0.04em; }
.news__clock i { font-style: normal; animation: news-blink 1s steps(1) infinite; }
.news__clock small { margin-inline-start: 0.25em; font-size: 0.7em; font-weight: 700; }

@keyframes news-in { from { opacity: 0; transform: translateY(1.5cqh); } }
@keyframes news-fade { from { opacity: 0; } }
@keyframes news-wipe { from { clip-path: var(--wipe, inset(0 100% 0 0)); } to { clip-path: inset(0 0 0 0); } }
@keyframes news-crawl { to { transform: translateX(var(--crawl, -50%)); } }
/* Arabic (dir rtl, shared/i18n.js): the graphics mirror (logical sides above), the wipes reveal from the right and the
   ticker crawls left→right, as on Arabic news channels: the track starts at the right edge, so +50% is its seamless loop.
   The intro's streaks stay as they are: they fly along Dojan's heading in the film, which doesn't mirror. */
[dir="rtl"] .news { --wipe: inset(0 0 0 100%); --crawl: 50%; }
[dir="rtl"] .news__tab { clip-path: polygon(2.4cqh 0, 100% 0, 100% 100%, 0 100%); }
/* the clock's parts are flex items, so RTL laid 06:33 out as 33:06. Digits always run left to right; Arabic's ص/م sits
   on their left, where Arabic text puts it ("06:33 م" reads right to left). */
.news__clock { direction: ltr; }
[dir="rtl"] .news__clock small { order: -1; margin: 0 0.25em 0 0; }
@keyframes news-pulse { 50% { opacity: 0.25; } }
@keyframes news-blink { 50% { opacity: 0.2; } }
@keyframes news-signal { 0%, 70% { opacity: 1; } 71%, 80% { opacity: 0.25; } 81% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .news, .news * { animation: none !important; } }

/* Intro (news.js): once per page visit when the channel comes on, timed to the tedbear sting (loops-src/newsaudio.py).
   After its 0.05 s trim the sting's build lands at 0.03 / 0.40 / 0.65 / 0.90 s, the hit at 1.18 s; it sustains to ~2.75 s
   and is silent by 3.55 s. Everything here waits --t0 first: the TV's 0.38 s static burst (player.js), which is when
   news.js rewinds and unmutes the sting. The streaks fly along Dojan's 43° heading, like the clouds. The broadcast
   graphics then come in after --in (their entrance delays above). The tagline, the white flash and the logo's
   signal bars were cut at the user's request (5 Oct 2026). */
.news--intro { --t0: 0.38s; --in: 3.1s; }   /* the other graphics start as the logo lands in the corner */
/* The ending (user: "has to be more tv intro like, not a wipe"): the stripes shoot off along the heading, the wash
   fades as the picture pushes in (camera landing on the live shot), and the card shrinks into the corner onto the
   DOJAN NEWS bug, which takes over as it lands: the classic ident-into-station-logo move. news.js measures the bug and
   sets --fx/--fy/--fk on the card; everything stays on this CSS timeline. */
.news__intro { position: absolute; inset: 0; overflow: hidden; }
/* The wash covers the picture from the first frame: it used to wipe in after the static, and the flight showed for a
   moment first (user, 5 Oct 2026: "it shows the flying a second before the intro starts"). */
.ni__wash { position: absolute; inset: 0; background: #141414; animation: ni-fade 0.5s calc(var(--t0) + 2.62s) ease-in forwards; }
/* stripes shoot in along the heading on the three build beats and stay, as bands behind the card (first try: short
   streaks that flew straight through were on screen for under 0.35 s and never read) */
.ni__streak { position: absolute; left: 50%; width: 220cqw; margin-left: -110cqw; animation: ni-streak 0.5s cubic-bezier(0.2, 0.75, 0.2, 1) both, ni-streak-out 0.45s cubic-bezier(0.6, 0, 0.9, 0.5) forwards; }
.ni__streak--1 { top: 24%; height: 7cqh; background: var(--red); animation-delay: calc(var(--t0) + 0.4s), calc(var(--t0) + 2.5s); }
.ni__streak--2 { top: 46%; height: 4.5cqh; background: var(--yellow); animation-delay: calc(var(--t0) + 0.65s), calc(var(--t0) + 2.56s); }
.ni__streak--3 { top: 66%; height: 6cqh; background: var(--blue); animation-delay: calc(var(--t0) + 0.9s), calc(var(--t0) + 2.62s); }
.ni__card { position: absolute; inset: 0; display: grid; place-content: center; justify-items: center; gap: 3.5cqh; }
/* the card is the bug (.news__bug, without its bars) scaled up by --S, so flying into the corner lands exactly on it */
.ni__mark { --S: 3.68; display: flex; align-items: center; padding: calc(1.4cqh * var(--S)) calc(2.4cqh * var(--S)); background: #141414; box-shadow: 0 2cqh 6cqh rgba(0,0,0,0.5); border-inline-start: calc(0.8cqh * var(--S)) solid var(--yellow); animation: ni-slam 0.38s calc(var(--t0) + 0.96s) cubic-bezier(0.2, 0.7, 0.2, 1) both, ni-to-bug 0.62s calc(var(--t0) + 2.6s) cubic-bezier(0.6, 0, 0.2, 1) forwards; }
.ni__logo { margin: 0; }
.ni__logo strong { display: block; font-size: calc(3.8cqh * var(--S)); font-weight: 900; letter-spacing: 0.1em; line-height: 1; }
.ni__logo em { display: block; margin-top: calc(0.9cqh * var(--S)); font-style: normal; font-size: calc(2.1cqh * var(--S)); font-weight: 800; letter-spacing: 0.45em; line-height: 1; color: var(--yellow); }
@keyframes ni-streak-out { from { transform: rotate(-43deg) translateX(0); } to { transform: rotate(-43deg) translateX(240cqw); } }
@keyframes ni-fade { to { opacity: 0; } }
@keyframes ni-to-bug { 85% { opacity: 1; } to { transform: translate(var(--fx), var(--fy)) scale(var(--fk)); box-shadow: none; opacity: 0; } }
@keyframes ni-in { from { opacity: 0; } }
@keyframes ni-push { from { transform: scale(1.18); } }
/* the bug skips its own entrance and appears as the card lands on it; the film pushes in under the fading wash
   (literal times: the <video> sits outside .news, so it can't read --t0 = 0.38 s) */
.news--intro .news__bug { animation: ni-in 0.15s calc(var(--t0) + 3.08s) both; }
.player:has(.news--intro) video { animation: ni-push 0.9s 3s cubic-bezier(0.2, 0.7, 0.2, 1) both; }
@keyframes ni-streak { from { transform: rotate(-43deg) translateX(-240cqw); } to { transform: rotate(-43deg) translateX(0); } }
@keyframes ni-slam { 0% { opacity: 0; transform: scale(1.7); filter: blur(6px); } 60% { opacity: 1; transform: scale(0.96); filter: blur(0); } to { transform: scale(1); } }
/* Dojan Cam (CH 04, shared/cam.js): a webcam's display over the Peek film: viewfinder corners, a blinking REC and the
   viewer's clock. White and thin like a real camera's, so the picture stays his; the REC dot gets a white ring because
   the film's ground is the same red. Same sizing and safe-area rules as Dojan News; hidden while the TV's OSD is up. */
.cam { position: absolute; inset: 0; container-type: size; pointer-events: none; color: #fff; font-family: var(--font); transition: opacity 0.25s var(--ease); }
.player:has(.osd.show) .cam { opacity: 0; }
.cam p { margin: 0; }
.cam__rec, .cam__clock { position: absolute; top: calc(var(--safe-t) + 7cqh); font-size: max(11px, 3.2cqh); font-weight: 800; line-height: 1; letter-spacing: 0.12em; font-variant-numeric: tabular-nums; text-shadow: 0 0 0.8cqh rgba(0,0,0,0.55); }
.cam__rec { inset-inline-start: calc(var(--safe-l) + 6.5cqw); display: flex; align-items: center; gap: 1.4cqh; }
.cam__rec i { width: 2cqh; height: 2cqh; min-width: 7px; min-height: 7px; border-radius: 50%; background: var(--red); box-shadow: 0 0 0 max(1.5px, 0.35cqh) #fff; animation: news-blink 1.2s steps(1) infinite; }
.cam__clock { inset-inline-end: calc(var(--safe-r) + 6.5cqw); }
.cam__c { position: absolute; width: 7cqh; height: 7cqh; border: 0 solid rgba(255,255,255,0.9); }
.cam__c--tl { top: calc(var(--safe-t) + 3cqh); left: calc(var(--safe-l) + 3cqw); border-top-width: max(2px, 0.5cqh); border-left-width: max(2px, 0.5cqh); }
.cam__c--tr { top: calc(var(--safe-t) + 3cqh); right: calc(var(--safe-r) + 3cqw); border-top-width: max(2px, 0.5cqh); border-right-width: max(2px, 0.5cqh); }
.cam__c--bl { bottom: calc(var(--safe-b) + 3cqh); left: calc(var(--safe-l) + 3cqw); border-bottom-width: max(2px, 0.5cqh); border-left-width: max(2px, 0.5cqh); }
.cam__c--br { bottom: calc(var(--safe-b) + 3cqh); right: calc(var(--safe-r) + 3cqw); border-bottom-width: max(2px, 0.5cqh); border-right-width: max(2px, 0.5cqh); }
@media (prefers-reduced-motion: reduce) { .cam__rec i { animation: none; } }

/* Deep Dive (CH 07, shared/deep.js): channel graphics after the user's mockup (7 Oct 2026), like a documentary channel's: a
   corner bug that stays, and a lower-third strap ("Ocean exploration / Into the deep / An expedition with Dojan") that comes
   and goes. Same rules as Dojan News: cq units of the stage, --safe-* clear of the stand, an 11px floor for small text,
   hidden while the TV's OSD is up. The bug's mark is a TV screen with antennas (the user's pick of three, 7 Oct), not the
   mockup's yellow upright rectangle: beside "GEO" that rectangle is National Geographic's registered logo (the intro avoids
   Netflix's ta-dum for the same reason). Tried: a porthole ring (plain), the client's Dojan head as an outline. */
.deep { position: absolute; inset: 0; container-type: size; pointer-events: none; color: #fff; font-family: var(--font); transition: opacity 0.25s var(--ease); }
.player:has(.osd.show) .deep { opacity: 0; }
.deep p, .deep h2 { margin: 0; }
.deep__bug {
  position: absolute; top: calc(var(--safe-t) + 5cqh); inset-inline-end: calc(var(--safe-r) + 4cqw);
  display: flex; align-items: center; gap: 1.5cqh; animation: news-fade 1.2s 0.8s var(--ease) both;
}
.deep__bug svg { height: max(20px, 6.4cqh); aspect-ratio: 64 / 56; overflow: visible; fill: none; stroke: var(--yellow); stroke-width: max(2px, 0.5cqh); stroke-linecap: round; stroke-linejoin: round; }
.deep__bug svg * { vector-effect: non-scaling-stroke; }   /* the line keeps its px width whatever the size */
.deep__bug b { font-size: max(11px, 2.4cqh); font-weight: 800; letter-spacing: 0.1em; line-height: 1.15; text-shadow: 0 1px 0.6cqh rgba(0,0,0,0.55); }
.deep__strap {
  position: absolute; bottom: calc(var(--safe-b) + 7cqh); inset-inline-start: calc(var(--safe-l) + 4cqw);
  padding-inline-start: max(10px, 2.1cqh); opacity: 0; transition: opacity 0.7s var(--ease);
  text-shadow: 0 1px 0.8cqh rgba(0,0,0,0.6);
}
.deep__strap::before { content: ""; position: absolute; inset-block: 0.4cqh; inset-inline-start: 0; width: max(2px, 0.4cqh); background: var(--yellow); transform-origin: bottom; }
.deep__strap.on { opacity: 1; }
.deep__strap.on::before { animation: deep-bar 0.6s var(--ease) both; }
.deep__strap.on > * { animation: news-in 0.6s var(--ease) both; }
.deep__strap.on > :nth-child(1) { animation-delay: 0.15s; }
.deep__strap.on > :nth-child(2) { animation-delay: 0.3s; }
.deep__strap.on > :nth-child(3) { animation-delay: 0.45s; }
.deep__kicker { font-size: max(11px, 1.6cqh); font-weight: 800; letter-spacing: 0.32em; line-height: 1; color: var(--yellow); text-transform: uppercase; }
.deep .deep__title { margin-block: 1cqh 0.9cqh; font-size: max(20px, 4.6cqh); font-weight: 800; letter-spacing: 0.01em; line-height: 1; text-transform: uppercase; }
.deep__sub { font-size: max(11px, 1.7cqh); font-weight: 500; letter-spacing: 0.14em; line-height: 1; color: rgba(255,255,255,0.88); text-transform: uppercase; }
/* Arabic: no spaced capitals (letter-spacing pulls joined letters apart); the strap and bug mirror by their logical sides */
:lang(ar) .deep__kicker, :lang(ar) .deep__title, :lang(ar) .deep__sub { letter-spacing: 0; line-height: 1.3; }
@keyframes deep-bar { from { transform: scaleY(0); } }
@media (prefers-reduced-motion: reduce) { .deep__strap.on::before, .deep__strap.on > * { animation: none; } }
