/* ------------------------------------------------------------------
   A program, standing inside the site

   The simulator is a whole application: it fixes things to the corners
   of the window, hides the page's overflow and owns the wheel. Opened at
   its own address it took over the browser completely, and a reader who
   got there had no way back that was not the back button.

   So it is framed rather than spliced. Neither one's CSS can reach the
   other, and the program keeps its own window.

   ⭐ AND THE WINDOW IS EVERYTHING UNDER THE BAR (Seth, 2026-10-27).
   It used to stop short: a title above it, a line about it, a link out,
   and the instrument panel below -- which on a wide screen left the
   program a letterbox in the middle of a lot of nothing. The bar stays,
   because that is how you leave. The rest of the glass is the program's.
   ------------------------------------------------------------------ */

/* ⚠️⚠️ THE PAGE IS EXACTLY THE GLASS AND NOTHING ON IT SCROLLS
   (Seth, 2026-10-29, from Chris's iPhone: *"it loaded it with space of the
   site below it and the top was under the header … hiding the details
   options which we really need"*).

   TWO FAULTS, ONE SYMPTOM, AND NEITHER WAS THE HEADER.

   1. **`100vh` ON iOS IS THE *LARGE* VIEWPORT — the height the page would
      have if the URL bar were hidden.** `reset.css` sets `body { min-height:
      100vh }` and `chrome.css` sets `.page { min-height: 100vh }`, so on a
      phone the page was forced TALLER THAN THE VISIBLE GLASS by exactly the
      height of the browser's own bar. That is the space he saw below the
      simulator, and it is why the page could scroll at all.

   2. **The frame's height was a SUBTRACTION OF A GUESS.** It was
      `calc(100svh - var(--barh))`, and `--barh` is a number typed in
      `chrome.css` (4rem, 3.5rem on a phone) while the bar's real height is
      whatever its contents come to. The two agree until they do not.

   Together: the page overflowed, so it scrolled — and the bar is
   `position: sticky`, so the moment it scrolled the bar sat ON TOP of the
   first few rems of the program. The orrery hangs its controls off the top
   of its own window (`position: fixed; inset: 0`), so the bar was covering
   precisely the details options.

   ⭐⭐ THE FIX IS TO STOP DOING ARITHMETIC. The page is a flex column
   that is exactly the visible height, the bar takes what it needs, and the
   program takes the rest — whatever that turns out to be. Nothing is
   subtracted, so nothing can be subtracted wrongly, and a bar that grows
   (a longer name, a bigger font, a notch) shortens the frame instead of
   covering it.

   `dvh` is the live visible height, which is safe here only BECAUSE nothing
   scrolls: with no scrolling the browser never collapses its bar, so the
   value never changes underneath the frame. On a page that scrolled, `dvh`
   would resize the program every time the bar slid away. */
html, body {
  height: 100vh;         /* for anything that does not know the unit below */
  height: 100dvh;        /* the visible height, and the whole of the iOS fix */
  min-height: 0;         /* ⚠️ undoes reset.css's own `min-height: 100vh` */
  overflow: hidden;
  overscroll-behavior: none;   /* no rubber-band over a program that owns the drag */
}

/* `stable` keeps a scrollbar's width reserved whether or not anything
   scrolls, so the whole site's layout cannot shift between a short page and
   a long one. Here it would be a dead strip down the side of a program that
   is meant to fill the glass, and nothing on this page can scroll anyway. */
html { scrollbar-gutter: auto; }

/* Already `display: flex; flex-direction: column` in chrome.css -- it is the
   `min-height` that has to go, and the height that has to become exact. */
.page {
  height: 100%;
  min-height: 0;
}

.sim {
  /* No `wrap`: the rails that hold running text to a readable measure are
     exactly what a full-bleed program must not have. */
  display: block;
  width: 100%;
  margin: 0;
  padding: 0;
  /* Everything the bar did not take, measured rather than assumed.
     `min-height: 0` because a flex item will not shrink below its content
     without it, and an iframe's content is a whole other document. */
  flex: 1 1 auto;
  min-height: 0;
  height: auto;
  overflow: hidden;
  background: #04070a;
}

.sim__frame {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}

/* ------------------------------------------------------------------
   ⭐ THE ORRERY'S ARRIVAL AND EXIT (Seth, 2026-10-29)

   *"slide transition as usual, but then we have a hologram kinda loading
   bar and once it loads, then the whole thing starts so tiny we can't see
   it and quickly expands to full. For transitioning away, it shrinks down
   quickly as the slide transition takes place."*

   The order of events is js/simframe.js; this is how each one looks.
   ------------------------------------------------------------------ */
/* No black behind the bar (Seth, 2026-10-29: "we want only the loading bar
   until it's ready"). The program's own window is dark enough once it is
   there; until then the page shows the sky like every other page. */
.sim--grow { position: relative; background: transparent; }

