/* ─────────────────────────────────────────────────────────────────────────
   surfaces/almanac/kit.css — the component kit for Prahar, Umbra and Yuti.

   Ether, 2026-08-13: "do a design revamp on Prahar, Overhead, Umbra and Yuti
   … deploy uiverse heavily on each one of them … use what is already existent
   to tweak it to match our design language, and if anything needs to be
   created, use the design elements to create something that goes with our
   style language."

   ── WHAT THIS FILE IS, AND WHAT IT IS NOT ─────────────────────────────────
   It is NOT a paste of uiverse components. Every element in the library is
   authored against a fixed palette, a fixed radius and a fixed size, and this
   network has none of those — it has four skins, one `--radius`, and a
   `check_css.py` that fails the build on a frozen hex. So what crosses over is
   the TECHNIQUE, re-expressed in tokens. Each block below names the element it
   came from by its uiverse.io URL, which is the citation `./uv sourced` reads
   back: a component file with no source on it is hand-authoring nobody
   decided on (I1390), and this file is here to be checkable.

   Fourteen uiverse components had already been taken into `product/app` when
   this was written, and ZERO into the surfaces. That asymmetry is the whole
   reason this exists: Prahar, Umbra and Yuti were built with shell furniture
   and nothing else, so every control on them is a rectangle with a hairline.
   They are correct and they are flat.

   ── THE PREFIX IS `uv-` AND IT MEANS SOMETHING ────────────────────────────
   `al-`, `ec-` and `ma-` are the three surfaces' own geometry — a limb rail,
   an umbral diagram, a kuta card — and those are ours. `uv-` is the borrowed
   layer: a button, a field, a rail, a tooltip, a switch. When the shell grows
   a real component set these lift out of here wholesale, and the prefix is how
   you find them.

   ── MOTION IS DECLARED HERE BEFORE ANYTHING IS IMPORTED ───────────────────
   I1397, measured 2026-08-12 across the app: twelve distinct cubic-beziers,
   nine used exactly once, twenty durations, and zero motion custom properties
   — because every imported component carries its own timing opinion and
   fourteen imports had already produced twelve curves. That item's ruling is
   the precondition on this one: DECLARE THE MOTION SCALE BEFORE IMPORTING
   ANYTHING ELSE. So the scale is the first thing in this file and every
   transition below reads it. A bare `cubic-bezier(…)` or a bare `220ms` in
   this file is a bug, the same way a bare `#RRGGBB` is.

   The shell already governs three durations — `--mo-skin`, `--mo-swap`,
   `--mo-sheet` — and those are PAGE-level: a palette dissolve, a soft
   navigation, a sheet rising. Nothing here redeclares them. What is missing is
   the CONTROL-level scale, which is what a component kit spends.

   ── WHERE THE SCALE BELONGS, AND WHERE IT NOW IS ──────────────────────────
   "THE SCALE" and "THE ATMOSPHERE" are network-wide and their home is
   `surfaces/_shell/shell.css`, beside `--mo-skin`. They were written here, and
   verbatim again in `surfaces/skymap/kit.css`, because `surfaces/_shell` was
   held by another wave (I1470) when this file was written — two copies of one
   token set, which is a drift waiting to happen and was filed as such.

   MOVED 2026-08-13 (I1523). Both copies are gone and the shell declares them
   once. Every rule below reads them unchanged; the sentence above about
   declaring the scale before importing anything is still the rule, it is just
   obeyed one file earlier now.

   ── EVERY COLOUR IS A TOKEN, SAME AS EVERY OTHER SHEET ────────────────────
       python3 surfaces/_shell/check_css.py
   before committing anything below. The `carries:` convention (I446/I472) is
   used wherever a rule paints a ground that some other rule's text sits on.
   ───────────────────────────────────────────────────────────────────────── */

/* ══ THE SCALE AND THE ATMOSPHERE LIVE IN THE SHELL NOW ════════════════════
   Moved 2026-08-13 (I1523). `--dur-tap/-move/-enter/-sweep`,
   `--ease-out/-move/-press`, `--rise-1/-2/-3`, `--edge-lit`, `--edge-grad`,
   their day values, their Plain overrides and the reduced-motion clamp are
   declared once in `surfaces/_shell/shell.css`, beside `--mo-skin`.

   They were HERE and identical in the other kit at the same time, because
   `_shell` was held when I1521 landed. Every rule below still reads them
   exactly as it did; nothing about this sheet's behaviour changed. If a token
   below resolves to nothing, the shell did not load — and on this network a
   kit without its shell is a page with no palette either, so it will not be a
   subtle failure.
   ─────────────────────────────────────────────────────────────────────── */


