/* ─────────────────────────────────────────────────────────────────────────
   surfaces/skymap/skymap.css — this surface's own stylesheet, and nothing
   that a second surface would also want.

   surfaces/_shell/README.md's test, applied honestly: "Something one surface
   needs and no other could is not shell material." A full-bleed sky stage
   with a heads-up panel over it is that. The palette, the type scale, the
   header, the footer, the install sheet and every primitive on the page below
   the fold come from shell.css and are not restated here.

   ONE COLOUR IS NAMED IN THIS FILE, AND IT IS MARKED AT THE LINE THAT NAMES
   IT. Every other value is a shell token, so all four skins work without this
   file knowing they exist — the same rule product/app/app.css sets and the
   reason check_css.py exists. The exception is the stage's own black, which
   is not a surface the reader looks AT: it is what both renderer tiers clear
   their buffer to, and a day value for it would be a lie about what is on the
   screen. The exemption is declared on that line, in the form the guard reads.

   AND IT IS DECLARED ONLY THERE, WHICH IS NOT A STYLE PREFERENCE. check_css.py
   strips comments before it parses — EXCEPT the ones carrying the exemption
   marker, which have to survive to reach the declaration they excuse. So a
   header that spelled the marker out would be fed to the CSS parser as
   though it were a rule, and its prose read as selectors and declarations.
   That is not a hypothetical: this paragraph is the second draft, and the
   guard rejected the first one on 2026-08-08 for exactly that. Describe the
   escape hatch in prose here; spell it only where it applies.
   ───────────────────────────────────────────────────────────────────────── */

/* ── the stage ─────────────────────────────────────────────────────────────
   FIXED, not absolute, and behind everything. The sky is the page's
   background in the literal sense: it fills the viewport, it does not scroll,
   and the reader scrolls the WORDS over it. That is what makes one URL both a
   sky map and an indexable page — MASTER PLAN §5c: the incumbent portfolio has
   "no URL to share, nothing to index, nothing Google can rank", and that is
   the whole opening.

   `touch-action:none` is set on this element by js/sky3d.js rather than here,
   because it is a fact about the drag handler and belongs with it.

   IT IS ALSO THE GROUND UNDER THE HEADS-UP PANEL, which is why it declares
   what it carries. `.sky-first` paints nothing, so the switches and the
   calibration number sit straight on this black — in every skin, because the
   black is not a token and does not flip. The buttons beside them are
   `.sh-btn` and paint their own background, so shell.css measures those. */
/* carries: .sky-switches, .sky-cal .val, .sky-probe */
.sky-stage{
  position:fixed; inset:0; z-index:0;
  background:#000;                 /* theme-ok: see js/sky3d.js on why both
                                      renderer tiers clear to exactly black */
  overscroll-behavior:none;
}
.sky-stage canvas{display:block;width:100%;height:100%;}

/* ── EVERYTHING STAYS ON TOP OF THE SKY, AND THIS IS NOT AUTOMATIC ─────────
   MEASURED IN CHROMIUM 2026-08-08, because the first version of this file got
   it wrong in a way that looked like a rendering fault rather than a CSS bug.
   `.sky-stage` is POSITIONED and lives inside `<main>`, and CSS paints a
   stacking context's children in a fixed order: non-positioned block-level
   boxes first, then positioned ones. So the canvas painted OVER the whole page
   — the readout and the buttons survived only because `backdrop-filter` makes
   them stacking contexts too, which promotes them into the same late layer.

   The symptom was that half the page silently vanished: the switches, the
   status strip and every word below the fold were laid out at the right size
   in the right place and covered by black. `document.elementFromPoint` on the
   switches returned a CANVAS, which is what named it.

   So the sections are positioned explicitly. It costs two declarations and it
   is the difference between a page and half a page.

   THE FOOTER ALSO CHANGES GROUND HERE, which is a colour decision hiding in a
   stacking fix and is why it declares what it carries. shell.css paints
   `.sh-foot` in `var(--panel)`; over a fixed sky it has to be opaque and it has
   to continue `.sky-below`, which is `var(--bg)` — a panel-coloured band under
   a bg-coloured page reads as a second, unexplained section. The words on it
   are shell.css's fine print: `.sh-fine` and its links land straight on this
   background, and `.sh-net a` paints its own. Overriding another sheet's
   ground without saying what stands on it is how a network sheet gets a pair
   nobody measures. */
