/* ==========================================================================
   Home page. Loaded only on "/". Tokens, the tab bar, the status strip, the
   band and the window kit come from site.css.

   The home page is ONE full-viewport scene (no scrolling on desktop, >= 900px
   wide and >= 600px tall; smaller screens stack and scroll), laid out on one
   grid shared by the <header> and the body (the header is a subgrid of <body>):

     cols   gutter | LEFT | MIDDLE | FRAME | gutter      (every gap is one gutter, --yh-g)
     rows   1 tabs (nav, account tab) / 2 status strip / 3 dashed band /
            4-5 the stage (to the bottom edge of the window: the home page has no footer band, only the row of social icons
            floating on the video at the lower right)

   The left side is a stack of two big flat type cells (a pxpush-style link list at giant size): the blue call-to-action and, under
   it, "о нас". The stack is as wide as the room left of the model's outer orbit, so the two never cross (--scene-cx / --scene-d,
   shared by the model and the menu), and it is centred on the model's height. MIDDLE is the model's (it is placed from the
   viewport, see below); FRAME holds the video frame at the top. The model is 1.35x its orbits, with a text ring
   («CYIIIECTBA» on a tilted 3D circle) passing behind and in front of it.

   The stage is the looping landscape video (quantised to a small palette with an
   ordered dither by public/js/home-bg.js) under a paper veil with the line-art.
   Layers, bottom to top: video / veil / watermark (.banner), the frame, the flare (the star at the frame's left edge, its
   glow and spikes), the model with its orbits and the back half of its text ring, the front half of the ring, the status strip
   and the tabs, the two cells (the <main>), the social icons (the footer).
   Everything that holds text sits on a solid plate, so the video never has to be readable under type.
   ========================================================================== */

/* ---------- the header: one grid, stacked first (phones, tablets, short screens) ---------- */

.site-header.site-header--banner {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
  padding: var(--hdr-pad-t) var(--yh-g) 0;
  color: var(--th-ink);
}
.site-header--banner > .header-nav { grid-row: 1; grid-column: 1; }
/* the top bar is the shared slim header (site.css "The header": the same tokens size the tabs, the strip and the chip; the scene starts under
   it). The status strip and the account chip are one flex row, .hud-top, exactly as the slim header lays them out: under 900px the chip
   above the strip, from 900px the strip at the left and the chip (the same width on every page) at the right. */
.site-header--banner > .hud-top {
  display: flex;
  flex-direction: column;
  grid-row: 2 / 4;
  grid-column: 1;
  min-width: 0;
}
@media (min-width: 900px) {
  .site-header--banner > .hud-top { flex-direction: row; column-gap: var(--hdr-col-gap); align-items: flex-start; }
  .site-header--banner .hud-bar { order: -1; flex: 1 1 0; min-width: 0; }
  .site-header--banner .hud-chip { flex: none; }
}
/* no plate behind the tabs and the strip: the header's fill is the scene itself (--hdr-plate: transparent). What can pass behind the text
   is the dotted paper (>= 7.6:1 for the ink, >= 4.6:1 for the soft chip text at its darkest), the model's white body and the thin
   strokes of its outline and orbits: a paper-coloured halo (--hdr-halo) round the letters knocks those strokes out where they cross a
   glyph, so no plate is needed (tests/header-geometry.test.js). */
body {
  --hdr-plate: transparent;
  --hdr-halo: 0 0 2px var(--hdr-bg), 0 0 2px var(--hdr-bg), 0 0 4px var(--hdr-bg);
}
.site-header--banner .header-nav a:not([aria-current]):not(.header-nav__cta):not(:hover):not(:focus-visible),
.site-header--banner .hud-bar,
.site-header--banner .hud-chip__text { text-shadow: var(--hdr-halo); }
/* (the dashed chevron band that used to run here is gone with the slim header; this empty 12px spacer keeps the stage where it was) */
.site-header--banner::after {
  content: "";
  grid-row: 4;
  grid-column: 1;
  display: block;
  height: 12px;
  margin-top: 6px;
  pointer-events: none;
}
.site-header--banner > .banner {
  grid-row: 5 / 7;
  grid-column: 1;
  position: relative;
  z-index: 0;
  align-self: stretch;
  margin: 0 calc(var(--yh-g) * -1);
  overflow: hidden;
  isolation: isolate;
  background: #b8cfe3;
}
.site-header--banner > .banner-model {
  grid-row: 6;
  grid-column: 1;
  z-index: 2;
  align-self: center;
  justify-self: center;
  margin: 12px 0 32px;
}
/* the frame is a desktop piece (it sits beside the model); stacked screens go without it */
.site-header--banner > .home-frame { display: none; }

/* ---------- backdrop layers (the .banner wrapper is aria-hidden in the markup) ----------
   Bottom to top: the poster (a pixelated CSS background until home-bg.js has the
   canvas drawing), the video canvas, the paper veil + line-art, the watermark. */

.home-video {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  background: #b8cfe3 url("/video/home-bg-poster.jpg") center / cover no-repeat;
  image-rendering: pixelated;
}
.home-video__el,
.home-video__canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  image-rendering: pixelated;
  opacity: 0;
}
.home-video__canvas { transition: opacity 0.5s ease; }
/* no canvas (script or 2D context missing): the plain <video> shows, scaled up pixelated */
.home-video.is-playing:not(.has-canvas) .home-video__el { opacity: 1; }
.home-video.has-canvas .home-video__canvas { opacity: 1; }

/* the dotted texture (public/js/portfolio-dots.js, the same module as /events): a halftone / noise dot screen over the video, with a
   torn cloud round the pointer that clears the dots. The canvas lies in the backdrop (.banner is its host and its section: the module
   reads --pf-bg / --pf-dots here and lays dots only over the banner's rows), first in it, so it is above the video (z 0) and under the
   paper veil (z 1, which comes later), the watermark, the frame, the model, the tabs and the cells (all of which are above the
   backdrop's own stacking context or hold their text on plates). The tones are blue-white: the dots are a darker sky blue than
   the sky of the video (blue hue 208-212, no pink / violet); the tint is translucent, so on the green hills it only cools them.
   It does not take the pointer. Lean mode (home-lean.js) hides it and stops it; reduced motion and touch keep one still frame. */
.site-header--banner > .banner {
  --pf-bg: #b8cfe3;
  --pf-dots: #8aacce;
  /* the page above the scene (the header's strip, down from y = 0 behind the tabs and the status strip) is the paper: the same dots,
     one step paler (portfolio-dots.js, --pf-above-*). The plates of the tabs and the strip are solid, so no dot lies under a letter. */
  --pf-above-bg: #e3e9ef;
  --pf-above-dots: #b8cce1;
}
.banner > .pf-dots {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  pointer-events: none;
  image-rendering: pixelated;
}

/* the paper veil: a soft fade of the video into the page at the top of the stage,
   and the line-art (arcs and diagonals) crossing the whole scene */
.home-fog {
  position: absolute;
  inset: 0;
  z-index: 1;
  background:
    var(--yh-lineart) center / cover no-repeat,
    linear-gradient(180deg, rgba(244, 247, 250, 0.5) 0%, rgba(244, 247, 250, 0) 42%);
}

/* the faint outline watermark logotype, cropped by the bottom edge of the stage */
.home-watermark {
  position: absolute;
  z-index: 2;
  left: 50%;
  bottom: -0.1em;
  color: transparent;
  font-family: var(--hud-display);
  font-size: clamp(4.5rem, 18vw, 20rem);
  font-weight: 300;
  letter-spacing: 0.02em;
  line-height: 1;
  white-space: nowrap;
  -webkit-text-stroke: 1px rgba(244, 247, 250, 0.5);
  transform: translateX(-50%);
  pointer-events: none;
  user-select: none;
}

