/* ==========================================================================
   TITLE SCREEN
   --------------------------------------------------------------------------
   A boot screen that fills the viewport, runs a loading bar over a set
   interval, then fades out and hands over to the desktop. Pure CSS: the
   timing is an animation, not a timer.

   DISMISS / REPLAY
     One checkbox, #intro-off, does both. A full-screen label sits over the
     screen, so clicking anywhere checks it and the screen is gone. The
     Replay Intro row in the Intro menu points at the same checkbox, so
     clicking it unchecks it and the screen returns.

     Showing the element again does NOT restart what runs here. Everything
     below that animates once is `forwards` and holds its end state, so on a
     second boot the foot bar was already full and stayed there. The script
     clears and reinstates those animations on reboot; see restartIntro() in
     scripts/faux.js. The marching blocks were the exception that hid this,
     being `infinite` and having no end state to hold.

   SPEED — TWO INDEPENDENT THINGS
     #isp-1 / #isp-2 / #isp-3 set --intro-dur: how long the screen is up. The
     fade, the wordmark and the foot bar all derive from that one value, so
     changing it keeps them in step.

     #bsp-1 / #bsp-2 / #bsp-3 set --march-dur: how fast the blocks cross. That
     is deliberately not tied to the length — the blocks are a busy signal and
     say nothing about progress, so a long boot should not make them crawl.
   ========================================================================== */