.sh-head{position:relative;z-index:3;}
main{position:relative;z-index:2;}
.sky-first,.sky-status,.sky-below{position:relative;z-index:1;}

/* ── THE SECTION WAS EATING NINETY PERCENT OF EVERY DRAG ───────────────────
   Found 2026-08-11 by measurement, after Ether: "Why can't I rotate and zoom
   it and use it like a true three d app... this is like a page which shows
   half thing." He was right, and it was not a 3-D problem at all.

   The rule directly above puts `.sky-first` at z-index 1 over a stage at
   z-index 0, which is correct and is what stops the canvas painting over the
   page — the long note further up records what that cost to find. But z-index
   decides HIT TESTING as well as painting, and `.sky-first` is a 716px-tall
   block that paints nothing. So it sat, invisible, over almost the whole sky
   and swallowed every pointerdown before sky3dCamera could see it.

   MEASURED: document.elementFromPoint over a 180-point grid at 375×812 found
   the canvas on 18 of them. Ten percent. Everywhere else the finger was
   landing on a transparent div, so the drag did nothing and the page scrolled
   instead — which is exactly what "a page which shows half a thing" feels
   like, and it had nothing to do with the renderer.

   So the section stops being a target and its actual controls become ones. It
   has to be this way round: `pointer-events:none` on a parent is inherited by
   every child, and each control turns it back on for itself. A blanket
   `.sky-first > *` would put the invisible wrappers straight back. */
.sky-first{pointer-events:none;}
.sky-first .sky-read,
.sky-first .sky-acts .sh-btn,
.sky-first .sky-cal,
.sky-first .sky-layers{pointer-events:auto;}
/* carries: .sh-fine, .sh-fine a */
.sh-foot{position:relative;z-index:2;background:var(--bg);}