/* optional artwork (only present when the file exists, see docs/art-slots.md) */
.banner-art-img,
.banner-mascot-img,
.banner-leaves-img {
  position: absolute;
  pointer-events: none;
  user-select: none;
}
.banner-art-img {
  z-index: 3;
  right: 0;
  bottom: 0;
  height: 100%;
  width: auto;
  max-width: 62%;
  object-fit: contain;
  object-position: right bottom;
}
.banner-mascot-img {
  z-index: 3;
  right: 2%;
  bottom: 0;
  height: 86%;
  width: auto;
  max-width: 30%;
  object-fit: contain;
  object-position: right bottom;
}
.site-header--banner > .banner-leaves-img {
  grid-row: 5 / 7;
  grid-column: 1;
  z-index: 3;
  align-self: start;
  width: 100%;
  max-height: 70%;
  object-fit: cover;
  object-position: top;
}

/* ---------- the wordmark: not drawn on the scene any more ----------
   «CYIIIECTBA» is the text ring round the model (public/js/home-ring.js). The h1 link stays for readers and search
   engines, visually hidden (it shows as a small tag while the keyboard is on it). With a logo file in the art slot
   (docs/art-slots.md) the same link shows that picture, bare, above the buttons (.banner-inner--logo). */

.banner-inner {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  border: 0;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}
.banner-inner:focus-within {
  z-index: 20;
  left: var(--yh-g);
  top: 6px;
  width: auto;
  height: auto;
  margin: 0;
  padding: 4px 12px;
  overflow: visible;
  border: 1px solid var(--yh-ink);
  background: var(--yh-paper-hi);
  clip: auto;
  clip-path: none;
}
.banner-logo {
  margin: 0;
  font-size: 1rem;
  line-height: 1.2;
}
.banner-logo a {
  display: inline-block;
  color: var(--yh-ink);
  font-family: var(--hud-mono);
  text-decoration: none;
}
.banner-logo a:focus-visible {
  outline: 2px solid var(--yh-ink);
  outline-offset: 2px;
}
.banner-logo-img {
  display: block;
  width: auto;
  height: auto;
  max-width: min(360px, 70vw);
  max-height: 72px;
}
/* the logo art, when the file exists: bare (no plate), on the scene, above the buttons */
.banner-inner.banner-inner--logo {
  position: relative;
  z-index: 4;
  width: auto;
  height: auto;
  margin: 24px 0 0;
  overflow: visible;
  clip: auto;
  clip-path: none;
  justify-self: center;
  grid-row: 5;
  grid-column: 1;
}


/* ---------- the 3D model, its orbits and its text ring ----------
   viewer3d.js renders 324x243 pixels into .retro-pixel-canvas; CSS scales it up
   (pixelated). --model-d is the diameter of the model's ring; every orbit, tick and line of
   .model-glow is a fraction of that diameter, so they follow the model and scale with it. The
   model's own box is --model-k (1.35) times bigger than the ring, 4:3 (one bitmap pixel stays
   --model-d / 180 wide, as before), so the model can grow past the orbits instead of being
   cropped at the ring: viewer3d.js fits the camera to the model's bounding sphere so its widest
   reach covers the orbits. The two text-ring canvases (home-ring.js) share that box: one under
   the model, one above it. The box itself is click-through; only the model canvas takes pointer
   events, and only inside a circle (clip-path), so the 4:3 box never blocks anything beside the
   ring (the buttons, the tabs). */

.banner-model {
  --model-d: min(54svh, 360px);   /* the stacked default; the desktop scene sets its own */
  --model-k: 1.35;                /* the model's box / the ring's diameter */
  position: relative;
  height: calc(var(--model-d) * var(--model-k));
  aspect-ratio: 4 / 3;
  max-width: 100%;
  pointer-events: none;
}
.model-glow {
  position: absolute;
  left: 50%;
  top: 50%;
  width: var(--model-d);
  height: var(--model-d);
  transform: translate(-50%, -50%);
  pointer-events: none;
}
.model-glow > i {
  position: absolute;
  display: block;
  pointer-events: none;
}
/* the ring: a hairline circle with a pale inner line, so it reads on the sky and on the hills */
.mg-ring {
  inset: 0;
  border: 1px solid var(--yh-ink);
  border-radius: 50%;
  box-shadow: inset 0 0 0 1px rgba(244, 247, 250, 0.75);
}
/* the blue arc: a quarter of a second circle, a little bigger, drifting to and fro */
.mg-arc {
  inset: -3%;
  border: 2px solid transparent;
  border-bottom-color: var(--yh-accent);
  border-right-color: var(--yh-accent);
  border-radius: 50%;
  transform: rotate(-12deg);
  animation: mg-arc 14s ease-in-out infinite alternate;
}
/* two dashed orbits turning against each other */
.mg-dash { border-radius: 50%; }
.mg-dash--out {
  inset: -5.5%;
  border: 1px dashed rgba(30, 48, 73, 0.6);
  animation: mg-spin 120s linear infinite;
}
.mg-dash--in {
  inset: 8%;
  border: 1px dashed rgba(244, 247, 250, 0.8);
  animation: mg-spin 90s linear infinite reverse;
}
/* the satellite: a small square travelling round the outer orbit */
.mg-orbit {
  inset: -5.5%;
  animation: mg-spin 26s linear infinite;
}
.mg-orbit b {
  position: absolute;
  left: 50%;
  top: 0;
  width: 9px;
  height: 9px;
  margin: -4.5px 0 0 -4.5px;
  background: var(--yh-accent);
  box-shadow: 0 0 0 1px var(--yh-paper-hi);
}
/* four ticks on the ring that pulse one after the other */
.mg-tick {
  left: 50%;
  top: 50%;
  width: 2px;
  height: 14px;
  margin: -7px 0 0 -1px;
  background: var(--yh-ink);
  transform: rotate(var(--a, 0deg)) translateY(calc(var(--model-d) / -2));
  animation: mg-pulse 3.2s ease-out infinite;
  animation-delay: var(--t, 0s);
}
.mg-tick--n { --a: 0deg; --t: 0s; }
.mg-tick--e { --a: 90deg; --t: 0.8s; }
.mg-tick--s { --a: 180deg; --t: 1.6s; }
.mg-tick--w { --a: 270deg; --t: 2.4s; }
/* a thin line that draws itself out of the ring, to the right, ending in a small square */
.mg-line {
  left: 100%;
  top: 50%;
  width: 16%;
  height: 1px;
  background: var(--yh-ink);
  transform-origin: 0 50%;
  animation: mg-draw 8s steps(16) infinite;
}
.mg-line::after {
  content: "";
  position: absolute;
  right: -3px;
  top: -3px;
  width: 7px;
  height: 7px;
  background: var(--yh-ink);
}

@keyframes mg-spin { to { transform: rotate(360deg); } }
@keyframes mg-arc {
  from { transform: rotate(-12deg); }
  to { transform: rotate(18deg); }
}
@keyframes mg-pulse {
  0% { opacity: 1; }
  30% { opacity: 0.25; }
  100% { opacity: 0.25; }
}
@keyframes mg-draw {
  0% { transform: scaleX(0); opacity: 1; }
  40%, 80% { transform: scaleX(1); opacity: 1; }
  100% { transform: scaleX(1); opacity: 0; }
}