/* ══ 1 · THE SURFACE ═══════════════════════════════════════════════════════
   `.uv-card` replaces "panel + 1px --line" everywhere it is worth replacing.
   What it adds is a hairline that is a GRADIENT rather than a flat rule: light
   at the top-left, dissolving to the panel by the bottom-right, so a card
   reads as lit rather than as outlined.

   THE TECHNIQUE IS THE TWO-LAYER BACKGROUND CLIP, not `border-image`.
   `abbos97_wise-jellyfish-49` uses `border-image: linear-gradient(…)` with
   `border-image-slice: 1`, which works and CANNOT FOLLOW A BORDER RADIUS —
   the corners go square the moment you round it, which on a sheet whose every
   surface is `var(--radius)` is the whole game.
       https://uiverse.io/abbos97/wise-jellyfish-49  (MIT)

   So: a transparent border, the panel painted to the padding box, the gradient
   painted to the border box. The gradient shows only where the border is, and
   it follows the radius exactly because both layers are clipped by the same
   rounded box. Supported since forever — `background-clip` is not the part
   that needs an iOS check.

   THE FALLBACK IS DECLARED FIRST and it is a real one: `background:var(--panel)`
   with a `--line` border is the card this network already ships. A browser
   that drops the multi-layer line keeps it and loses nothing but the light. */
.uv-card{
  position:relative;
  padding:16px;
  border:1px solid var(--line);
  border-radius:var(--radius);
  background:var(--panel);
  box-shadow:var(--rise-1);
}
.uv-card{
  border-color:transparent;
  background:
    linear-gradient(var(--panel),var(--panel)) padding-box,
    var(--edge-grad) border-box;
}

/* Raised — for the one block on a page that is answering the question. */
.uv-card--lift{box-shadow:var(--rise-2),var(--edge-lit);}

/* Recessed — a well, for things that are INSIDE something else: a readout
   inside the status block, a table inside a card. The inset direction is what
   `horrible-rabbit-39` is doing and it is the honest opposite of a rise. */
.uv-card--well{
  background:var(--ink);
  border-color:var(--line);
  box-shadow:inset 0 1px 3px rgb(0 0 0 / .22);
}

/* ══ 2 · THE AURA ══════════════════════════════════════════════════════════
   One card per page may glow. This is the technique from
   `htwarriors108_little-cat-84`: the same gradient painted twice, once as the
   frame and once behind it through a heavy blur, so the frame appears to be
   emitting rather than to have a shadow.
       https://uiverse.io/htwarriors108/little-cat-84  (MIT)

   WHAT CHANGED IN THE CROSSING, and it is most of the component. The original
   glows at full strength permanently, in yellow-to-magenta, at 30px blur, on
   every card in the grid. Three things are wrong with that here:

     · AMBIENT MOTION AND AMBIENT LIGHT ARE THE SAME RULE. "Response stays,
       ambient goes" is why `./uv` scores a component down for looping at
       rest; a card that is shouting before anybody has touched it is the
       static version of the same mistake. So the aura sits at a low resting
       strength and comes UP on hover and focus-within. It is feedback.
     · The hue is the palette's. `--moon` into `--amber` in a dark skin is the
       moonlight-into-lamplight axis this whole product is drawn on, and in a
       light skin both tokens invert to deep tones, so the glow becomes a soft
       shadow rather than a neon smear. That is the correct behaviour and it
       is free — it is what happens when the colours are tokens.
     · It is one card, not every card.

   `filter:blur()` on a positioned pseudo-element is composited on iOS and
   costs nothing at rest; the transition is on `opacity` alone, never on the
   blur radius, which is the expensive one.

   ── THE ONE THING THAT MAKES OR BREAKS IT: NO `isolation:isolate` ─────────
   This was written with `isolation:isolate` on the host first, and the result
   was a blurred gradient lying ON TOP of the card, washing out the umbral
   diagram it was supposed to be lighting. The painting order is the reason and
   it is worth stating so nobody adds it back:

     A negative-`z-index` child paints AFTER the background of its stacking
     context and BEFORE that context's in-flow content. So if the host is a
     stacking context, `z-index:-1` puts the glow behind the text and IN FRONT
     OF THE HOST'S OWN BACKGROUND — inside the card, not behind it.

   With no stacking context on the host, the pseudo-element escapes to the
   nearest one — the page — and paints behind the card's background, which is
   opaque. The blur then shows only where it extends past the card. That is a
   halo. It is also exactly what the uiverse original does and why its card
   carries no background of its own.

   TWO CONSEQUENCES FOR CALLERS, both real:
     · THE HOST MUST NOT BE A STACKING CONTEXT. No `transform`, no `opacity`
       below 1, no `filter`, no `isolation`, no `will-change` on the element
       carrying `.uv-aura`.
     · THE HOST MUST NOT CLIP. `overflow:hidden` cuts the halo off at the very
       edge it is meant to bleed past. `.sh-card` satisfies both; Prahar's
       `.al-now` satisfies neither, which is why it is not used there. */