/* Until it is ready the frame is a speck at the centre, and invisible. */
.sim--grow .sim__frame {
  transform: scale(.02);
  opacity: 0;
  transform-origin: 50% 50%;
}
.sim--grow.is-live .sim__frame {
  transform: scale(1);
  opacity: 1;
  /* It turns solid AS it grows, over the same time, rather than snapping
     opaque at the start: a speck that is see-through, a full-size frame
     that is solid (Seth, 2026-10-29). */
  transition: transform 520ms cubic-bezier(.16, .9, .3, 1.06), opacity 520ms cubic-bezier(.3, .6, .4, 1);
}

/* The hologram: a turning ring, a line of type, the bar, the number. */
.simload {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: .9rem;
  pointer-events: none;
  font-family: var(--font-hud);
  color: var(--accent);
  text-shadow: 0 0 8px var(--accent-glow);
  animation: holoFlicker 2.8s steps(1, end) infinite;
  transition: opacity 200ms ease-out, transform 260ms ease-in;
}
/* Gone for good once the program is up. ⚠️ The flicker is an animation on
   opacity, and an animation outranks a plain declaration -- so "opacity: 0"
   alone left the bar fully lit underneath the frame, and it reappeared the
   moment the frame shrank away on leaving (Seth, 2026-10-29). The animation
   stops, and the bar is hidden outright once it has faded. */
.sim--grow.is-live .simload {
  animation: none;
  opacity: 0;
  transform: scale(1.06);
  visibility: hidden;
  transition: opacity 200ms ease-out, transform 260ms ease-in, visibility 0s linear 260ms;
}
.simload__ring {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  border: 1px solid var(--accent-faint);
  border-top-color: var(--accent);
  border-right-color: var(--accent-glow);
  box-shadow: 0 0 18px var(--accent-faint), inset 0 0 12px var(--accent-faint);
  animation: holoTurn 1.1s linear infinite;
}
.simload__label {
  margin: 0;
  font-size: .68rem;
  letter-spacing: .32em;
  text-transform: uppercase;
}
.simload__bar {
  position: relative;
  width: min(320px, 70vw);
  height: 6px;
  border: 1px solid var(--accent-glow);
  border-radius: 3px;
  overflow: hidden;
  background:
    repeating-linear-gradient(90deg, var(--accent-faint) 0 6px, transparent 6px 10px);
  box-shadow: 0 0 14px var(--accent-faint);
}
.simload__bar span {
  position: absolute;
  inset: 0 auto 0 0;
  width: 0;
  background: linear-gradient(90deg, var(--accent-dark), var(--accent) 70%, var(--accent-pale));
  box-shadow: 0 0 10px var(--accent), 0 0 22px var(--accent-glow);
}
/* A scan sweeping along the bar, so a slow connection still looks alive. */
.simload__bar::after {
  content: '';
  position: absolute;
  inset: 0;
  width: 30%;
  background: linear-gradient(90deg, transparent, rgba(157, 246, 222, .35), transparent);
  animation: holoSweep 1.6s ease-in-out infinite;
}
.simload__pct {
  margin: 0;
  font-size: .72rem;
  letter-spacing: .18em;
  color: var(--accent-pale);
}
@keyframes holoTurn  { to { transform: rotate(360deg); } }
@keyframes holoSweep { from { transform: translateX(-100%); } to { transform: translateX(340%); } }
@keyframes holoFlicker { 0%, 100% { opacity: 1; } 47% { opacity: .82; } 48% { opacity: 1; } 83% { opacity: .9; } 84% { opacity: 1; } }

/* Leaving: it shrinks away while the page slides.
   With the browser's own page transition the frame is lifted out of the
   page's snapshot by giving it a name of its own, so it can shrink on its own
   while the rest of the page slides. Without it, the script's leaving class
   does the same. Only once it is live: a frame still loading has nothing to
   shrink. */
.sim--grow.is-live .sim__frame { view-transition-name: simframe; }
/* ⚠️ The ::view-transition-*(simframe) rules and @keyframes simShrink live
   in chrome.css, NOT here. A cross-document transition is styled by the page
   being ARRIVED AT, and that page does not load sim.css -- so from here they
   were never applied and the frame only cross-faded in place (Seth,
   2026-10-29: "now has it just disappear"). */
body.is-leaving .sim--grow.is-live .sim__frame {
  transition: none;
  animation: simShrink var(--t-shrink) var(--ease-shrink) both;
}

@media (prefers-reduced-motion: reduce) {
  .simload, .simload__ring, .simload__bar::after { animation: none; }
  .sim--grow .sim__frame, .sim--grow.is-live .sim__frame { transform: none; transition: opacity 120ms linear; }
  body.is-leaving .sim--grow.is-live .sim__frame { animation: none; }
}

/* A program is not something to print. */
@media print {
  .sim { display: none; }
}