.retro-pixel-canvas {
  position: relative;
  z-index: 1;
  width: 100%;
  height: 100%;
}
.retro-pixel-canvas canvas {
  display: block;
  width: 100%;
  height: auto;
  cursor: grab;
  /* only a circle a little wider than the model's reach (0.62 of the ring's diameter) takes the pointer (and shows the model) */
  clip-path: circle(calc(var(--model-d) * 0.66) at 50% 50%);
  /* The model is white; against the scene it gets a crisp ink outline (four hard
     2px offsets follow the alpha silhouette). No soft shadow. */
  filter:
    hue-rotate(-56deg) saturate(0.55)
    drop-shadow(2px 0 0 var(--yh-ink))
    drop-shadow(-2px 0 0 var(--yh-ink))
    drop-shadow(0 2px 0 var(--yh-ink))
    drop-shadow(0 -2px 0 var(--yh-ink));
}
.retro-pixel-canvas canvas:active { cursor: grabbing; }

/* ---------- the model pops in ----------
   The MODEL (this box, the 3D canvas) scales in from 0 to 105% and settles on 100% like a spring; the orbits and the text ring
   stay as they are. It uses the separate `scale` property (compositor-only), so the lean of the whole box on a lit cell
   (.banner-model, transform), the drag and the outline filter do not clash with it. public/js/viewer3d.js: .is-model-wait while the
   model loads (scale 0, so nothing shows at full size before the pop), then .is-model-pop once the first frame is drawn and the
   scene has had its first 0.3 s (the class is taken off at the end; the keyframes yh-model-pop are in site.css with the other motion). If the model never loads, the wait is lifted and nothing is stuck.
   The curve is a damped spring in 81 samples, one per 12.5 ms of 1000 ms: 0 -> 1.05 in 275 ms (ease-out), then a spring released from
   105% at rest (mass 1, stiffness k = 22^2 = 484, damping c = 2 * 0.25 * 22 = 11): 97.8% at 425 ms, 101.0% at 575 ms, 99.6% at 712 ms,
   100% at 1 s (tests/home-model-pop.test.js recomputes it). Browsers without linear() keep the cubic-bezier overshoot before it. */
.retro-pixel-canvas.is-model-wait { scale: 0; }
.retro-pixel-canvas.is-model-pop {
  animation: yh-model-pop 1000ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
  animation-timing-function: linear(0, .1368, .2611, .3736, .4749, .5655, .6461, .7172, .7794, .8334, .8796, .9188, .9514, .9781, .9995, 1.0162, 1.0287, 1.0377, 1.0437, 1.0473, 1.0492, 1.0499, 1.05, 1.0482, 1.0433, 1.0359, 1.027, 1.0173, 1.0077, .9987, .991, .9849, .9807, .9783, .9778, .9789, .9814, .9848, .9889, .9932, .9974, 1.0013, 1.0046, 1.0071, 1.0089, 1.0097, 1.0098, 1.0092, 1.008, 1.0064, 1.0046, 1.0026, 1.0008, .9991, .9977, .9966, .996, .9956, .9957, .996, .9966, .9973, .9981, .999, .9998, 1.0005, 1.0011, 1.0016, 1.0018, 1.0019, 1.0019, 1.0017, 1.0015, 1.0011, 1.0007, 1.0004, 1, .9997, .9995, .9993, 1);
}
/* leaving the page: viewer3d.js names the box (.is-model-leaving) when a view transition starts, the model's own snapshot is
   animated by ::view-transition-old(yh-model) in site.css (the destination page's stylesheet runs the transition) */
.retro-pixel-canvas.is-model-leaving { view-transition-name: yh-model; }

/* the text ring: two low-res canvases (home-ring.js), the back half under the model, the front half over it */
.model-ring {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  image-rendering: pixelated;
  pointer-events: none;
  user-select: none;
}
.model-ring--back { z-index: 0; }
.model-ring--front { z-index: 2; }

.model-hint {
  position: absolute;
  z-index: 3;
  left: 50%;
  bottom: calc(var(--model-d) * (var(--model-k) - 1) / 2);   /* on the ring's lower edge */
  padding: 3px 12px;
  border: 1px solid var(--yh-ink);
  background: var(--yh-paper-hi);
  color: var(--yh-ink);
  font-family: var(--hud-mono);
  font-size: 0.66rem;
  letter-spacing: 0.04em;
  line-height: 1.4;
  white-space: nowrap;
  transform: translateX(-50%);
  transition: opacity 0.6s ease, visibility 0.6s;
}
.banner-model.is-touched .model-hint {
  opacity: 0;
  visibility: hidden;
}

/* ---------- the left side of the scene: two big type cells (the <main>) ----------
   A pxpush-style link list at giant size (the bottom cells of pxpush.com, the .pf-btn of /events): every cell is a hairline on top, a
   mono row (number, status, arrow) and a giant Inter Tight label, flat on a plate, no drawn shape. The first cell is the call-to-action
   (vibrant blue plate, white type), the second is "о нас" (a <details>, paper plate, ink type). On hover (and keyboard focus) a fill
   rises from the bottom (the cell's text colour flips with it), the label leans in and grows a little, the arrow moves, the letters
   roll (public/js/home-cta.js) and a cloud of dots with a thin film of colour follows the pointer inside the cell; the orbit text
   ring speeds up and the model leans towards the cell. The sizes come from the width of the menu (container units, cqw): the
   desktop sets that width from the model's orbit, so the stack stops short of the ring. */

.home-main {
  display: flex;
  flex-direction: column;
  padding: 24px var(--yh-g) 8px;
}
.home-menu {
  position: relative;
  width: 100%;
  max-width: 640px;
  margin: 0 auto;
  container-type: inline-size;
}
.home-cta { position: relative; display: block; }

:is(.home-cta > a, .home-about__summary) {
  --hc-bg: var(--yh-paper-hi);
  --hc-fg: var(--yh-ink);
  --hc-fill: var(--yh-accent);
  --hc-fg-lit: var(--yh-on-accent);
  position: relative;
  isolation: isolate;
  display: flex;
  flex-direction: column;
  gap: clamp(10px, 3cqw, 22px);
  padding: clamp(12px, 3cqw, 18px) clamp(14px, 4.4cqw, 28px) clamp(14px, 3.4cqw, 24px);
  overflow: hidden;
  background: var(--hc-bg);
  color: var(--hc-fg);
  text-decoration: none;
  list-style: none;
  cursor: pointer;
  outline: none;
  -webkit-tap-highlight-color: transparent;
  transition: color 0.12s ease;
}
:is(.home-cta > a, .home-about__summary)::-webkit-details-marker { display: none; }
/* the call-to-action cell: lit, the deepest blue rises under the white type (not a white flip: the oil-film cloud of home-glow.js and the dots read on it) */
.home-cta > a {
  --hc-bg: var(--yh-accent);
  --hc-fg: var(--yh-on-accent);
  --hc-fill: var(--yh-accent-deep);
  --hc-fg-lit: var(--yh-on-accent);
  min-height: clamp(220px, 54cqw, 440px);
}
.home-about__summary { min-height: clamp(104px, 24cqw, 190px); }