.uv-aura{position:relative;}
.uv-aura::before{
  content:"";position:absolute;z-index:-1;inset:-1px;
  border-radius:inherit;
  background:linear-gradient(150deg,
    color-mix(in srgb,var(--moon) 70%,transparent),
    color-mix(in srgb,var(--amber) 55%,transparent));
  filter:blur(18px);
  opacity:.22;
  transition:opacity var(--dur-enter) var(--ease-out);
  pointer-events:none;
}
.uv-aura:hover::before,
.uv-aura:focus-within::before{opacity:.42;}

/* Plain has no atmospheric layer at all. */
:root[data-skin="7"] .uv-aura::before{display:none;}

/* ══ 3 · THE BUTTON ════════════════════════════════════════════════════════
   Three sources, and each supplies exactly one idea:

     · the press — `rrr63_empty-cow-96`, whose whole trick is that the OUTLINE
       moves rather than the button: `outline-offset` grows on hover so the
       ring steps away from the edge. It costs no layout and it reads on a
       touch screen, where a hover shadow does not.
           https://uiverse.io/rrr63/empty-cow-96  (MIT)
     · the sweep — `KINGFRESS_nice-deer-12`, a gradient wider than the button
       translated across it. Kept as a one-shot on hover, dropped as a resting
       state.
           https://uiverse.io/KINGFRESS/nice-deer-12  (MIT)
     · the glow — `mrhyddenn_moody-badger-62`, four stacked box-shadows in the
       button's own colour. Its resting state is a 25px halo on every button
       on the page; here the halo IS the hover, which is what makes it mean
       something.
           https://uiverse.io/mrhyddenn/moody-badger-62  (MIT)

   THE SHELL ALREADY HAS A BUTTON and this does not replace it — `.uv-btn` is
   opt-in, for the three or four controls per surface that are actually the
   point of the page. Everything else keeps the shell's, which is correct and
   quiet and should stay that way. A page where every control is the loudest
   control has no loudest control. */
/* carries: .uv-btn */
.uv-btn{
  position:relative;overflow:hidden;
  display:inline-flex;align-items:center;justify-content:center;gap:8px;
  padding:11px 20px;
  font-family:var(--mono);font-size:var(--t-sm);
  letter-spacing:.08em;
  color:var(--text);
  background:var(--panel2);
  border:1px solid var(--line2);
  border-radius:calc(var(--radius) * 2.4);
  cursor:pointer;
  outline:1px solid transparent;
  outline-offset:0;
  box-shadow:var(--rise-1),var(--edge-lit);
  transition:
    outline-offset var(--dur-move) var(--ease-out),
    outline-color  var(--dur-move) var(--ease-out),
    border-color   var(--dur-move) var(--ease-out),
    box-shadow     var(--dur-move) var(--ease-out),
    transform      var(--dur-tap)  var(--ease-press);
}

/* The sweep. Sits above the ground and below the label, parked off the left
   edge, and crosses once per hover. `translate` only — nothing here paints on
   a property that would cost a layout. */
.uv-btn::after{
  content:"";position:absolute;inset:0;
  background:linear-gradient(100deg,
    transparent 20%,
    color-mix(in srgb,var(--moon) 22%,transparent) 50%,
    transparent 80%);
  transform:translateX(-100%);
  transition:transform var(--dur-sweep) var(--ease-move);
  pointer-events:none;
}
.uv-btn > *{position:relative;z-index:1;}