/* ── THE SKY IS A NIGHT SCENE IN EVERY SKIN, AND THAT BREAKS THE LIGHT ONES ─
   SEEN IN CHROMIUM 2026-08-08 on Porcelain, and it is worth writing down
   because it is not a bug in the palette — it is the palette being asked the
   wrong question.

   Every other surface in this network paints a document, so `--text` is "the
   colour writing goes in" and `--ink` is "the colour behind it", and both
   flip when the skin does. This surface paints a PHOTOGRAPH OF THE NIGHT that
   stays black whatever the skin is, and then writes on top of it. On Porcelain
   that meant the ground below the horizon filled NEAR-WHITE and every star
   name, planet name and nakshatra label came out near-black on black.

   So the stage — and only the stage — carries the night palette when the skin
   is a light one. The values are Midnight's, copied verbatim from shell.css,
   which is the same bargain shell.css itself strikes with app.css: a palette
   is copied by hand, on purpose, and never diverged silently. The DARK skins
   are untouched, so Abyss and Plain still look like themselves over the sky.

   js/overlay.js reads its tokens from THIS ELEMENT rather than from <html>,
   which is what makes the scoping do anything.

   IT COVERS THE HEADS-UP PANEL TOO, and for the same reason: `.sky-first` has
   no background of its own, so its readout, its buttons and its switches sit
   directly on the same black. Scoping only the canvas fixed the labels and
   left the switches near-black on black beside them, which was worse than
   fixing neither. The result on a light skin is a dark heads-up display over
   a dark sky and a light page below it — which is what every camera app on a
   phone does, and for this reason. `.sky-status` and `.sky-below` are NOT in
   here: both paint an opaque background and are documents, so they stay in
   whatever skin the reader chose.

   ── THE COPY MUST BE COMPLETE, AND FOR TWO RELEASES IT WAS NOT ─────────────
   Found 2026-08-08 by the scoped-palette check the moment it existed (I445),
   which is the first thing that had ever measured this block. `--panel2` and
   `--glow` were missing. Every other token here matched shell.css's `:root`
   verbatim, so the omission read as complete and was invisible by inspection.

   A MISSING TOKEN IN A SCOPE IS WORSE THAN A WRONG ONE, because it does not
   resolve to nothing — it INHERITS, and what it inherits from is the light
   skin this block exists to escape. So `--text` was Midnight's near-white
   while `--glow` was Porcelain's #FFFFFF: 1.29:1, white on white, in the one
   place a reader in daylight would be looking. Thirty-two pairs failed on two
   missing lines.

   THE RULE THAT FOLLOWS: this block is a palette, not a patch list. Every
   GROUND shell.css declares belongs here whether or not a rule in this file
   currently paints on it — a rule added later has no way to know the scope is
   partial, and it will inherit daylight without a word.

   ── AND THAT RULE IS NOW ENFORCED, BY test/scope_check.py (I522) ───────────
   It was a sentence in a comment until 2026-08-09, which is a check that is
   true once. Delete the two lines again today and
   `surfaces/_shell/check_css.py` still prints "the network's stylesheets are
   clear" — its matrix measures a scope against the grounds it DECLARES, so an
   omitted ground drops out of the sweep instead of failing it, and that
   narrowing is correct because widening it reports twenty-six failures for
   pairs that exist nowhere.

       python3 surfaces/skymap/test/scope_check.py

   asserts three things this sheet says about itself and nothing else could:
   that the scope declares every colour token :root does, that every value is
   still byte-identical to Midnight's, and that every tone clears AA on every
   ground painted over the stage — including the literal black below, which is
   not a token and is therefore in no palette's matrix.

   ── `carries:` NOW WORKS INSIDE THIS SCOPE, AND FOR ONE DAY IT DID NOT ─────
   This paragraph said the opposite until 2026-08-09, and the sentence it
   replaces was TRUE when it was written: check_carried() ran once per layer
   but was handed the whole sheet, so an annotation on a scoped ground was also
   measured in the skin-3 layer where the scope is NOT applied. Annotating the
   three grounds in here produced three findings describing a page nobody can
   reach — Porcelain's near-black tones on the sky, inside elements that
   inherit Midnight's — so the convention was backed out and test/scope_check.py
   shipped instead. That was I567; it landed the same day, check_carried() now
   takes the layer's own rules, and the three grounds above the sky are
   annotated. The measurement stays written down because it is why this file
   has both a test and a set of annotations.

   IT IS RECORDED HERE RATHER THAN DELETED FOR A SECOND REASON. A comment that
   forbids the right thing is what a reviewer reads INSTEAD of the code, and
   this project has already shipped one wrong answer that way. The stale
   sentence cost a day; leaving it would have cost the convention.

   ── AND THE TWO CHECKS ARE NOT THE SAME CHECK ─────────────────────────────
   MEASURED 2026-08-09, by deleting one scoped token at a time from a scratch
   copy and running the network guard:

       --panel, --muted, --text, --moon deleted   →  the annotations catch it
       --panel2, --glow deleted                   →  still nothing, still clear

   `carries:` measures the pairs THIS SHEET paints, so it reaches a token only
   where a rule here uses it. Nothing here paints with --panel2 or --glow: the
   grounds that do inside `.sky-first` are shell.css primitives, and an
   annotation lives on the rule, in the sheet that owns it. So the I445 bug —
   a token the scope OMITS, which inherits daylight rather than resolving to
   nothing — is still caught by exactly one thing, and it is assertion 1 of

       python3 surfaces/skymap/test/scope_check.py

   That is the division, and it is not redundancy: `carries:` is this sheet
   declaring what its own grounds hold, in the network's own guard; scope_check
   is the palette being checked for completeness, byte-drift, and every tone on
   every ground including the ones shell.css paints in here. Neither subsumes
   the other and removing either loses a real fault. */