/* the fill that rises from the bottom (pf-btn: 0.1s to retract upward, 0.2s to rise) and the hairline on top, drawn by the entrance */
:is(.home-cta > a, .home-about__summary)::before {
  content: "";
  position: absolute;
  inset: 0 0 -1px;
  z-index: -1;
  background: var(--hc-fill);
  transform: scaleY(0);
  transform-origin: 50% 0;
  transition: transform 0.1s ease-in-out;
}
:is(.home-cta > a, .home-about__summary)::after {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  left: 0;
  z-index: 3;
  height: 2px;
  background: var(--yh-ink);
  transform: scaleX(var(--sep, 1));
  transform-origin: 0 50%;
}
:is(.home-cta > a, .home-about__summary):is(:hover, :focus-visible, :active),
.home-about[open] > .home-about__summary { color: var(--hc-fg-lit); }
:is(.home-cta > a, .home-about__summary):is(:hover, :focus-visible, :active)::before,
.home-about[open] > .home-about__summary::before {
  transform: scaleY(1);
  transform-origin: 50% 100%;
  transition: transform 0.2s cubic-bezier(0.22, 0.61, 0.36, 1);
}
/* keyboard: the same lit cell and a ring inside it (the plate has no room outside) */
:is(.home-cta > a, .home-about__summary):focus-visible { outline: 3px solid var(--yh-ink); outline-offset: -3px; }
.home-cta > a:focus-visible { outline-color: var(--yh-paper-hi); }
/* a press: the fill snaps over the whole cell and the label sits back */
:is(.home-cta > a, .home-about__summary).is-press::before { transform: scaleY(1); transform-origin: 50% 100%; transition: none; }

/* the mono row on top: number, a hairline that fills the gap, the status, the arrow */
.hc-meta {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 0.9em;
  font-family: var(--hud-mono);
  font-size: clamp(10px, 2.3cqw, 12.5px);
  letter-spacing: 0.04em;
  line-height: 1.35;
}
.hc-index { flex: none; font-weight: 700; }
.hc-rule {
  flex: 1 1 14px;
  height: 1px;
  min-width: 14px;
  background: currentColor;
  opacity: 0.5;
  transform-origin: 0 50%;
}
.hc-status {
  display: flex;
  flex: 0 1 auto;
  align-items: center;
  gap: 0.7em;
  min-width: 0;
}
.hc-led {
  flex: none;
  width: 0.62em;
  height: 0.62em;
  background: currentColor;
  animation: yh-blink 3.6s steps(1, end) infinite;
}
.hc-arrow {
  flex: none;
  min-width: 1ch;
  font-size: 1.5em;
  line-height: 1;
  text-align: right;
}
/* the arrow is drawn here, not in the markup (it is decoration; the label is the name) */
.hc-arrow::before { content: "\2197\FE0E"; display: inline-block; }
.home-about__summary .hc-arrow::before { content: "+"; }
.home-about[open] .home-about__summary .hc-arrow::before { content: "\2212"; }

/* the label: giant, tight, uppercase, at the foot of the cell */
.hc-label {
  position: relative;
  z-index: 1;
  display: block;
  margin-top: auto;
  font-family: var(--hud-display);
  /* the longest line (--len characters) fills the cell; on the desktop the type is also capped by the height of the scene (--hc-cap), so a wide, low window never grows the stack into the status strip */
  font-size: clamp(2rem, min(calc(128cqw / max(var(--len, 9), 8)), var(--hc-cap, 9rem)), 9rem);
  font-weight: 700;
  letter-spacing: -0.025em;
  line-height: 0.92;
  text-transform: uppercase;
}
/* the second cell is one long word, SUNCYCLE³ (--len 9 in the markup): sized like the first cell's longest line (128cqw / len), but a step smaller in the height cap */
.home-about__summary .hc-label { font-size: clamp(1.8rem, min(calc(128cqw / max(var(--len, 9), 8)), calc(var(--hc-cap, 9rem) * 0.75)), 5.4rem); }
.hc-label__line { display: block; white-space: nowrap; }
.hc-sub {
  position: relative;
  z-index: 1;
  font-family: var(--hud-mono);
  font-size: clamp(10px, 2.2cqw, 12.5px);
  letter-spacing: 0.04em;
  line-height: 1.4;
  opacity: 0.92;
}

/* the pointer cloud (home-cta.js writes --mx / --my / --ang while the pointer is in the cell): a thin film of colour that turns, over a
   halftone of dots, both opened by a soft circle round the pointer. Blue, cyan and sage only (the palette test keeps every hue in the blue-green family). Between the fill and the text. */