.uv-btn:hover{
  border-color:color-mix(in srgb,var(--moon) 45%,var(--line2));
  outline-color:color-mix(in srgb,var(--moon) 28%,transparent);
  outline-offset:3px;
  box-shadow:var(--rise-2),var(--edge-lit);
}
.uv-btn:hover::after{transform:translateX(100%);}

/* A real ring, not the sweep's ring. `:focus-visible` so a mouse press does
   not leave one behind — and it is declared AFTER :hover so a keyboard user
   who is also hovering still sees the focus state. */
.uv-btn:focus-visible{
  outline:2px solid var(--moon);
  outline-offset:3px;
}

.uv-btn:active{transform:translateY(1px) scale(.985);}

/* The one that is the point of the page. Moon ground, ink label — which
   inverts correctly on its own in the light skins, where `--moon` is a deep
   navy and `--ink` is near-white. */
/* carries: .uv-btn--go */
.uv-btn--go{
  color:var(--ink);
  background:var(--moon);
  border-color:var(--moon);
  font-weight:700;
}
.uv-btn--go:hover{
  background:color-mix(in srgb,var(--moon) 88%,var(--text));
  border-color:color-mix(in srgb,var(--moon) 88%,var(--text));
  outline-color:color-mix(in srgb,var(--moon) 34%,transparent);
}
.uv-btn--go::after{
  background:linear-gradient(100deg,
    transparent 20%,
    color-mix(in srgb,var(--ink) 26%,transparent) 50%,
    transparent 80%);
}

/* Quiet — a text control that still wants the press and the ring. */
.uv-btn--ghost{
  background:transparent;
  border-color:var(--line);
  color:var(--muted);
  box-shadow:none;
}
.uv-btn--ghost:hover{color:var(--text);box-shadow:none;}

.uv-btn[disabled]{opacity:.45;cursor:not-allowed;}
.uv-btn[disabled]:hover{outline-offset:0;box-shadow:var(--rise-1);transform:none;}
.uv-btn[disabled]:hover::after{transform:translateX(-100%);}

/* ══ 4 · THE FIELD ═════════════════════════════════════════════════════════
   Yuti asks for two full birth charts and Prahar asks for a city, and both
   were doing it with a bare input and a mono label stacked above it. This is
   the floating-label field from `adamgiebl_ordinary-impala-4` — already taken
   into the app once, for the date panel, at fit 99 — with the underline-grow
   from `saidbl1_little-eagle-84`.
       https://uiverse.io/adamgiebl/ordinary-impala-4  (MIT)
       https://uiverse.io/saidbl1/little-eagle-84  (MIT)

   THE LABEL IS A REAL <label> AND THE INPUT KEEPS ITS PLACEHOLDER, which is
   the part uiverse fields habitually get wrong: most of them fake the label
   with a <span> and drive it off `:placeholder-shown`, so a screen reader gets
   an unlabelled box. Here the <label> is associated properly and the resting
   position is a visual choice on top of correct markup. `:placeholder-shown`
   decides only WHERE it sits.

   THE PLACEHOLDER MUST BE A SINGLE SPACE (`placeholder=" "`) for the resting
   state to work, and that is stated here because it is invisible in the markup
   and silently breaks the whole effect if somebody tidies it away. */
.uv-field{position:relative;display:block;margin:0 0 18px;}

.uv-field input,
.uv-field select,
.uv-field textarea{
  width:100%;
  font-family:var(--sans);font-size:var(--t-md);
  padding:20px 12px 8px;
  color:var(--text);
  background:var(--ink);
  border:1px solid var(--line2);
  border-radius:var(--radius);
  outline:none;
  transition:
    border-color var(--dur-move) var(--ease-out),
    box-shadow   var(--dur-move) var(--ease-out),
    background   var(--dur-move) var(--ease-out);
}
.uv-field input::placeholder,
.uv-field textarea::placeholder{color:transparent;}