:root[data-skin="3"] .sky-stage,
:root[data-skin="3"] .sky-first{
  --ink:#0A0D12; --panel:#10141C; --panel2:#141A24; --line:#1E2530; --line2:#2A3442;
  --text:#DCE3EC; --muted:#96A2B2; --faint:#7A8698;
  --moon:#D6E6F7; --moon-deep:#9FB8D4;
  --amber:#E0A85C; --green:#84BE8B; --rose:#D194A0;
  --bg:#05070A; --glow:#121B29;
}

/* The header sits over that same black. On a light skin its own words would
   be near-black on it, and `.sh-name b` in shell.css is more specific than
   `.sh-name`, so the bold half has to be named too or only half of the
   wordmark changes — which is worse than neither. */
:root[data-skin="3"] .sh-head .sh-name,
:root[data-skin="3"] .sh-head .sh-name b{color:var(--ink);}
:root[data-skin="3"] .sh-head .sh-name{text-shadow:0 1px 10px rgb(0 0 0 / .8);}

/* ── the first screen ──────────────────────────────────────────────────────
   One viewport tall, so the sky is the whole first impression and the words
   begin exactly one scroll below it. 100dvh is iOS 15.4+; the fallback keeps
   it usable on anything older, which is all it has to do. */
.sky-first{
  min-height:calc(100dvh - 96px);
  display:flex; flex-direction:column; justify-content:flex-end;
  padding-bottom:14px;
}
@supports not (height:100dvh){ .sky-first{min-height:calc(100vh - 96px);} }

/* ── the readout ───────────────────────────────────────────────────────────
   What is under the reticle. Deliberately the largest type on the screen and
   deliberately only two lines: a name, and what it means. MASTER PLAN §5's
   sentence — not "that is Aldebaran" but "that is ROHINI, and the Moon is
   standing in it right now" — is the second line, and it is why the second
   line is allowed to be longer than the first is big.

   ── IT IS WRITTEN `.sky-first .sky-read` SO THE GUARD CAN SEE WHERE IT IS ───
   The parent is not decoration and it is not specificity for its own sake. A
   scope's layer is joined by rules whose SELECTOR proves containment — nothing
   in check_css.py looks at the built page, deliberately, because guessing
   whether one class sits inside another is how a guard starts measuring a pair
   nobody paints. Written bare, `.sky-read` stays on the root palette, and the
   annotation below then reports Porcelain's --moon over the sky: a page no
   reader can reach, because this element is inside `.sky-first` and inherits
   Midnight's. Naming the parent costs one class of specificity and moves the
   whole family into the scope. Every rule that overrides this one carries the
   parent too — the reduced-motion rule at the foot of this file would
   otherwise drop BELOW it and stop suppressing the blur.

   The ground is translucent, so it is measured composited: `over:` names what
   is behind it, which is the stage's black in every skin. */
/* carries: .sky-first .sky-read .name, .sky-first .sky-read .detail
   over: .sky-stage */
.sky-first .sky-read{
  background:color-mix(in srgb, var(--panel) 62%, transparent);
  backdrop-filter:blur(14px);
  border:1px solid var(--line2); border-radius:var(--radius);
  padding:12px 14px; margin:0 0 10px;
}
/* Plain has no translucency anywhere, so the readout is opaque --panel there
   and needs no `over:` — it is the same two lines on a ground that resolves. */
/* carries: .sky-first .sky-read .name, .sky-first .sky-read .detail */
:root[data-skin="7"] .sky-first .sky-read{background:var(--panel);backdrop-filter:none;}
.sky-first .sky-read .name{
  font-size:var(--t-xl); line-height:1.15; color:var(--text);
  font-weight:600; margin:0; min-height:1.15em;
}
.sky-first .sky-read .detail{
  font-family:var(--mono); font-size:var(--t-xs); letter-spacing:.04em;
  line-height:1.6; color:var(--moon); margin:4px 0 0; min-height:1.6em;
}