body {
  --intro-dur: 3s;              /* medium */
  --march-dur: 1.25s;           /* one crossing of the trough */
}
body:has(#bsp-1:checked) { --march-dur: 0.7s; }
body:has(#bsp-2:checked) { --march-dur: 1.25s; }
body:has(#bsp-3:checked) { --march-dur: 2s; }
body:has(#isp-1:checked) { --intro-dur: 1.75s; }
body:has(#isp-2:checked) { --intro-dur: 3s; }
body:has(#isp-3:checked) { --intro-dur: 5s; }

.intro {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: grid;
  place-items: center;
  /* NOT --desk. The title screen carries its own ground, because the desktop
     is not a safe one: it can be any of the five palette colours, and the
     dimness slider can take it to the raw colour — which Randomize Pattern
     does, deliberately. The wordmark and the machine are drawn in --light, so
     on the light ground that was --light on --light: measured 1.00:1, which is
     to say invisible, and it happened on every reboot after a randomise
     rather than only if you went looking for it.

     A boot screen is dark. Mixing the palette's darkest colour with black
     keeps the scheme's hue while guaranteeing the contrast: the worst case
     across all thirty-nine palettes, both modes and all five grounds is
     4.90:1, against 1.00 before. It no longer depends on the desktop at all. */
  background-color: color-mix(in srgb, var(--dark) 60%, #000000);
  background-image: repeating-linear-gradient(
    180deg,
    color-mix(in srgb, #000000 22%, transparent) 0 1px,
    transparent 1px 3px);
  animation: intro-out var(--intro-dur) linear forwards;
}

/* once the run is over the screen is gone for good — no invisible layer left
   sitting on top of the interface */
/* The last step drops the element out of the layout rather than merely
   hiding it. A faded-but-present overlay keeps a full-viewport layer alive
   for the rest of the visit; the script also checks #intro-off once the run
   is over, which covers browsers that do not animate `display`. */
@keyframes intro-out {
  0%, 82%   { opacity: 1; visibility: visible; display: grid; }
  99.9%     { opacity: 0; visibility: visible; display: grid; }
  100%      { opacity: 0; visibility: hidden; display: none; }
}

body:has(#intro-off:checked) .intro { display: none; }

/* the click target: the whole screen */
.intro-hit {
  position: absolute;
  inset: 0;
  z-index: 2;
}

.intro-inner {
  position: relative;
  z-index: 1;
  width: 420px;
  text-align: center;
  color: var(--light);
  /* The title screen scales with the window it is booting into, so the
     machine and the wordmark arrive at the size they will hand over to
     rather than jumping smaller at the fade. At 420px wide it would fit
     almost any viewport unscaled — this is about matching, not fitting.

     NOTHING position: fixed MAY LIVE IN HERE. A transform makes an element
     the containing block for its fixed descendants, so a fixed child stops
     being measured against the viewport and starts being measured against
     this 420px column. The foot bar was in here and that is exactly what
     happened to it; it is a sibling now. See the note in index.html. */
  transform: scale(var(--fit, 1));
}


/* -- the machine ----------------------------------------------------------- */

.intro-rig {
  display: block;
  width: 256px;                  /* 4x the 64-unit drawing */
  height: 176px;
  margin: 0 auto 24px;
  color: var(--light);
  shape-rendering: crispEdges;
}
.intro-rig .s-case   { fill: var(--light); }
.intro-rig .s-lip    { fill: color-mix(in srgb, var(--deep) 70%, var(--dark)); }
.intro-rig .s-screen { fill: var(--pop); }
.intro-rig .s-dark   { fill: var(--dark); }
.intro-rig .s-tb     { fill: var(--light); }

/* the power lamp, breathing rather than blinking */
.intro-rig .s-led { fill: var(--light); animation: led 1.6s steps(1) infinite; }
@keyframes led { 0%, 60% { opacity: 1; } 61%, 100% { opacity: 0.25; } }

/* the little window on the machine's screen redraws itself as it "loads" */
.intro-rig .s-line { fill: var(--dark); opacity: 0; }
.intro-rig .s-l1 { animation: rig-line var(--intro-dur) steps(1) forwards; animation-delay: calc(var(--intro-dur) * 0.18); }
.intro-rig .s-l2 { animation: rig-line var(--intro-dur) steps(1) forwards; animation-delay: calc(var(--intro-dur) * 0.34); }
.intro-rig .s-l3 { animation: rig-line var(--intro-dur) steps(1) forwards; animation-delay: calc(var(--intro-dur) * 0.50); }

@keyframes rig-line { 0% { opacity: 0; } 1%, 100% { opacity: 1; } }


/* -- the mascot ------------------------------------------------------------ */

.intro-name {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
}

.intro-fox {
  flex: none;
  width: 64px;
  height: 64px;
  shape-rendering: crispEdges;
  animation: fox-peek var(--intro-dur) steps(1) forwards;
}
.intro-fox .f-coat  { fill: var(--pop); }
.intro-fox .f-light { fill: var(--light); }
.intro-fox .f-dark  { fill: var(--dark); }

/* The fox used to arrive at 25% of the run — after the machine had already
   drawn its first two lines — so the wordmark sat there alone and the logo
   assembled itself out of order. It now lands at 5%, which reads as the logo
   appearing whole rather than the fox turning up late. */
@keyframes fox-peek {
  0%, 4%   { transform: translateY(20px); opacity: 0; }
  5%, 100% { transform: translateY(0); opacity: 1; }
}


/* -- wordmark and loading bar ---------------------------------------------- */

.intro-word {
  font-family: var(--f-head);
  font-weight: 700;
  font-size: 40px;               /* 5 x the 8px grid */
  line-height: 44px;
  letter-spacing: 2px;
  color: var(--light);
}

.intro-sub {
  font-size: 11px;
  line-height: 16px;
  letter-spacing: 3px;
  margin: 8px 0 20px;
  color: color-mix(in srgb, var(--light) 88%, var(--desk));
}

/* ---------------------------------------------------------------------------
   THE LOADING BAR
   Three blocks travelling left to right through a rounded trough and
   vanishing off the end, the way XP's did. They do not accumulate: each one
   clears the trough behind it, which is the whole character of that bar. A
   filling version was tried and reads wrong here — it stacks up and nothing
   ever empties.

   It measures nothing, deliberately. The thin rule along the foot of the
   screen is what tracks the actual wait.
   --------------------------------------------------------------------------- */

/* THE LOADING BAR — Windows XP's, which is a specific thing.

   Three small blocks travel together as a tight group, left to right, and
   leave the trough empty behind them. It is not a progress bar: nothing
   fills, and the group simply crosses again.

   What was wrong before: the three blocks were given animation delays a
   third of the cycle apart, which spread them evenly across the whole
   trough. At any moment one sat at each end and one in the middle, so it
   read as a bar filling up rather than as a group crossing, and there was
   never a moment when the trough was clear. The delays here are a fraction
   of that — just enough to space the blocks by their own width plus a gap —
   so the three stay together as one moving cluster.

   The travel runs from off the left edge to off the right, so blocks enter
   and leave rather than appearing and vanishing in place; overflow on the
   trough does the clipping. Motion is linear, because XP's was smooth.
   -------------------------------------------------------------------------- */

.intro-bar {
  position: relative;
  width: 300px;
  height: 20px;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 5px;
  background: color-mix(in srgb, var(--dark) 70%, #000);
  border: 2px solid var(--light);
  box-shadow: inset 2px 2px 0 0 color-mix(in srgb, #000 45%, transparent);
}

.intro-bar i {
  position: absolute;
  top: 3px;
  bottom: 3px;
  width: 11px;
  border-radius: 3px;
  background: var(--pop);
  /* lit along the top, shaded at the foot — XP's block had a gloss */
  box-shadow:
    inset 0  2px 0 0 color-mix(in srgb, #fff 55%, transparent),
    inset 0 -2px 0 0 color-mix(in srgb, #000 35%, transparent);
  animation: intro-march var(--march-dur) linear infinite backwards;
}

/* 11px block + 5px gap = 16px apart, over a 316px cycle — so the offset is
   16/316 = 0.0506 of however long one crossing takes. Expressed as a fraction
   of --march-dur rather than a fixed number of seconds, because a fixed delay
   would space the blocks differently at each speed: at Fast they would drift
   apart, at Slow they would overlap. This way the cluster holds its shape.
   Negative, so the group is already formed on the first pass. */
.intro-bar i:nth-child(2) { animation-delay: calc(var(--march-dur) * -0.0506); }
.intro-bar i:nth-child(3) { animation-delay: calc(var(--march-dur) * -0.1012); }

@keyframes intro-march {
  from { left: -16px; }
  to   { left: 300px; }
}

.intro-hint {
  font-size: 11px;
  line-height: 16px;
  letter-spacing: 2px;
  margin-top: 18px;
  color: color-mix(in srgb, var(--light) 78%, var(--desk));
  /* six ticks covers the longest title screen; an infinite animation would
     keep asking for frames long after the screen is gone */
  animation: intro-blink 1s steps(1) 6;
}
@keyframes intro-blink { 0%, 50% { opacity: 1; } 50.01%, 100% { opacity: 0.15; } }


/* Visitors who ask their system for less motion get the screen without the
   moving parts — it still appears and still clears itself. */
@media (prefers-reduced-motion: reduce) {
  .intro-bar i { animation: none; left: 8px; }
  .intro-bar i:nth-child(2) { left: 24px; }
  .intro-bar i:nth-child(3) { left: 40px; }
  .intro-rig .s-led { animation: none; }
  .intro-fox, .intro-rig .s-line { animation-delay: 0s; animation-duration: 0.01s; }
  .intro-hint { animation: none; }
}


/* --------------------------------------------------------------------------
   THE WAIT INDICATOR
   The marching blocks say the machine is busy but not for how long, so a
   thin rule along the foot of the screen fills once over the run. It is the
   only thing here that actually measures anything.
   -------------------------------------------------------------------------- */

.intro-total {
  position: fixed;
  left: 12px; right: 12px; bottom: 10px;
  height: 8px;
  padding: 1px;
  border-radius: 5px;
  overflow: hidden;
  background: color-mix(in srgb, #000 45%, transparent);
}
/* THE FOOT BAR MOVES ON A TRANSFORM, NOT ON ITS WIDTH.

   This went from 40 steps to 120 and still read as choppy, which is the
   clue: the step count was not what was wrong with it. Animating `width`
   changes the geometry of the box, so every frame costs a layout and a
   repaint and none of it can be handed to the compositor. Running that
   alongside the marching blocks, the fade, the rig lines, the fox and the
   blinking hint is what made it stutter — the bar was dropping frames, and
   a dropped frame looks exactly like a coarse step.

   scaleX() is a compositor transform: no layout, no repaint, just the same
   texture drawn at a different size each frame. So it can run at the display
   refresh rate, and with a linear curve there are no increments left to see.
   Smaller steps could never have fixed this; there is nothing to quantise
   now, because the movement is continuous.

   The fill gives up its own radius. Scaling a rounded box stretches the
   corners with it, so the leading cap would have distorted as the bar grew.
   The trough is already rounded and clips to it, which shapes the ends
   correctly at no cost.

   IT FINISHES BEFORE THE SCREEN GOES. The bar used to run for the whole of
   --intro-dur, but intro-out starts fading at 82% of that, so the bar was
   only four fifths full when the screen began to leave and filled the last
   fifth while fading out. It never visibly completed — the one thing on the
   title screen that measures anything never got to show that it was done.

   So it runs to 200ms short of the fade instead: 0.82 is where intro-out
   begins, and the 0.2s buys a beat with the bar sitting full before anything
   moves. Both numbers come from the same keyframe, so if the fade is ever
   moved off 82% this has to move with it. `forwards` holds it full through
   the fade. */
.intro-total i {
  display: block;
  width: 100%;
  height: 100%;
  background: var(--light);
  transform: scaleX(0);
  transform-origin: left center;
  animation: intro-total calc(var(--intro-dur) * 0.82 - 0.2s) linear forwards;
}
@keyframes intro-total { from { transform: scaleX(0); } to { transform: scaleX(1); } }

@media (prefers-reduced-motion: reduce) {
  .intro-total i { animation-duration: 0.01s; }
}