.uv-field > span{
  position:absolute;left:13px;top:14px;
  font-family:var(--mono);font-size:var(--t-sm);
  letter-spacing:.04em;
  color:var(--faint);
  pointer-events:none;
  transform-origin:left top;
  transition:transform var(--dur-move) var(--ease-out),
             color     var(--dur-move) var(--ease-out);
}
.uv-field input:not(:placeholder-shown) + span,
.uv-field textarea:not(:placeholder-shown) + span,
.uv-field select + span,
.uv-field input:focus + span,
.uv-field textarea:focus + span{
  transform:translateY(-9px) scale(.78);
  letter-spacing:.14em;
}
.uv-field input:focus + span,
.uv-field textarea:focus + span,
.uv-field select:focus + span{color:var(--moon-deep);}

.uv-field input:focus,
.uv-field select:focus,
.uv-field textarea:focus{
  border-color:var(--moon-deep);
  box-shadow:0 0 0 3px color-mix(in srgb,var(--moon) 18%,transparent);
}
.uv-field input:hover:not(:focus),
.uv-field select:hover:not(:focus),
.uv-field textarea:hover:not(:focus){border-color:var(--line2);}

/* A field that is wrong says so on the border AND in the message under it —
   never on the border alone, which is the same rule the choghadiya tints
   follow: quality is never colour alone. */
.uv-field.is-bad input,
.uv-field.is-bad select{border-color:var(--rose);}
.uv-field-msg{
  display:block;margin:5px 2px 0;
  font-size:var(--t-xs);color:var(--rose);
}

/* ══ 5 · THE RAIL ══════════════════════════════════════════════════════════
   Prahar's limb rail, Yuti's eight kuta bars and Umbra's magnitude are three
   drawings of the same thing: a fraction, with a position in it. The surfaces
   each drew their own flat two-tone bar.

   WHAT THE KIT ADDS IS THE HEAD. A fill that stops dead reads as a bar chart;
   a fill with a brighter cap on its leading edge reads as a thing in motion
   that has got this far — which for a tithi that is 34% elapsed is the actual
   fact. The gradient-into-a-cap is `htwarriors108_proud-fish-6`'s edge
   treatment applied along one axis.
       https://uiverse.io/htwarriors108/proud-fish-6  (MIT)

   `--uv-at` is the one number a caller sets, as a percentage. Nothing here
   animates on its own; the fill transitions when the number changes, which is
   response and not ambience. */
.uv-rail{
  --uv-at:0%;
  position:relative;
  height:4px;border-radius:999px;
  background:var(--line);
  overflow:hidden;
}
.uv-rail::before{
  content:"";position:absolute;inset:0 auto 0 0;
  width:var(--uv-at);
  border-radius:inherit;
  background:linear-gradient(90deg,
    color-mix(in srgb,var(--moon-deep) 70%,var(--line)),
    var(--moon));
  transition:width var(--dur-enter) var(--ease-out);
}
/* The cap. `box-shadow` rather than a second element, so the head glows into
   the empty track without painting a box on top of it. */
.uv-rail::after{
  content:"";position:absolute;top:50%;left:var(--uv-at);
  width:3px;height:3px;margin:-1.5px 0 0 -1.5px;
  border-radius:999px;
  background:var(--moon);
  box-shadow:0 0 6px 1px color-mix(in srgb,var(--moon) 60%,transparent);
  transition:left var(--dur-enter) var(--ease-out);
}
.uv-rail--done::after{display:none;}

/* Graded — for a kuta that scored 3 of 4, where the fraction has a verdict.
   The word is always in the cell too; this is reinforcement. */
.uv-rail--good::before{background:linear-gradient(90deg,
  color-mix(in srgb,var(--green) 55%,var(--line)),var(--green));}
.uv-rail--good::after{background:var(--green);
  box-shadow:0 0 6px 1px color-mix(in srgb,var(--green) 60%,transparent);}
.uv-rail--warn::before{background:linear-gradient(90deg,
  color-mix(in srgb,var(--amber) 55%,var(--line)),var(--amber));}
.uv-rail--warn::after{background:var(--amber);
  box-shadow:0 0 6px 1px color-mix(in srgb,var(--amber) 60%,transparent);}
.uv-rail--bad::before{background:linear-gradient(90deg,
  color-mix(in srgb,var(--rose) 55%,var(--line)),var(--rose));}
.uv-rail--bad::after{background:var(--rose);
  box-shadow:0 0 6px 1px color-mix(in srgb,var(--rose) 60%,transparent);}