/* ── ?probe · the loop, read off the sky ───────────────────────────────────
   Added 2026-08-09 for blackbox I611. js/skymap.js mounts this only when the
   URL carries `?probe`, and js/sky3d.js::health() is what it prints.

   IT SITS UNDER THE RETICLE, AND THAT IS A PLACEMENT DECISION RATHER THAN A
   TASTE ONE. The shell's header owns the top of this viewport and `.sky-first`
   owns the bottom; the one band of the sky that is reliably nobody else's is
   the middle, and it is also where the eye already is because the reticle is
   there. The reading has to be legible AT THE MOMENT THE SKY GOES OUT, which
   is why it cannot live in the status strip one scroll below.

   No ground of its own: it is measured against the stage's black, which is
   what is behind it in every skin, and `.sky-stage` declares that it carries
   this. --amber rather than --text so it can never be mistaken for something
   the product is saying. */
.sky-probe{
  position:absolute; z-index:5; pointer-events:none;
  top:calc(50% + 34px); left:0; right:0; margin:0;
  text-align:center;
  font-family:var(--mono); font-size:var(--t-xs); letter-spacing:.04em;
  color:var(--amber);
}

/* ── the controls ──────────────────────────────────────────────────────────
   A row of pills that wraps. Nothing here is a menu: every control is one tap
   and its state is visible without opening anything, because a sky map is
   used one-handed in the dark with your other arm in the air. */
.sky-acts{display:flex;flex-wrap:wrap;gap:8px;align-items:center;}
.sky-acts .sh-btn{white-space:nowrap;}

/* ONE ROW THAT SCROLLS, NOT A GRID THAT WRAPS. Measured at 375x812: wrapping
   to a second line cost 40px and pushed the sky to 45% of a phone screen. The
   scrollbar is hidden because on a touch device there is nothing to grab and
   on a desktop the row fits. */
.sky-switches{
  display:flex; gap:14px; margin:10px 0 0;
  overflow-x:auto; -webkit-overflow-scrolling:touch;
  scrollbar-width:none;
  font-family:var(--mono); font-size:var(--t-xs); letter-spacing:.06em;
  text-transform:uppercase; color:var(--muted);
}
.sky-switches::-webkit-scrollbar{display:none;}
.sky-switches label{
  display:inline-flex;align-items:center;gap:6px;cursor:pointer;
  white-space:nowrap;flex:0 0 auto;
}
.sky-switches input{accent-color:var(--moon);width:14px;height:14px;margin:0;}

/* ── THE LAYER DRAWER ───────────────────────────────────────────────────────
   Eleven switches in eleven categories, costing one line of screen when shut.

   THE PANEL IS ABSOLUTE, AND THAT IS THE WHOLE DESIGN. skymap.css already
   carries the measurement that forced it: at 375×812 a switch row wrapped to
   two lines pushed the sky to 45% of the screen. A drawer that pushed layout
   would repeat that mistake at four times the size, so this one is laid OVER
   the sky — the reader can watch a category vanish from the sky behind the
   panel at the moment they untick it, which is the one interaction where
   seeing the result immediately is the entire point. */
/* ── THE CONTROL BAR, AND THE PAGE FOLDED BEHIND IT ────────────────────────
   Ether, 2026-08-11: "use my location and point at the sky, all of these and
   these layers, all of this can be betterly organized" and "on this page when
   I scroll I see a lot of text... that should all be removed, and it should
   have like just a menu that can be collapsed."

   Both are the same complaint: the controls were three stacked rows in normal
   flow with a wall of prose underneath, which reads as a form on a document.
   One wrapping row of buttons, and the document behind an About button.

   THE WORDS ARE NOT DELETED, THEY ARE FOLDED. §5c wants a real page for a real
   search result and the whole indexed argument lives in `.sky-below`; it is
   `display:none` when the sheet is shut, which is still crawled. What changes
   is that a reader who drags the sky no longer falls into an essay. */
.sky-bar{
  display:flex; flex-wrap:wrap; align-items:center; gap:8px; margin:10px 0 0;
}
.sky-bar .sky-acts{display:contents;}

body:not(.sky-info-open) .sky-status,
body:not(.sky-info-open) .sky-below,
body:not(.sky-info-open) .sh-foot{display:none;}