.hc-glow {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.25s ease;
}
.hc-glow::before,
.hc-glow::after {
  content: "";
  position: absolute;
  inset: 0;
}
.hc-glow::before {
  background: conic-gradient(from var(--ang, 0deg) at var(--mx, 50%) var(--my, 50%), var(--yh-accent), #5fd0ff, #bff0d4, #d4f1c9, #7fb0ff, var(--yh-accent));
  opacity: 0.5;
  -webkit-mask-image: radial-gradient(circle 16cqw at var(--mx, 50%) var(--my, 50%), #000 0, rgba(0, 0, 0, 0) 100%);
  mask-image: radial-gradient(circle 16cqw at var(--mx, 50%) var(--my, 50%), #000 0, rgba(0, 0, 0, 0) 100%);
}
.hc-glow::after {
  background: radial-gradient(circle, var(--hc-fg-lit) 0.9px, transparent 1.5px) 0 0 / 6px 6px;
  opacity: 0.55;
  -webkit-mask-image: radial-gradient(circle 24cqw at var(--mx, 50%) var(--my, 50%), #000 0, rgba(0, 0, 0, 0) 100%);
  mask-image: radial-gradient(circle 24cqw at var(--mx, 50%) var(--my, 50%), #000 0, rgba(0, 0, 0, 0) 100%);
}
:is(.home-cta > a, .home-about__summary):hover .hc-glow { opacity: 1; }
/* the oil-film cloud of the call-to-action cell (public/js/home-glow.js makes the canvas the first time the pointer lands): inside the cell,
   over the fill and the CSS cloud, under the text; the CSS film (the conic gradient) steps aside when the canvas is there, the dots stay */
.hc-film {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  image-rendering: pixelated;
  /* the mono lines (the status row at the top, the foot line) stay clear of the colours: their small type needs the plain blue under it */
  -webkit-mask-image: linear-gradient(180deg, transparent 0, transparent 38px, #000 74px, #000 calc(100% - 74px), transparent calc(100% - 38px));
  mask-image: linear-gradient(180deg, transparent 0, transparent 38px, #000 74px, #000 calc(100% - 74px), transparent calc(100% - 38px));
}
.home-cta > a.has-film .hc-glow::before { display: none; }

/* the lit cell, with motion: the label leans in and grows, the arrow moves up and right, the number's hairline shortens */
@media (prefers-reduced-motion: no-preference) {
  .hc-label,
  .hc-arrow,
  .hc-sub { transition: transform 0.35s cubic-bezier(0.22, 0.61, 0.36, 1); }
  .hc-label { transform-origin: 0 100%; }
  :is(.home-cta > a, .home-about__summary):is(:hover, :focus-visible) .hc-label { transform: translateX(0.04em) scale(1.035); }
  :is(.home-cta > a, .home-about__summary):is(:hover, :focus-visible) .hc-arrow { transform: translate(3px, -3px); }
  .home-about__summary:is(:hover, :focus-visible) .hc-arrow { transform: rotate(90deg); }
  :is(.home-cta > a, .home-about__summary):is(:hover, :focus-visible) .hc-sub { transform: translateX(8px); }
  :is(.home-cta > a, .home-about__summary).is-press .hc-label { transform: scale(0.985); transition-duration: 0.08s; }
}

/* "о нас": a <details>; its panel opens under the cell (a plate with a hairline frame) */
.home-about { position: relative; }
.home-about__body {
  position: relative;
  z-index: 1;
  padding: 14px clamp(14px, 4.4cqw, 28px) 16px;
  border: 1px solid var(--yh-ink);
  border-top: 0;
  background: var(--yh-paper-hi);
  color: var(--yh-ink);
  font-family: var(--hud-mono);
  font-size: 0.76rem;
  line-height: 1.6;
  pointer-events: auto;
}
.home-about[open] .home-about__body { animation: yh-scan 280ms steps(6, end) backwards; }
.home-about__body p + p { margin-top: 0.6em; }
.home-about__tg { font-weight: 700; }
.home-discord-link {
  color: var(--yh-ink);
  font-weight: 700;
  text-decoration: underline;
  text-decoration-color: var(--yh-accent);
  text-decoration-thickness: 2px;
  text-underline-offset: 0.18em;
}
.home-discord-link:hover { background: var(--yh-ink); color: var(--th-on-accent); }

/* "узнать больше": the third piece of the stack, a compact link under the two cells (to /events#suncycle). One row on an ink plate: the
   label at the left, the arrow at the right (the pxpush bottom link); it uses the cells' motion, a fill that rises from the bottom (blue),
   the roll of the letters (home-cta.js lists it with the cells), the arrow that moves. It is a sibling of .home-about, so the opened
   "о нас" panel (out of the flow, z-index 1) lies over it and nothing moves. */
.home-more {
  --hc-bg: var(--yh-ink);
  --hc-fg: var(--yh-paper-hi);
  --hc-fill: var(--yh-accent);
  --hc-fg-lit: var(--yh-on-accent);
  position: relative;
  isolation: isolate;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1em;
  min-height: clamp(48px, 9cqw, 68px);
  padding: 0 clamp(14px, 4.4cqw, 28px);
  overflow: hidden;
  background: var(--hc-bg);
  color: var(--hc-fg);
  font-family: var(--hud-display);
  font-size: clamp(0.95rem, 3.1cqw, 1.6rem);
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1;
  text-decoration: none;
  text-transform: uppercase;
  cursor: pointer;
  outline: none;
  -webkit-tap-highlight-color: transparent;
  transition: color 0.12s ease;
}
.home-more::before {
  content: "";
  position: absolute;
  inset: 0 0 -1px;
  z-index: -1;
  background: var(--hc-fill);
  transform: scaleY(0);
  transform-origin: 50% 0;
  transition: transform 0.1s ease-in-out;
}
.home-more:is(:hover, :focus-visible, :active) { color: var(--hc-fg-lit); }
.home-more:is(:hover, :focus-visible, :active)::before {
  transform: scaleY(1);
  transform-origin: 50% 100%;
  transition: transform 0.2s cubic-bezier(0.22, 0.61, 0.36, 1);
}
.home-more:focus-visible { outline: 3px solid var(--yh-paper-hi); outline-offset: -3px; }
.home-more:focus-visible:is(:hover, :active) { outline-color: var(--yh-ink); }
.home-more.is-press::before { transform: scaleY(1); transform-origin: 50% 100%; transition: none; }
/* the label is the cells' .hc-label (the roll finds it) at the size of a button, not of a poster */
.home-more .hc-label { margin: 0; font-size: inherit; font-weight: inherit; letter-spacing: inherit; line-height: inherit; }
.home-more .hc-arrow { font-size: 1.25em; }
.home-more .hc-glow::after { opacity: 0.4; }
.home-more:hover .hc-glow { opacity: 1; }
@media (prefers-reduced-motion: no-preference) {
  .home-more .hc-arrow { transition: transform 0.35s cubic-bezier(0.22, 0.61, 0.36, 1); }
  .home-more:is(:hover, :focus-visible) .hc-arrow { transform: translate(3px, -3px); }
  .home-more:is(:hover, :focus-visible) .hc-label { transform: translateX(0.3em); }
  .home-more.is-press .hc-label { transform: scale(0.985); transition-duration: 0.08s; }
}


/* ---------- the frame: a close-up of the scene video with a player readout ---------- */

.home-frame {
  flex-direction: column;
  margin: 0;
  border: 1px solid var(--yh-ink);
  background: var(--yh-paper-hi);
  color: var(--yh-ink);
  pointer-events: none;
}
.home-frame__bar {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 28px;
  padding: 0 12px;
  background: var(--yh-ink);
  color: var(--th-chrome-ink);
  font-family: var(--hud-mono);
  font-size: 0.7rem;
  line-height: 1.2;
}
.home-frame__name {
  display: flex;
  align-items: center;
  gap: 9px;
}
.home-frame__name::before {
  content: "";
  width: 6px;
  height: 6px;
  background: var(--th-tint-deep);
}
.home-frame__path { margin-left: auto; }
.home-frame__controls {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 12px 6px;
  font-family: var(--hud-mono);
  font-size: 0.68rem;
  line-height: 1.2;
}
.home-frame__btns {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.home-frame__btn {
  display: block;
  width: 12px;
  height: 12px;
  background: var(--yh-ink);
}
.home-frame__btn--prev { clip-path: polygon(0 0, 2px 0, 2px 45%, 100% 0, 100% 100%, 2px 55%, 2px 100%, 0 100%); }
.home-frame__btn--next { clip-path: polygon(100% 0, calc(100% - 2px) 0, calc(100% - 2px) 45%, 0 0, 0 100%, calc(100% - 2px) 55%, calc(100% - 2px) 100%, 100% 100%); }
.home-frame__btn--pause { background: linear-gradient(90deg, var(--yh-ink) 0 34%, transparent 34% 66%, var(--yh-ink) 66%); }
.home-frame[data-state="poster"] .home-frame__btn--pause { background: var(--yh-ink); clip-path: polygon(15% 0, 100% 50%, 15% 100%); }
.home-frame__media {
  position: relative;
  display: block;
  aspect-ratio: 4 / 3;
  margin: 0 12px;
  overflow: hidden;
  border: 4px solid var(--yh-ink);
  background: var(--yh-paper-lo);
}
.home-frame__still,
.home-frame__canvas {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  image-rendering: pixelated;
}
.home-frame__canvas { opacity: 0; transition: opacity 0.4s ease; }
.home-frame__media.has-canvas .home-frame__canvas { opacity: 1; }
.home-frame__progress {
  position: relative;
  height: 1px;
  margin: 10px 12px 0;
  background: rgba(30, 48, 73, 0.45);
}
.home-frame__progress i {
  position: absolute;
  left: 0;
  top: -2px;
  width: 100%;
  height: 5px;
  background: var(--yh-ink);
  transform: scaleX(0);
  transform-origin: left center;
}
.home-frame__caption {
  margin: 8px 12px 12px;
  font-family: var(--hud-mono);
  font-size: 0.7rem;
  font-weight: 700;
  line-height: 1.4;
}

/* ---------- phones: no model (and viewer3d.js doesn't mount it), no text ring, no frame; the landscape stays as a strip with its watermark ---------- */
@media (max-width: 639px) {
  .banner-model { display: none; }
  .home-frame { display: none; }
  .site-header--banner > .banner { min-height: 150px; }
}

/* ---------- desktop: exactly one screen, no scrolling ---------- */

@media (min-width: 900px) and (min-height: 600px) {
  /* nothing scrolls (this sheet is only linked on the home page); the page-in
     slide of <body> would otherwise flash a scrollbar for a moment */
  html { overflow: hidden; }
  body {
    --col-l: clamp(400px, 41vw, 600px);   /* the left window: about 5/12 of the frame */
    --col-r: clamp(200px, 23vw, 420px);   /* the video frame (and the account tab above it) */
    --dock-h: 56px;                       /* the strip at the bottom that the scene's layout leaves free: the row of social icons floats there, on the video */
    --stage-gap: 16px;                    /* the space between the band and the tops of the stage pieces */
    /* the model's place, shared by the model and the menu: its ring is a circle as high as 0.9 of the scene (the window above the icon
       dock), its centre at 68% of the width but never over the left window */
    --scene-h: calc(100svh - var(--dock-h));
    --scene-x1: calc(var(--yh-g) + var(--col-l));
    --scene-d: min(calc(var(--scene-h) * 0.9), calc((100vw - var(--scene-x1) - 8px) / 1.06));
    --scene-cx: max(68vw, calc(var(--scene-x1) + 8px + var(--scene-d) * 0.5));
    display: grid;
    height: 100svh;
    grid-template-columns: 0 var(--col-l) minmax(0, 1fr) var(--col-r) 0;
    column-gap: var(--yh-g);
    /* tabs / status strip / band / the stage's top row / the rest of the stage (down to the bottom edge of the window) */
    grid-template-rows: auto auto auto auto minmax(0, 1fr);
  }
  body > .site-header.site-header--banner {
    grid-column: 1 / -1;
    grid-row: 1 / 6;
    grid-template-columns: subgrid;
    grid-template-rows: subgrid;
    padding: 0;
    min-height: 0;
    overflow: clip;
  }
  /* the footer is only the row of social icons: at the lower right of the stage, its right edge on the frame's (and the gutter's) line,
     above the header's layers (the model's ring ends left of them; the line-art may pass under the icons, their plates cover it) */
  body > .site-footer.site-footer--home {
    position: relative;
    z-index: 12;
    grid-column: 3 / 5;
    grid-row: 5;
    align-self: end;
    justify-self: end;
    min-height: 0;
    margin: 0 0 16px;
    padding: 0;
    background: none;
  }
  /* the tabs, the status strip and the account tab lie over the model's orbits */
  .site-header--banner > .header-nav,
  .site-header--banner > .hud-top {
    position: relative;
    z-index: 4;
  }
  .site-header--banner > .header-nav {
    grid-row: 1;
    grid-column: 2 / 5;
    margin-top: var(--hdr-pad-t);
  }
  /* the status strip and the account chip: one row over the left window, the middle and the frame column, the chip at the right end */
  .site-header--banner > .hud-top {
    grid-row: 2;
    grid-column: 2 / 5;
  }
  .site-header--banner::after {
    grid-row: 3;
    grid-column: 2 / 5;
  }
  .site-header--banner > .banner {
    grid-row: 4 / 6;
    grid-column: 1 / -1;
    margin: 0;
  }
  /* the logo art (only when its file exists): bare, at the top of the left column, on the line of the frame */
  .site-header--banner > .banner-inner.banner-inner--logo {
    grid-row: 4;
    grid-column: 2;
    align-self: start;
    justify-self: start;
    margin: var(--stage-gap) 0 0;
  }
  .site-header--banner > .banner-leaves-img { grid-row: 4 / 6; }

  /* the frame: the top of the frame column, behind the model */
  .site-header--banner > .home-frame {
    position: relative;
    z-index: 1;
    display: flex;
    grid-row: 4 / 6;
    grid-column: 4;
    align-self: start;
    margin: var(--stage-gap) 0 0;
  }

  /* the model: its ring is a circle as high as 0.9 of the scene (the window above the icon dock), its centre at
     68% of the width but never over the buttons (its orbits may slip under them), vertically centred in the scene, so it rises
     above the status strip's lower edge into the tab band. The model's own box is 4:3 and --model-k times the ring (the
     bitmap), centred on the ring: the model itself is as big as the orbits and a little more. */
  .site-header--banner > .banner-model {
    --model-d: var(--scene-d);
    --model-cx: var(--scene-cx);
    position: absolute;
    z-index: 3;   /* above the flare (2): the model is in front of the star, its glow and spikes come out from behind it */
    left: calc(var(--model-cx) - var(--model-d) * var(--model-k) * 2 / 3);
    top: calc((var(--scene-h) - var(--model-d) * var(--model-k)) / 2);
    width: calc(var(--model-d) * var(--model-k) * 4 / 3);
    height: calc(var(--model-d) * var(--model-k));
    margin: 0;
    grid-row: auto;
    grid-column: auto;   /* auto lines: the box is placed against the whole header, not a grid area */
    justify-self: auto;
    max-width: none;
  }
  /* the flare: a square box (--flare-d) whose centre is the star, on the left edge of the video frame (the frame's column starts
     there), a little above the frame's middle: light seems to shine out of the window from BEHIND the model and across the scene.
     It lies over the video and the frame (z 2) and under the model (z 3), the tabs and the status strip (z 4) and the buttons (z 11):
     the model's white body hides the star itself, and the halo and the long spikes (the box is about 1.7x what it was) reach out round
     its silhouette, over the frame's left part and the sky above. A glow of pale blue, not a veil; the plates of the tabs and
     the buttons stay solid over it. */
  .site-header.site-header--banner > .model-flare {
    --flare-d: calc(var(--col-r) * 3);
    --fx: 50%;
    --fy: 50%;
    --core: 22%;
    --sun-x: calc(var(--flare-d) / 2 + var(--col-r) * 0.82);   /* the ASCII sun: under the frame's lower right corner (the star is the box's centre) */
    --sun-y: calc(var(--flare-d) / 2 + var(--col-r) * 0.94);
    position: relative;
    z-index: 2;
    grid-row: 4 / 6;
    grid-column: 4;
    align-self: start;
    justify-self: start;
    width: var(--flare-d);
    height: var(--flare-d);
    margin: calc(var(--stage-gap) + 12px - var(--flare-d) / 2) 0 0 calc(var(--flare-d) / -2);
  }
  /* the glow fades out above the star (over 70px, ending 60px above it: about the dashed band), so it never reaches the tabs' band */
  .model-flare .mf-glow {
    -webkit-mask-image: linear-gradient(180deg, transparent calc(50% - 130px), #000 calc(50% - 60px));
    mask-image: linear-gradient(180deg, transparent calc(50% - 130px), #000 calc(50% - 60px));
  }

  /* the left side: the stack of two cells, centred on the model's height (the middle of the scene). The <main> is a click-through
     layer over the whole scene; the cells take the pointer themselves. The stack is as wide as the room left of the model's outer
     dashed orbit (--scene-cx and --scene-d are the model's centre and ring, set on <body> above): it stops 26px short of the ring (the text belt sticks out a little past the dashed orbit),
     so the buttons and the orbit never cross, whatever the window. The "о нас" panel opens under its cell and is out of the flow,
     so nothing moves when it opens. */
  .home-main {
    position: absolute;
    z-index: 11;
    left: 0;
    top: 0;
    width: 100%;
    height: var(--scene-h);
    padding: 0 var(--yh-g);
    justify-content: center;
    pointer-events: none;
  }
  .home-menu {
    --cta-w: min(calc(var(--scene-cx) - var(--scene-d) * 0.555 - 26px - var(--yh-g)), 760px);
    --hc-cap: calc(var(--scene-h) * 0.115);   /* the biggest label type: two lines must leave the stack room under the status strip */
    width: var(--cta-w);
    max-width: none;
    margin: 0;
    pointer-events: none;
  }
  :is(.home-cta > a, .home-about__summary) { pointer-events: auto; }
  .home-cta > a { min-height: calc(var(--scene-h) * 0.37); }
  .home-about__summary { min-height: calc(var(--scene-h) * 0.14); }
  .home-more { pointer-events: auto; min-height: calc(var(--scene-h) * 0.075); }
  .home-about__body {
    position: absolute;
    top: 100%;
    left: 0;
    width: 100%;
    max-height: calc(100svh - (var(--scene-h) + var(--scene-h) * 0.51) / 2 - 24px);
    overflow: auto;
    scrollbar-width: thin;
  }
}
/* big screens: the type of the scene grows a little with the window and the frame */
@media (min-width: 1700px) and (min-height: 940px) {
  html { font-size: 106.25%; }
}
/* medium-height desktops: a wide, low picture in the frame */
@media (min-width: 900px) and (min-height: 600px) and (max-height: 789px) {
  .home-frame__media { aspect-ratio: 16 / 9; }
}
/* the narrowest desktops: the video frame goes first (the flare goes with it) */
@media (min-width: 900px) and (max-width: 999px) and (min-height: 600px) {
  .site-header--banner > .home-frame { display: none; }
  .site-header.site-header--banner > .model-flare { display: none; }
}

/* ---------- the flare: a dreamy bloom at the left edge of the video frame ----------
   A sibling of the model in the header (views/partials/flare.ejs): above the video, under the frame, the model, its orbits, the
   tabs and the buttons. On the desktop it is a square box whose centre (the star) is the left edge of the frame, a little above the
   frame's middle: a soft halo, two sets of thin spikes turning against each other and breathing, a faint horizontal streak (it runs
   left across the scene), a bright core, a few pixel motes drifting up and an ASCII sun at the lower right. Stacked (tablets) the
   frame is gone and the flare sits under the model as before. Everything animates transform / opacity only (compositor), and
   stands still under reduced motion.
   Plain alpha compositing, deliberately NOT mix-blend-mode: screen. The layers are white and pale blue, which "screen" and
   "normal" paint almost the same; but a blend mode makes the browser copy and re-blend everything behind the flare (the video
   canvas, the WebGL model) every frame, and measured in software compositing that cost the whole page 6x its frame rate. */

.site-header--banner > .model-flare {
  --model-d: min(54svh, 360px);   /* the stacked default, as .banner-model */
  display: none;
  position: relative;
  grid-row: 6;
  grid-column: 1;
  z-index: 1;
  align-self: center;
  justify-self: center;
  width: var(--model-d);
  height: var(--model-d);
  margin: 12px 0 32px;
  pointer-events: none;
}
@media (min-width: 640px) {
  .site-header--banner > .model-flare { display: block; }
}
.model-flare > *,
.mf-glow > * {
  position: absolute;
  display: block;
  pointer-events: none;
}
.mf-glow { inset: 0; }
.model-flare {
  --fx: 29%;   /* stacked: the star sits low and to the left of the model (centre of the box = the model's centre) */
  --fy: 74%;
  --core: 34%;
}
.mf-halo {
  /* the glow round the star: a white-blue core fading through a saturated pale blue (so it reads on the pale sky and on the dark hills), out well before the edge of the box */
  inset: 20%;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 50%, rgba(255, 255, 255, 0.95) 0, rgba(230, 240, 255, 0.8) 9%, rgba(176, 206, 255, 0.6) 22%, rgba(128, 170, 255, 0.42) 40%, rgba(116, 160, 255, 0.22) 60%, rgba(116, 160, 255, 0.08) 80%, rgba(120, 164, 255, 0) 100%);
  animation: mf-breathe 7s ease-in-out infinite alternate;
}
.mf-spikes {
  left: var(--fx);
  top: var(--fy);
  width: 124%;
  height: 124%;
  margin: -62% 0 0 -62%;
  overflow: visible;
}
.mf-spikes--a { animation: mf-turn 160s linear infinite, mf-glow 6s ease-in-out infinite alternate; }
.mf-spikes--b { animation: mf-turn 220s linear infinite reverse, mf-glow 8.5s ease-in-out -3s infinite alternate; }
.mf-streak {
  left: -18%;
  right: -18%;
  top: var(--fy);
  height: 2px;
  margin-top: -1px;
  background: linear-gradient(90deg, rgba(150, 185, 255, 0), rgba(150, 185, 255, 0.8) 30%, #fff 50%, rgba(150, 185, 255, 0.8) 70%, rgba(150, 185, 255, 0));

  animation: mf-streak 9s ease-in-out infinite alternate;
}
.mf-core {
  left: var(--fx);
  top: var(--fy);
  width: var(--core);
  height: var(--core);
  margin: calc(var(--core) / -2) 0 0 calc(var(--core) / -2);
  border-radius: 50%;
  background: radial-gradient(circle, #fff 0, #fff 13%, rgba(255, 255, 255, 0.86) 28%, rgba(170, 200, 255, 0.55) 52%, rgba(170, 200, 255, 0) 72%);
  animation: mf-pulse 4.2s ease-in-out infinite;
}
/* pixel motes: tiny squares rising slowly and twinkling out */
.mf-mote {
  left: var(--x);
  top: var(--y);
  width: 4px;
  height: 4px;
  background: #fff;
  opacity: 0;
  animation: mf-drift var(--t) linear var(--dl) infinite;
}
@keyframes mf-turn { to { transform: rotate(360deg); } }
@keyframes mf-glow {
  from { opacity: 0.55; }
  to { opacity: 0.95; }
}
@keyframes mf-breathe {
  from { opacity: 0.7; transform: scale(0.96); }
  to { opacity: 1; transform: scale(1.04); }
}
@keyframes mf-pulse {
  0%, 100% { opacity: 0.85; transform: scale(0.94); }
  50% { opacity: 1; transform: scale(1.06); }
}
@keyframes mf-streak {
  from { opacity: 0.45; transform: scaleX(0.72); }
  to { opacity: 0.95; transform: scaleX(1); }
}
@keyframes mf-drift {
  0% { opacity: 0; transform: translate(0, 10px); }
  15% { opacity: 0.95; }
  70% { opacity: 0.8; }
  100% { opacity: 0; transform: translate(16px, -64px); }
}
/* the ASCII sun: 25 x 11 characters in the bundled mono, white on the hills. Its rays twinkle in three groups, one hard step at a time */
.mf-sun {
  left: var(--sun-x, 80%);
  top: var(--sun-y, 72%);
  margin: 0;
  color: #fff;
  font-family: var(--hud-mono);
  font-size: 11px;
  line-height: 11px;
  white-space: pre;
  user-select: none;
}
.sun-a { animation: mf-twinkle 3.6s steps(1, end) infinite; }
.sun-b { animation: mf-twinkle 3.6s steps(1, end) -1.2s infinite; }
.sun-d { animation: mf-twinkle 2.3s steps(1, end) -0.6s infinite; }
@keyframes mf-twinkle {
  0%, 52% { opacity: 1; }
  53%, 62% { opacity: 0.2; }
  63%, 100% { opacity: 1; }
}

/* ---------- the ASCII picture in the frame: the scene video as characters (public/js/home-ascii.js) ---------- */
.home-frame__ascii {
  position: absolute;
  inset: 0;
  z-index: 1;
  margin: 0;
  padding: 2px 3px;
  overflow: hidden;
  background: var(--yh-paper-lo);
  color: var(--th-accent);
  font-family: var(--hud-mono);
  font-size: 9px;
  line-height: 9px;
  letter-spacing: 0;
  white-space: pre;
  opacity: 0;
  user-select: none;
  pointer-events: none;
}
.home-frame__media.has-ascii .home-frame__ascii { opacity: 1; }

/* ---------- the footer of the home page: only the five social icons, on the video ----------
   views/partials/footer.ejs with variant 'home' has no band and no status line. On the desktop (the rules above) the icon row floats at the
   lower right of the scene, on the dark green hills of the video; stacked (phones, tablets, short windows) it is a plain dark strip at
   the end of the page. Pale line icons, a 1px pale hairline, a translucent dark plate behind every icon (so the icon reads on any frame
   of the video), the motion kit's stepped sweep on hover (the plate fills pale, the icon turns dark), a pale focus ring on a dark halo. */
.site-footer.site-footer--home {
  /* the footer's colours (site.css "footer"): pale on the dark ink, which is the strip of the stacked layout and, as a plate, what sits behind
     every icon; on the video the plates are the same ink at 72% (below). tests/header-colours.test.js computes the pairs, the video worst case included. */
  --ftr-bg: var(--yh-ink);
  --ftr-paint: var(--yh-ink);
  --ftr-fg: var(--yh-paper-hi);
  --ftr-soft: var(--yh-paper-hi);
  --ftr-edge: var(--yh-blue);   /* the pale hairline of an icon square (it was the paper at 72% over the plate) */
  --ftr-plate: var(--yh-ink);
  --ftr-fill: var(--yh-paper-hi);
  --ftr-fill-fg: var(--yh-ink);
  --ftr-mark: var(--yh-accent-lit);
  margin: 0;
  padding: 14px var(--yh-g);
  background: var(--ftr-paint);
  color: var(--ftr-fg);
}
.site-footer--home::before { display: none; }
.site-footer--home .social-links {
  --ico: 30px;
  flex-wrap: nowrap;
  gap: 6px;
  margin-left: auto;
}
.site-footer--home .social-links a {
  border-color: var(--ftr-edge);
  /* the plate: one translucent dark layer (the motion kit's opaque cover would hide the video); the hover fill sweeps over it in the kit's 8 steps */
  background-color: rgba(22, 36, 58, 0.72);
  background-image: linear-gradient(90deg, var(--yh-sw) var(--yh-p), transparent var(--yh-p));
  background-size: 100% 100%;
  background-repeat: no-repeat;
  color: var(--ftr-fg);
}
.site-footer--home .social-links a:focus-visible {
  outline: 2px solid var(--ftr-fg);
  outline-offset: 2px;
  box-shadow: 0 0 0 5px rgba(22, 36, 58, 0.92);
}

/* ---------- the little face in the status strip: it blinks now and then ---------- */
.hud-bar__pet {
  display: inline-grid;
  color: var(--th-ink);
  font-family: var(--hud-mono);
  font-size: var(--hdr-bar-fs);
  white-space: pre;
  user-select: none;
}
.hud-bar__pet > .pet { grid-area: 1 / 1; }
.pet--b, .pet--c { opacity: 0; }
.pet--a { animation: pet-a 9s steps(1, end) infinite; }
.pet--b { animation: pet-b 9s steps(1, end) infinite; }
.pet--c { animation: pet-c 9s steps(1, end) infinite; }
@keyframes pet-a { 0%, 85.9% { opacity: 1; } 86%, 90.9% { opacity: 0; } 91%, 94.9% { opacity: 1; } 95%, 97.9% { opacity: 0; } 98%, 100% { opacity: 1; } }
@keyframes pet-b { 0%, 85.9% { opacity: 0; } 86%, 90.9% { opacity: 1; } 91%, 100% { opacity: 0; } }
@keyframes pet-c { 0%, 94.9% { opacity: 0; } 95%, 97.9% { opacity: 1; } 98%, 100% { opacity: 0; } }

/* (the pixel border that used to dissolve the scene's top edge into the page, a row of 4px Bayer tiles in .banner::before, is gone: the
   dotted texture of portfolio-dots.js alone runs from the very top of the page down over the scene.) */

/* the frame's title strip: ink pixels hang under it, like the window titles of the other pages */
.home-frame__bar { position: relative; }
.home-frame__bar::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  height: 6px;
  background: var(--yh-dz-edge) left top / 8px 6px repeat-x;
  pointer-events: none;
}

/* ---------- a cell lit: the scene answers ----------
   While the pointer (or the keyboard) is on a cell, public/js/home-cta.js puts .is-cta-hot on <body> (and tells the text ring to
   speed up): the video takes a faint blue veil and the model leans a little towards the cell, both eased, both compositor-only
   (opacity and transform). */
.home-fog::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--yh-accent);
  opacity: 0;
  transition: opacity 0.4s ease;
}
body.is-cta-hot .home-fog::after { opacity: 0.16; }
/* the call-to-action cell lit by the pointer (home-glow.js puts .is-cta-glow on <body>): the scene answers louder, almost twice the blue veil */
body.is-cta-glow .home-fog::after { opacity: 0.28; }
@media (min-width: 900px) and (min-height: 600px) and (prefers-reduced-motion: no-preference) {
  .site-header--banner > .banner-model { transition: transform 0.7s cubic-bezier(0.22, 0.61, 0.36, 1); }
  body.is-cta-hot .site-header--banner > .banner-model { transform: translateX(calc(var(--model-d) * -0.025)); }
}

/* ---------- first paint of the scene (about 1.2s in all): the video scans open, the cells wipe in one after the other (their hairlines
   draw, then the label settles letter by letter: home-cta.js), the frame scans open, the text ring flickers in ---------- */
.site-header--banner > .banner { animation: yh-scan 640ms steps(12, end) backwards; }
.home-frame { animation: yh-scan 480ms steps(10, end) 560ms backwards; }
.home-cta > a { animation: yh-wipe-x 420ms steps(10, end) 300ms backwards; }
.home-about { animation: yh-wipe-x var(--yh-t-wipe) steps(8, end) 520ms backwards; }
.home-more { animation: yh-wipe-x var(--yh-t-wipe) steps(8, end) 640ms backwards; }
.home-cta .hc-rule { animation: hc-draw 480ms steps(8, end) 620ms backwards; }
.home-about .hc-rule { animation: hc-draw 480ms steps(8, end) 780ms backwards; }
.model-ring { animation: yh-flicker 480ms steps(1, end) 700ms backwards; }
@keyframes hc-draw {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}

/* ---------- lean mode (public/js/home-lean.js): the scene on a machine that cannot keep up ----------
   <html> gets .is-lean after two slow looks at the frame rate. The orbits, the flare, the blinking LEDs, the face and the pointer cloud
   stand still, and the model loses its outline filter (four drop-shadows and a hue turn over a big canvas every frame). The picture,
   the model, the text ring and the cells stay (the three scripts draw about half as often). */
.is-lean .model-glow > i,
.is-lean .model-flare *,
.is-lean .sun-a,
.is-lean .sun-b,
.is-lean .sun-d,
.is-lean .pet,
.is-lean .hc-led { animation: none; }
.is-lean .hc-glow,
.is-lean .hc-film { display: none; }
.is-lean .retro-pixel-canvas canvas { filter: none; }
.is-lean .banner > .pf-dots { display: none; }

/* ---------- reduced motion: the scene stands still (the reduced-motion block of site.css also zeroes every animation and
   transition). A lit cell keeps its fill and its colours, instantly: no roll, no lean, no zoom, no glow, no model move. ---------- */
@media (prefers-reduced-motion: reduce) {
  .home-video__canvas,
  .home-frame__canvas { transition: none; }
  .model-glow > i { animation: none; }
  /* the flare is one still picture: no breathing, no turning, no drift, no twinkle; the little face keeps its eyes open */
  .model-flare *,
  .sun-a,
  .sun-b,
  .sun-d,
  .pet { animation: none; }
  .mf-mote { opacity: 0.8; }
  .hc-glow,
  .hc-film { display: none; }
  .hc-led { animation: none; }
  :is(.home-cta > a, .home-about__summary)::before,
  .home-more::before { transition: none; }
  /* the model is simply there: no pop-in (viewer3d.js does not even set the wait class under reduced motion) */
  .retro-pixel-canvas.is-model-wait,
  .retro-pixel-canvas.is-model-pop { scale: none; animation-name: none; }
}