/* ══ 6 · THE DRAW ══════════════════════════════════════════════════════════
   `xopc333_soft-shrimp-30` retracts a four-line SVG border on hover, so the
   frame appears to be drawn on. It is the nicest idea in the Cards category
   and it ships 1.2 KB of SVG and four `stroke-dasharray` lines to do it.
       https://uiverse.io/xopc333/soft-shrimp-30  (MIT)

   THE SVG IS NOT NEEDED. Two pseudo-elements with `transform: scaleX/scaleY`
   and a `transform-origin` draw the same four lines, cost no markup, and
   inherit the border radius — which the SVG version cannot. What is lost is
   the original's ability to run each of the four edges to a different length;
   what is gained is that a caller adds one class.

   THIS IS THE HOVER TREATMENT FOR A GRID OF CARDS — Yuti's eight kutas, the
   calendar's festival list — where a shadow on hover would make the whole
   grid twitch. A line drawn along two edges is quieter and more precise. */
.uv-draw{position:relative;}
.uv-draw::before,
.uv-draw::after{
  content:"";position:absolute;
  background:var(--moon);
  opacity:0;
  transition:transform var(--dur-sweep) var(--ease-out),
             opacity   var(--dur-move)  var(--ease-out);
  pointer-events:none;
}
/* top edge, drawn left to right */
.uv-draw::before{
  inset:0 0 auto 0;height:1px;
  transform:scaleX(0);transform-origin:left center;
}
/* left edge, drawn top to bottom */
.uv-draw::after{
  inset:0 auto 0 0;width:1px;
  transform:scaleY(0);transform-origin:center top;
}
.uv-draw:hover::before,
.uv-draw:focus-within::before{transform:scaleX(1);opacity:.7;}
.uv-draw:hover::after,
.uv-draw:focus-within::after{transform:scaleY(1);opacity:.7;}

/* ══ 7 · THE SWITCH ════════════════════════════════════════════════════════
   260 toggles in the library and all but a handful are `input[type=checkbox]`
   hidden behind a `<span class="slider">`. I1390's rule is that the markup
   becomes the app's own semantics, and this network's switch is
   `<button role="switch" aria-checked>`. So what crosses over is the SHAPE —
   `adamgiebl_grumpy-moth-36`'s proportions and its knob travel — driven off
   `aria-checked` instead of `:checked`.
       https://uiverse.io/adamgiebl/grumpy-moth-36  (MIT)

   Reading the state off the ARIA attribute rather than a hidden input means
   the visual state and the announced state cannot disagree. That is not a
   nicety: a slider styled off `:checked` while `aria-checked` is stale is a
   control that lies to a screen reader and looks perfect. */
.uv-switch{
  position:relative;flex:none;
  width:42px;height:24px;padding:0;
  border:1px solid var(--line2);
  border-radius:999px;
  background:var(--ink);
  cursor:pointer;
  transition:background var(--dur-move) var(--ease-out),
             border-color var(--dur-move) var(--ease-out);
}
.uv-switch::after{
  content:"";position:absolute;top:2px;left:2px;
  width:18px;height:18px;border-radius:999px;
  background:var(--muted);
  transition:transform var(--dur-move) var(--ease-press),
             background var(--dur-move) var(--ease-out);
}
.uv-switch[aria-checked="true"]{
  background:color-mix(in srgb,var(--moon) 30%,var(--ink));
  border-color:var(--moon-deep);
}
.uv-switch[aria-checked="true"]::after{
  transform:translateX(18px);
  background:var(--moon);
}
.uv-switch:focus-visible{outline:2px solid var(--moon);outline-offset:3px;}
.uv-switch:active::after{width:22px;}

/* ══ 8 · THE TOOLTIP ═══════════════════════════════════════════════════════
   `title=` does not exist on a touch screen — I1398 counted thirty-nine pieces
   of copy in the app delivered that way and unreachable on the floor device.
   The surfaces have the same habit. `G4b413l_dry-turtle-84` is the cleanest of
   the 62 tooltips: one pseudo-element, one transform, no wrapper div.
       https://uiverse.io/G4b413l/dry-turtle-84  (MIT)

   IT OPENS ON FOCUS AS WELL AS HOVER, which is the whole point and is what
   none of the 62 do. The trigger must be focusable — a <button> or a
   `tabindex="0"` <abbr> — so a tap reaches it. `content` comes from
   `data-tip`, never from `title`, so there is no native bubble racing it. */