.sky-layers{position:relative;}

/* MEASURED ON THE LIVE SITE AT 375×812, and it was wrong: the panel was
   420px tall around 578px of content, so 160px — the last two categories, both
   preset buttons and the attribution — sat below a fold with no scrollbar,
   because this sheet hides scrollbars and iOS hides them anyway. Ether's
   report was "most items are unavailable", and they were.

   TWO COLUMNS RATHER THAN A TALLER PANEL. Height is the scarce thing here: the
   panel already opens upward from a button near the bottom of a phone, and
   the note above records what happened the last time this control grew
   vertically. Two columns halve the list, so eleven switches and everything
   under them fit without scrolling at all — and the panel stops being a thing
   that has to be discovered by dragging it. */
.sky-layer-panel{
  position:absolute; z-index:3; bottom:calc(100% + 8px); left:0;
  min-width:min(300px, 92vw); max-width:min(360px, 92vw);
  max-height:min(74vh, 560px); overflow-y:auto;
  padding:14px 16px 12px;
  border:1px solid var(--line2); border-radius:12px;
  /* Translucent, so the sky is never fully hidden by the thing that edits it.
     The blur is what keeps the labels readable over a moving star field. */
  background:color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter:blur(14px); -webkit-backdrop-filter:blur(14px);
  box-shadow:0 12px 40px color-mix(in srgb, var(--ink) 55%, transparent);
}
.sky-layer-panel[hidden]{display:none;}

.sky-layer-group + .sky-layer-group{
  margin-top:12px; padding-top:12px; border-top:1px solid var(--line2);
}
.sky-layer-group h3{
  margin:0 0 8px; font-family:var(--mono); font-size:var(--t-xs);
  letter-spacing:.1em; text-transform:uppercase; color:var(--faint);
  font-weight:600;
}
/* The switches themselves, two abreast. auto-fit rather than a fixed 2, so a
   narrow phone that cannot give a label 118px collapses to one column on its
   own instead of clipping the words. */
.sky-layer-group{
  display:grid; grid-template-columns:repeat(auto-fit, minmax(118px, 1fr));
  gap:0 12px;
}
.sky-layer-group h3{grid-column:1 / -1;}

.sky-layer-group label{
  display:flex; align-items:center; gap:9px; cursor:pointer;
  padding:5px 0; font-size:var(--t-sm); color:var(--text);
  /* 34px of target on a 5px-padded 14px box — a switch that is opened
     outdoors, one-handed, in the dark. */
  min-height:34px;
  /* "Planetary nebulae" and "Globular clusters" are the long ones and they
     must not wrap a column into two lines and re-break the grid. */
  white-space:nowrap;
}
.sky-layer-group input{
  accent-color:var(--moon); width:15px; height:15px; margin:0; flex:0 0 auto;
}

/* The ground dial. Its own grid rather than the two-column switch layout —
   a range needs the width, and pairing it with a live percentage is what makes
   "drag it to nothing" a thing you can aim rather than guess at. */
.sky-layer-slider{grid-template-columns:auto 1fr auto; align-items:center; gap:0 10px;}
.sky-layer-slider label{
  font-family:var(--mono); font-size:var(--t-xs); letter-spacing:.06em;
  text-transform:uppercase; color:var(--muted); min-height:0; white-space:nowrap;
}
.sky-layer-slider input[type=range]{
  width:100%; accent-color:var(--moon); min-height:34px; margin:0;
}
.sky-layer-slider output{
  font-family:var(--mono); font-size:var(--t-xs); color:var(--faint);
  min-width:4ch; text-align:right;
}

.sky-layer-actions{
  display:flex; gap:8px; margin-top:12px; padding-top:12px;
  border-top:1px solid var(--line2);
}

.sky-credit{
  margin:12px 0 0; padding-top:10px; border-top:1px solid var(--line2);
  font-size:var(--t-xs); line-height:1.45; color:var(--faint);
}
.sky-credit a{color:var(--muted);}

/* ── the status strip ──────────────────────────────────────────────────────
   Where the surface says which of its facts are guesses. §5c's argument is
   that the incumbent is stale, uncalibrated and silent about both; this strip
   is the opposite of that and it is styled to be read rather than dismissed —
   --muted, not --faint, the same decision shell.css makes about the method
   line and for the same reason.

   THE TWO GROUNDS BELOW THE SKY DECLARE WHAT THEY CARRY (I446, I522). This
   half of the page is a DOCUMENT in the reader's own skin — no scope reaches
   it — so `carries:` resolves honestly in every layer here, which is exactly
   what it cannot do inside the scope above. Each name is resolved in this
   sheet and then in shell.css, in all four palettes; a rename fails the run
   rather than quietly covering nothing. */
/* carries: .sky-status b, .sky-status .warn */
.sky-status{
  font-family:var(--mono); font-size:var(--t-xs); line-height:1.7;
  letter-spacing:.04em; color:var(--muted);
  background:var(--bg); border-top:1px solid var(--line);
  margin:14px 0 0; padding:12px 0;
}
.sky-status b{color:var(--text);font-weight:600;}
.sky-status p{margin:0;}
.sky-status p:empty{display:none;}
.sky-status .warn{color:var(--amber);}

/* ── calibration ───────────────────────────────────────────────────────────
   Two arrows and a number. MASTER PLAN §5c names sensor calibration as one of
   the two standing complaints against Indian Sky Map, unaddressed since 2023,
   and this is the whole of the answer. */
.sky-cal{display:inline-flex;align-items:center;gap:6px;}
.sky-cal .val{
  font-family:var(--mono);font-size:var(--t-xs);color:var(--text);
  min-width:3.2em;text-align:center;
}

/* ── everything below the fold ─────────────────────────────────────────────
   Real prose on a real page, over an opaque background so the sky does not
   show through the reading. This half is what Google indexes and what a
   person who arrived from a search actually reads.

   The lede, the link and the method line sit STRAIGHT on this background; the
   cards below them paint their own, so shell.css measures those. `h1` is not
   named because it declares no colour of its own — it inherits, and naming an
   inherited tone would be a pair this sheet cannot resolve. */
/* carries: .sh-lede, a, .sh-method, .sh-method b */
.sky-below{
  background:var(--bg); border-top:1px solid var(--line);
  margin:18px -16px 0; padding:22px 16px 0;
}
.sky-below > :first-child{margin-top:0;}

/* The sky is still worth seeing behind a short page, so the background is
   only painted where there are words. */
@media (min-width:680px){
  .sky-below{margin-left:-24px;margin-right:-24px;padding-left:24px;padding-right:24px;}
}

/* ── the failure state ─────────────────────────────────────────────────────
   A sky map that could not load its catalogue must look broken, not empty. An
   empty black rectangle is indistinguishable from a working sky map pointed at
   a dark patch, and js/sky3d.js refuses to invent a placeholder sky for
   exactly that reason.

   IT IS THE SAME GROUND AS THE WORKING STAGE, so it carries the same words:
   the panel does not disappear when the catalogue fails to load, and a reader
   looking at a broken sky map is the one most likely to be reading the
   switches. `var(--ink)` is Midnight's near-black inside the scope and every
   dark skin's near-black outside it, so this is the ground the black stops
   being literal — which is exactly why it is worth measuring rather than
   assuming it inherits the line above. */
/* carries: .sky-switches, .sky-cal .val */
.sky-stage[data-failed]{
  background:var(--ink);
  box-shadow:inset 0 0 0 1px var(--line2);
}

/* ── motion ────────────────────────────────────────────────────────────────
   The sky itself is not animation in the sense this query means — it is the
   content, and stopping it would leave a still photograph of a rotating sky
   that is wrong within a minute. What IS suppressed is the idle drift, which
   is decoration. js/sky3d.js reads this. */
@media (prefers-reduced-motion:reduce){
  /* carries: .sky-first .sky-read .name, .sky-first .sky-read .detail */
  .sky-first .sky-read{backdrop-filter:none;background:var(--panel);}
}