.uv-tip{position:relative;}
.uv-tip::after{
  content:attr(data-tip);
  position:absolute;bottom:calc(100% + 8px);left:50%;
  z-index:5;
  width:max-content;max-width:min(260px,60vw);
  padding:7px 10px;
  font-family:var(--sans);font-size:var(--t-xs);line-height:1.45;
  letter-spacing:0;text-transform:none;
  color:var(--text);
  background:var(--panel2);
  border:1px solid var(--line2);
  border-radius:8px;
  box-shadow:var(--rise-2);
  opacity:0;
  transform:translate(-50%,4px);
  transition:opacity var(--dur-move) var(--ease-out),
             transform var(--dur-move) var(--ease-out);
  pointer-events:none;
}
.uv-tip:hover::after,
.uv-tip:focus-visible::after{opacity:1;transform:translate(-50%,0);}

/* ══ 9 · THE LOADER ════════════════════════════════════════════════════════
   The one place a loop at rest is not a loop at rest: a spinner is on screen
   only while something is genuinely running, and it stops because the work
   stopped. `Javierrocadev_hard-horse-30`'s two-ring construction, one arc per
   ring, counter-rotating.
       https://uiverse.io/Javierrocadev/hard-horse-30  (MIT)

   IT MUST BE REMOVED FROM THE DOM, not hidden, when the work ends — a
   `display:none` spinner keeps animating in some engines and costs a frame
   forever. Callers use `hidden`, which this file honours below. */
.uv-spin{
  display:inline-block;position:relative;
  width:22px;height:22px;
  border:2px solid color-mix(in srgb,var(--line2) 80%,transparent);
  border-top-color:var(--moon);
  border-radius:999px;
  animation:uv-turn 900ms linear infinite;
}
.uv-spin[hidden]{display:none;}
@keyframes uv-turn{to{transform:rotate(360deg);}}
@media (prefers-reduced-motion:reduce){
  .uv-spin{animation-duration:2400ms;}
}

/* ══ 10 · THE ARRIVAL ══════════════════════════════════════════════════════
   A result that replaces a form, a card that appears when a date is picked —
   these arrive, and until now they appeared. One class, applied by the code
   that inserts the node, and it plays once.

   NOT `@starting-style`, which is Safari 17.5 and above the floor. A plain
   keyframe on the element as it enters the document works everywhere and needs
   no capability check. */
.uv-in{animation:uv-rise var(--dur-enter) var(--ease-out) both;}
@keyframes uv-rise{
  from{opacity:0;transform:translateY(8px);}
  to{opacity:1;transform:none;}
}
/* A list that arrives in order. Four steps and then it stops — a stagger that
   keeps counting past the fold is a page that takes a second to settle. */
.uv-in:nth-child(2){animation-delay:40ms;}
.uv-in:nth-child(3){animation-delay:80ms;}
.uv-in:nth-child(4){animation-delay:120ms;}
@media (prefers-reduced-motion:reduce){
  .uv-in{animation:none;}
}

/* ══ 11 · THE SHELL FURNITURE, LIFTED ══════════════════════════════════════
   Everything above is opt-in: a class somebody has to remember to write. This
   block is the opposite, and it is where most of the revamp actually lands.

   `.sh-card` and `.sh-btn` are declared in `surfaces/_shell/shell.css` and
   they are on EVERY page of every surface — nine on Prahar's index, ten on
   Kundli, eight on Overhead's about, six built by matching.js. Adding
   `.uv-card` to each of those by hand would be sixty edits across thirteen
   files, and the sixty-first card somebody writes next month would not have
   it. Restyling the two shell classes from here does the whole surface at
   once and keeps doing it.

   THIS IS AN OVERRIDE AND IT IS THE SANCTIONED KIND. build.py links a
   surface's stylesheets AFTER shell.css precisely so a surface can override —
   the comment at build.py's `styles =` says so in as many words. What would
   NOT be sanctioned is redeclaring the shell's geometry: the padding, the
   margin, the radius and the type are all still the shell's, untouched. What
   is added here is depth, and only depth.

   WHY NOT JUST EDIT shell.css. Because it belongs there and it will go there.
   `surfaces/_shell` was held by another wave (I1470) when this was written,
   and a design change made inside somebody else's live claim is how I352
   happens. The same paragraph at the head of this file applies: lift it,
   delete this block, and the surfaces lose nothing. */

/* THE CARD. Same panel, same radius, same padding — a lit hairline instead of
   a flat one, and a shadow that says it is sitting on the page rather than
   printed into it. Two rules for the same reason `.uv-card` is two: the flat
   form is the fallback and survives on its own. */
.sh-card{box-shadow:var(--rise-1),var(--edge-lit);}
.sh-card{border-color:transparent;
  background:
    linear-gradient(var(--panel),var(--panel)) padding-box,
    var(--edge-grad) border-box;}

/* THE BUTTON. The shell's is a translucent pill with a blur behind it and a
   border that changes colour on hover — correct, quiet, and with no press
   state and no focus ring. All three of those are added here and none of its
   own decisions are touched.

   `outline-offset` IS THE HOVER, from `rrr63_empty-cow-96`, for the reason
   given at `.uv-btn`: it reads on a touch screen, where a hover shadow does
   not, and it costs no layout.
       https://uiverse.io/rrr63/empty-cow-96  (MIT)

   THE `backdrop-filter` STAYS THE SHELL'S, including the fact that it is
   unprefixed there. That is not this file's call to make — it is the shell's
   own decision on its own control, `:root[data-skin="7"] *` already turns it
   off globally, and adding a `-webkit-` line here would leave the network with
   the prefix in one file and not the other, which is worse than either. It is
   worth a look on the floor device and it is filed rather than fixed in
   passing. */
.sh-btn{
  outline:1px solid transparent;
  outline-offset:0;
  box-shadow:var(--rise-1);
  transition:
    outline-offset var(--dur-move) var(--ease-out),
    outline-color  var(--dur-move) var(--ease-out),
    border-color   var(--dur-move) var(--ease-out),
    background     var(--dur-move) var(--ease-out),
    box-shadow     var(--dur-move) var(--ease-out),
    transform      var(--dur-tap)  var(--ease-press);}
.sh-btn:hover{
  outline-color:color-mix(in srgb,var(--moon) 26%,transparent);
  outline-offset:3px;
  box-shadow:var(--rise-2);}
.sh-btn:focus-visible{outline:2px solid var(--moon);outline-offset:3px;}
.sh-btn:active{transform:translateY(1px) scale(.985);}

/* A disabled control does not respond, and until now it still grew a ring on
   hover because `[disabled]` only changed its opacity. */
.sh-btn[disabled]:hover{outline-offset:0;outline-color:transparent;
  box-shadow:var(--rise-1);}
.sh-btn[disabled]:active{transform:none;}

/* The primary pill is the loudest thing on any page it appears on, so it gets
   the sweep as well — the one from `KINGFRESS_nice-deer-12`, in ink over moon
   so it reads as a highlight travelling across a lit surface rather than as a
   shine on glass.
       https://uiverse.io/KINGFRESS/nice-deer-12  (MIT)

   `overflow:hidden` is added HERE and not on `.sh-btn`, because clipping every
   button on the network to add a sweep to three of them is a cost the other
   ninety-seven pay for nothing. */
.sh-btn.primary{position:relative;overflow:hidden;}
.sh-btn.primary::after{
  content:"";position:absolute;inset:0;
  background:linear-gradient(100deg,
    transparent 20%,
    color-mix(in srgb,var(--ink) 24%,transparent) 50%,
    transparent 80%);
  transform:translateX(-100%);
  transition:transform var(--dur-sweep) var(--ease-move);
  pointer-events:none;}
.sh-btn.primary:hover::after{transform:translateX(100%);}
.sh-btn.primary > *{position:relative;z-index:1;}

/* ── the details disclosure ────────────────────────────────────────────────
   `<details><summary>View options</summary>` is the one control on Prahar's
   index that is neither a card nor a button, and it had no hover state at all
   — a 44px-tall strip that gave no sign it could be pressed. The same three
   ideas as the button, at a quarter of the strength. */
details > summary{transition:color var(--dur-move) var(--ease-out);}
details > summary:hover{color:var(--text);}
details > summary:focus-visible{outline:2px solid var(--moon);outline-offset:3px;
  border-radius:6px;}
