/* NOVAL default skin: "Noval".
   Ported from the v2 prometheus theme (tag v2-final) per this folder's
   DESIGN.md: aurora bands, the parallax star field, the nova pulse on press,
   and the Outfit wordmark.

   Contract (Rebuild_Plan.md 6.6): no #id selectors, no position:fixed outside
   the two skin layers, no z-index above --z-skin-front, never sets
   --focus-ring. */

/* ── Self-hosted faces. No CDN at runtime. ─────────────────────────────── */

@font-face {
  font-family: "Outfit";
  src: url("./fonts/outfit-latin.woff2") format("woff2");
  font-weight: 100 900;
  font-display: swap;
}
@font-face {
  font-family: "JetBrains Mono";
  src: url("./fonts/jetbrains-mono-latin.woff2") format("woff2");
  font-weight: 100 800;
  font-display: swap;
}
@font-face {
  font-family: "Literata";
  src: url("./fonts/literata-latin.woff2") format("woff2");
  font-weight: 200 900;
  font-display: swap;
}

[data-skin="noval"] {
  --bg: #0c0c10;

  /* Translucent, not the opaque greys this skin shipped with (owner,
     2026-09-11: "use the semi-transparent black from the nav-bar instead of
     the grey colour"). DESIGN.md 226 already asked for this - "depth comes
     from layered translucency over the skin's background, not from heavy drop
     shadows" - and 61 requires --bg to be dark enough that the star field
     reads THROUGH a surface. Flat hexes could not do either: they buried the
     star field under every card and made a card a grey box.

     The ladder is opacity over the skin's own ground, so each step up is more
     solid rather than a different hue:

       --surface-1  78%  the nav bar's exact value - keep the two in step, see
                         `features/nav/nav.css`, which states the same mix and
                         explains why it does not read this token. Cards,
                         chips, the drawer, popovers. This is the colour the
                         owner pointed at.
       --surface-2  90%  recessed, per DESIGN.md 63 - input fields, rows, the
                         gutter. Deeper and more solid, so a field reads as a
                         well cut into the plane above it.
       --surface-3  92%  dialogs, menus and popovers, toasts, the reference
                         stack's top card - per DESIGN.md 64. These float over
                         the page rather than sitting in it, and most of them
                         carry no scrim, so they are nearly solid AND take
                         `--glass-filter-strong`: at 92% the 8% that shows
                         through is blurred into a wash rather than left as
                         readable words behind a menu label. The owner asked
                         for the restyling to reach the menus too
                         (2026-09-11); leaving them opaque grey would have
                         made them the last grey thing in the product.

     Measured against DESIGN.md 65 (--text >= 7:1, --text-muted >= 4.5:1) at
     the worst point in the skin - a bright star core under the peak of all
     four screen-blended aurora bands, composited RGB 198,207,246:

       78%   --text 10.57:1   --text-muted 5.02:1
       90%   --text 14.58:1   --text-muted 6.93:1
       92%   --text 15.24:1   --text-muted 7.24:1

     Both pass with room, and over the flat ground both are 17.46 and 8.30. */
  --surface-1: color-mix(in oklab, var(--bg) 78%, transparent);
  --surface-2: color-mix(in oklab, var(--bg) 90%, transparent);
  --surface-3: color-mix(in oklab, var(--bg) 92%, transparent);
  /* SOFTENED TO v2's INK (owner, 2026-10-05: "white text to heavy"). This was
     #eef2ff - very nearly pure white, and at display sizes that is what reads
     as heavy however light the weight is. The v2 sheet the owner approved
     runs its brightest ink at #e8e6f2, which is the same value with the glare
     taken off: warmer, a shade down, and still about 15:1 on this ground. */
  --text: #e8e6f2;
  --text-muted: #a2a8c3;
  --text-on-accent: #04141a;
  --border: rgb(174 190 255 / 0.13);

  /* THE SECTION RULE, separate from the box outline. Added 2026-09-16.
     Owner, same day: "I love the soft clean lines, section divider lines and
     background aura and glows we have" - so the hairline idiom is the thing to
     build ON, and it was doing two jobs through one token.

     A hairline that OUTLINES a box and a hairline that DIVIDES stacked content
     are not the same value, because a long line reads heavier than a short one
     at the same alpha. The reference set varies it by exactly that: Linear's
     96px card edge is `rgba(255,255,255,0.08)` at 1.21:1, and its full-width
     containers drop to 0.05 at 1.09:1. Measured here against `--bg`: `--border`
     at 0.13 is 1.25:1, which is right for a card and too present drawn all the
     way across a screen.

     Landed at 0.09, which is 1.16:1, after looking at 0.07 (1.11:1) on the
     chapter route and judging it a step too far: the owner said he LIKES the
     section rules, so a divider that has to be hunted for is the wrong
     correction. Lighter than a card edge, still unmistakably a line.

     `--border` is UNCHANGED and keeps every call site it has. This is a new
     value beside it, not a retune of it. */
  --divider: rgb(174 190 255 / 0.09);

  /* THE EDGE LIGHT'S STRENGTH. Added 2026-09-19 with the option B port.
     Owner, 2026-09-16: "throw in some lighting/gradient effects at the edge to
     make the window feel a bit more alive."

     One number for the whole effect, so a skin can dial it — or set it to 0
     and have no edge light at all — without touching a gradient. The light
     itself is drawn from `--accent`, `--accent-2` and `--accent-3`, which is
     what DESIGN.md section 2 says the second and third accents are for: "the
     map, the aurora and gradient display type, not for CTAs". Nothing in the
     effect names a colour, so a third skin gets its own edge light for free. */
  --edge-glow: 0.13;

  --accent: #22c8e0;
  --accent-2: #a855f7;
  --accent-3: #ec4899;

  --danger: #ff6b70;
  --warning: #f5b544;
  --success: #45d69b;
  --info: #22c8e0;

  /* The map's original Phase 0 tint. Kept defined - DESIGN.md 12.6, never
     rename and never delete - and it is still what `--map-shape` falls back
     to. The rest of this skin's map set is in one block further down, beside
     `--map-bg`. `--map-link` moved there when its alpha came out of it. */
  --map-node: #8ea0c8;

  /* Outfit for display AND body. The wordmark is a geometric sans, not a
     serif: an earlier draft used Cinzel, which was wrong against the v2
     reference and against what the product should feel like. */
  --font-display: "Outfit", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-body: "Outfit", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, Consolas, monospace;
  --font-prose: "Literata", ui-serif, Georgia, "Iowan Old Style", serif;

  /* THE SHAPE LOCK, at the design system's own numbers (DESIGN.md section 6:
     6 / 10 / 16). This skin was running 6 / 12 / 18 - `--radius-sm` at the
     documented value and the two larger steps inflated by 20% and 12.5%.

     Section 6 permits a skin to "scale the whole set", and this was not a
     scale: it was the two big corners getting bigger while the small one
     stayed put, which flattens the ratio between a control and the panel it
     sits in and is the most reliable way to make a surface read as bubbly.
     The drawer panel, the dialog and the manuscript page all take
     `--radius-lg` and were therefore carrying the largest inflation in the
     product. Owner, 2026-09-16: "cards and drawer elements feel heavy and
     cartoonish/unrefined". Corrected to the documented set. */
  --radius-sm: 6px;
  --radius-md: 10px;
  --radius-lg: 16px;

  /* THE COVER CARD, SQUARE (2026-10-13). `DESIGN.md` 0.1 settles the shape of
     this skin's own language as square, and CLAUDE.md rule 11 makes user Home
     the reference every other surface is built to. Declared rather than left
     to `base.css`'s default so that this file states the whole shape system in
     one place - the next person reading for "what shape is a card here" finds
     the answer beside the other four radii instead of absent. */
  --card-radius: 0px;

  /* Outfit 200 with 0.06em tracking - the wordmark, per DESIGN.md D9. */
  --brand-weight: 200;
  --brand-tracking: 0.06em;

  /* DESIGN.md section 6, first sentence: "Depth comes from layered
     translucency over the skin's background, not from heavy drop shadows. A
     card is a slightly lighter, slightly more opaque plane with a hairline; it
     is not a floating box with a black shadow."

     `--shadow-2` was `0 10px 34px rgb(2 6 20 / 0.6)` over a second 10px drop -
     larger and half again as dark as the base fallback it overrides, on the
     one skin whose ground is nearly black. A 34px blur at 60% under a drawer
     paints a visible dark halo on the page around the panel, and a halo is
     what reads as a box sitting ON the screen rather than a plane in it.

     RESHAPED, not just dimmed, and the shape came from measuring four dark
     products (Linear, Raycast, Resend, Vercel - see agent_UI-fixes.md). Every
     drop shadow any of them puts under a genuinely floating panel has a SMALL
     offset and a WIDE blur: Linear's menu is `0 2px 32px rgba(0,0,0,0.25)`,
     Raycast's hero mock `0 30px 50px rgba(0,0,0,0.4)`. None of them pairs a
     large offset with a tight second drop, which is the combination that reads
     as a halo drawn on the page rather than as light falling past an object.

     So: offset 10px to 2px, blur 34px kept, opacity 0.6 to 0.5, and the tight
     `0 2px 10px` second layer deleted outright - it was the halo. The hairline
     ring is UNCHANGED and is now the thing doing the separating, which is what
     section 6 says depth comes from. Compared against the old value and against
     a reference-shaped one in a comp at 1440 before landing.

     `--shadow-1` keeps a drop because the surfaces that still use it genuinely
     lift: the manuscript page is separated by "the blur, the hairline and
     --shadow-1" (11.1). It came off the shared card rule in kit.css instead,
     which is where the weight actually was. */
  --shadow-1: 0 1px 2px rgb(2 6 20 / 0.4), 0 0 0 1px rgb(174 190 255 / 0.05);
  --shadow-2: 0 2px 34px rgb(2 6 20 / 0.5),
              0 0 0 1px rgb(174 190 255 / 0.08);

  /* ── Claimed from base.css, 2026-09-15 ────────────────────────────────
     The owner's rule: a look value is set HERE, and everything else reads
     it. Each of the values below was living only in `base.css`, which made
     it a product constant that no skin could restyle - and three of them
     (`--component-*`, `--map-bg`, and the pill radius that belongs to the set
     above) are listed as skin tokens in Rebuild_Plan.md 6.6, so the plan and
     the tree disagreed.

     Every value here is byte-identical to the fallback it replaces. This
     change makes them skinnable; it changes nothing on screen. `base.css`
     keeps its copy as the no-skin-loaded fallback, which is what stops the
     product rendering unstyled in the moment before a skin's sheet lands.

     Holo has not claimed these yet and falls back to base for now, which is
     the documented behaviour rather than a bug (DESIGN.md 12.6). */

  /* The card's ground, and the one surface in this skin that is nearly
     opaque. 97% measured against this skin's own star field: at 93% a point
     star is still visible through the card behind its text, at 97% it is not.
     The remaining 3% is what lets the aurora - blur(70px), very low frequency
     - still tint a card without any star punching through it. */
  --card-surface: color-mix(in oklab, var(--bg) 97%, transparent);

  /* The pill completes the radius set three lines up. It was the one member
     of the shape lock a skin could not scale. */
  --radius-pill: 999px;

  /* The manuscript page. THE surface of this product, and until now the one
     surface no skin could touch. Darker than --bg rather than lighter: a page
     of prose is a well you read into, and the star field behind it has to
     recede. The blur is what makes the translucency legible at all - without
     it the stars twinkle under live text. */
  --sheet-surface: color-mix(in oklab, color-mix(in oklab, black 10%, var(--bg)) 63%, transparent);
  --sheet-blur: blur(13px);

  /* The two glass depths. Holo already owned both; the default skin was
     inheriting them, which meant the brand skin had less say over its own
     look than the alternate did. */
  --glass-filter: blur(6px);
  --glass-filter-strong: blur(14px);

  /* Component type colours. Never the only signal for anything (DESIGN.md
     section 2) - every card carries the type in words as well - so a skin is
     free to re-hue the whole set to its own palette. */
  --component-article: #f472b6;
  --component-event: #fbbf24;
  --component-innovation: #c084fc;
  --component-tradition: #4ade80;
  --component-person: #38bdf8;
  --component-location: #94a3b8;
  --component-storyBlock: #2dd4bf;
  --component-custom: #fb923c;

  /* ── THE 3D MAP ──────────────────────────────────────────────────────
     Claimed 2026-09-23 against the map's skin contract. DESIGN.md section
     14 carries the grammar and the reasoning; `public/app/styles/map-tokens.css`
     carries the fallbacks. Everything not set here inherits, on purpose.

     The rule the whole set exists to serve: no map colour is written in the
     map's own code (owner, 2026-09-23). If the map needs a colour no token
     names, the token is the missing thing. */

  /* The map draws no ground of its own: the skin's star field shows through.
     A skin that wants an opaque map says so here. */
  --map-bg: transparent;

  /* THIS SKIN WANTS THE MOST SCRIM OF THE TWO. Its sky is the densest in the
     product - three parallax star layers, four screen-blended aurora bands at
     blur(70px), and the edge light - and a 1px wireframe drawn over the peak
     of all of that is unreadable. A radial pool of the skin's own ground,
     falling to zero well inside the frame so the stars come back at the rim
     and there is no seam. 0.45 was chosen against the aurora's brightest
     crossing, not against the flat ground. */
  --map-scrim-opacity: 0.45;
  --map-scrim-size: 78%;

  /* Ambient rotation, seconds per revolution. This skin is a night sky and
     the ruling asks for a SLOWLY spinning cube, so it sits at the base
     values rather than moving off them - claimed explicitly because a rate
     left to inherit is a rate nobody chose. Grid runs both faster; see its
     appendix. */
  --map-centre-spin: 40s;
  --map-shape-spin: 18s;
  --map-orbit-spin: 240s;

  /* The shape colour is this skin's existing `--map-node` periwinkle, which
     was already tuned against this ground. Cooler and lighter than
     `--text-muted`, so a wireframe reads as line rather than as haze. */
  --map-shape: #8ea0c8;
  --map-shape-opacity: 0.78;

  /* THE DENSITY LEVERS, and they are on the shapes rather than the lines.
     Measured on the seeded 27-component universe and 5x / 10x versions of it:
     the wireframes are ~90% of the ink at every scale and the connections
     are a tenth of it. See `map-tokens.css` for the table. This skin takes a
     slightly tighter budget than the base default because its sky is the
     busiest in the product, so the frame has less room to spare. */
  --map-shape-opacity-min: 0.34;
  --map-shape-budget: 80;

  /* The universe cube is brighter than a node because it is the anchor and
     the only object in the scene that is not a record. */
  --map-centre: #c6d2f2;
  --map-centre-opacity: 0.6;

  --map-dot-component-opacity: 0.72;
  --map-dot-component-glow: 0.3;
  --map-dot-story-glow: 1.7;

  /* The hue of the old `--map-link: rgb(120 200 240 / 0.24)`, with the alpha
     moved into its own scalar where a colour parser cannot drop it. */
  --map-link: #78c8f0;
  --map-link-opacity: 0.24;
  --map-link-opacity-min: 0.14;

  /* A COOL NEAR-WHITE, NOT THE ACCENT, and this is the one value here worth
     arguing about. The bracket has to keep meaning "the pointer is here"
     while the shape inside it wears any one of nine hover colours. This
     skin's accent is #22c8e0; Character is #38bdf8. An accent-coloured
     bracket around a hovered Character is one cyan blob with a cyan frame.
     Near-white is claimed by no component type and reads as chrome under all
     nine. */
  --map-bracket: #d8e6ff;
  --map-bracket-thickness: 1.5px;

  --map-label-component-opacity: 0.88;
  --map-label-component-opacity-far: 0.14;

  /* The proofreader's squiggle, painted through ::highlight(). Deliberately
     not --danger: a misspelling is not an error state, and the two must not
     be mistaken for each other at a glance. */
  --misspell: #ff57d8;
}

/* ── Background ground and vignette ────────────────────────────────────── */

[data-skin="noval"] .skin-layer-back {
  background:
    radial-gradient(ellipse 160% 60% at 50% -10%,
      #191926 0%, #0d0d14 42%, #08080c 68%, #050507 100%);
}

/* ── THE POLAR GRID (v2, 2026-10-02) ─────────────────────────────────────
 *
 * Concentric rings and radial spokes, centred a little above the middle of
 * the viewport. This is what carries "cosmic" in the rebuilt shell, and it
 * is the one element that was genuinely missing rather than mis-tuned: the
 * old backdrop was a single vertical gradient, so there was nothing behind a
 * card to separate it from, and nothing that said SPACE rather than dark.
 *
 * Drawn entirely from two repeating gradients, so there is no image to load,
 * nothing to decode, and it resizes for free - which is what makes it
 * affordable on a phone, where the owner's priority is load time.
 *
 * Both are MASKED to fade before they reach an edge. Without that the rings
 * terminate in a hard circle and read as a drawn object sitting on the page
 * instead of a structure the page is inside.
 *
 * `--grid-ring`, `--grid-spoke` and `--grid-rings` are skin tokens, so Grid
 * can swap them for its survey grid and Null can set them to `transparent`
 * and simply not have a sky. See this skin's CONTEXT.md.
 */
[data-skin="noval"] {
  --grid-ring: rgb(168 85 247 / 0.085);
  --grid-spoke: rgb(34 211 238 / 0.05);
  --grid-rings: 6;

  /* THE STAR FIELD'S WHOLE PALETTE (2026-10-02). Two colours, read by
     `skin-noval.js`'s `readPalette()`: the far layers and the two nearest
     ones. They are tokens rather than constants in the script so another skin
     can have a different sky without shipping its own star field - Grid can
     take these to a survey green, Null can take both to the same flat grey.

     They replace a palette that took `--text` for the field and `--accent`,
     `--accent-2` and `--accent-3` for a handful of haloed foreground stars.
     The halos are gone; see the comment on `readPalette()` for why. */
  --star: #ffffff;
  --star-near: #cfe9ff;
}

[data-skin="noval"] .skin-layer-back::before,
[data-skin="noval"] .skin-layer-back::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 46%;
  translate: -50% -50%;
  pointer-events: none;
}

[data-skin="noval"] .skin-layer-back::before {
  inline-size: 190vmax;
  block-size: 190vmax;
  background: repeating-radial-gradient(circle at center,
    transparent 0 calc(100% / var(--grid-rings) - 1px),
    var(--grid-ring) calc(100% / var(--grid-rings) - 1px) calc(100% / var(--grid-rings)));
  mask-image: radial-gradient(circle at center, #000 0%, #000 42%, transparent 72%);
  animation: nv-grid-turn 240s linear infinite;
}

[data-skin="noval"] .skin-layer-back::after {
  inline-size: 150vmax;
  block-size: 150vmax;
  background: repeating-conic-gradient(from 0deg at center,
    var(--grid-spoke) 0deg 0.12deg, transparent 0.12deg 15deg);
  mask-image: radial-gradient(circle at center, transparent 0%, #000 30%, transparent 70%);
  animation: nv-grid-turn 420s linear infinite reverse;
}

/* 240s and 420s: four and seven minutes for one turn. Slow enough that it is
   never motion you notice, present enough that the sky is not a photograph. */
@keyframes nv-grid-turn { to { rotate: 360deg; } }

@media (prefers-reduced-motion: reduce) {
  [data-skin="noval"] .skin-layer-back::before,
  [data-skin="noval"] .skin-layer-back::after { animation: none; }
}

/* ── THE SKY STANDS DOWN BEHIND THE 3D MAP (owner, 2026-10-08) ───────────
 *
 * `[data-sky="off"]` is set on the root by the screen that owns its own
 * background - today only `features/universe` while the map view is open.
 * It is the same bargain as `data-depth`: the app states a condition, and
 * each skin decides what to do about it.
 *
 * Why this skin honours it. The polar grid is concentric rings and radial
 * spokes, turning slowly. The map in front of it is orbits on inclined great
 * circles, also turning. Two sets of rings and radii rotating at different
 * rates do not layer - they read as one confused object, and the one the
 * reader came for is the one that loses.
 *
 * `display: none`, not `opacity: 0`: the grid is two 190vmax pseudo-elements
 * with masked repeating gradients and a running animation, and the map is the
 * most expensive screen in the product. Taking them out of the render tree
 * entirely is the point, not a side effect - a transparent layer still
 * composites, and on a phone that is the frame budget the map needs.
 *
 * NOT A TRANSITION, and `DESIGN.md` section 9 is satisfied rather than
 * broken: this is not an effect collapsing to nothing, it is a backdrop
 * changing state because the screen in front of it changed. There is nothing
 * for a reader to perceive as unfinished, and fading two full-viewport
 * gradient layers in and out on every view toggle is motion nobody asked
 * for in the one place that can least afford it.
 *
 * The STAR FIELD is untouched. It does not turn and it is not lines; the
 * owner's ask was specific; and `CLAUDE.md` records that the map draws no
 * stars so that this skin's sky shows through it. Taking the stars as well
 * would leave the map floating on flat ground. */
[data-skin="noval"][data-sky="off"] .skin-layer-back::before,
[data-skin="noval"][data-sky="off"] .skin-layer-back::after {
  display: none;
}

/* The writing surface is sacred: the grid stops with everything else. */
body[data-writing] .skin-layer-back::before,
body[data-writing] .skin-layer-back::after { animation-play-state: paused; }

/* ── No vignette ──────────────────────────────────────────────────────────

   There was a black wash up from the bottom of the viewport, and it is gone
   (owner's call, 2026-09-10). Three versions of it all showed a hard
   horizontal line across the background, and the measurements say why.

   Averaging every seventh column of a screenshot into one luminance per row,
   at 1650x1010:

     ground alone                    0 steps
     ground + aurora               146 steps, flat for up to 84 rows
     ground + aurora, no vignette  206 steps, flat for up to 39 rows

   Every step is ~1/255 - the smallest one there is - so no gradient here is
   wrong. It is 8-bit quantisation of the aurora's blur(70px), and the
   vignette's own very shallow ramp was stretching those rounding errors into
   the widest flat plateaus on the screen: a 400x140 patch of mid-dark
   background held just TWO distinct luminance levels, with the edge between
   them running across it as a line.

   A low-alpha fractal-noise dither did fix it (two levels became nine), but
   additive noise over near-black lifts the blacks - the same patch went from
   a mean of 4.2 to 7.5 - and `overlay` and `soft-light` both compress toward
   black exactly where the dither is needed. Not worth it for a layer whose
   only job was to darken an already-dark page.

   So the ground gradient and the aurora carry the background now, which is
   what the owner wanted looked at anyway. If a future change wants the
   bottom grounded again, dither it from the start and check a wide flat
   patch, not a thumbnail. */

/* ── Star field. Drawn on a canvas by the skin JS. ─────────────────────── */

[data-skin="noval"] .noval-stars {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* ── The edge light. Light gathering at the edges of the window. ────────
   Added 2026-09-19 with the option B port, on the owner's note.

   Drawn entirely from the three atmospheric accents through `--edge-glow`, so
   it carries no colour of its own and a third skin restyles it by choosing an
   accent. It sits in the back layer, under the app.

   NO `filter: blur()` and NO `mix-blend-mode`, and both are deliberate: the
   note above `.noval-aurora` records that a blurred, promoted, screen-blended
   layer was the single largest performance cost in this skin (25.6fps against
   48.5). A radial gradient is already soft and needs neither. The animation is
   OPACITY ONLY, so there is nothing to re-rasterise. */
[data-skin="noval"] .noval-edge {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(130% 55% at 50% -12%,
      color-mix(in oklab, var(--accent) calc(var(--edge-glow) * 100%), transparent) 0%, transparent 62%),
    radial-gradient(60% 55% at -10% 22%,
      color-mix(in oklab, var(--accent-2) calc(var(--edge-glow) * 76%), transparent) 0%, transparent 68%),
    radial-gradient(62% 60% at 110% 74%,
      color-mix(in oklab, var(--accent-3) calc(var(--edge-glow) * 68%), transparent) 0%, transparent 68%),
    radial-gradient(120% 40% at 50% 112%,
      color-mix(in oklab, var(--accent-2) calc(var(--edge-glow) * 46%), transparent) 0%, transparent 60%),
    /* ── THE DEAD MIDDLE THIRD, lit (2026-10-01) ────────────────────────
       Every other stop on this element is anchored OUTSIDE the viewport -
       50% -12%, -10% 22%, 110% 74%, 50% 112% - and all four aurora bands are
       pinned to the top with fixed pixel heights of 130 to 200px. Measured:
       below roughly 280px there was no atmosphere at all, and that is exactly
       where every card on every page sits.

       That is what made the product read as flat, and it is NOT the card's
       fault. `agent_UI-REFERENCES.md` grades Linear's surface treatment A at
       a card 1.00:1 against its ground with a 1.21:1 border and no shadow,
       and notes this product already matches it. A surface level with its
       ground is correct; there was simply nothing behind it to be level
       WITH.

       Why lighting the backdrop separates a card rather than washing it out:
       the surfaces are `color-mix(var(--bg) 78%, transparent)`, so a card
       shows 22% of whatever is behind it over 78% of the page colour, while
       the page beside it shows 100% of it. Light the ground and the card
       holds that light back - it reads as a pane over a lit field, which is
       the "glass AND lit, both" the owner settled, and the opposite of the
       opaque plate they disliked.

       Two stops, offset, so the middle has a gradient across it rather than
       one even wash - an even wash lifts the card and its surroundings by
       the same amount and separates nothing. */
    radial-gradient(85% 45% at 32% 46%,
      color-mix(in oklab, var(--accent-2) calc(var(--edge-glow) * 58%), transparent) 0%, transparent 72%),
    radial-gradient(75% 50% at 72% 62%,
      color-mix(in oklab, var(--accent) calc(var(--edge-glow) * 44%), transparent) 0%, transparent 70%);
  animation: nv-edge-breathe 34s ease-in-out infinite;
}

@keyframes nv-edge-breathe {
  0%, 100% { opacity: 0.82; }
  50%      { opacity: 1; }
}

/* The static twin sits at the MIDPOINT of the breath rather than at one end,
   so it reads as finished rather than as an effect switched off mid-frame. */
@media (prefers-reduced-motion: reduce) {
  [data-skin="noval"] .noval-edge { animation: none; opacity: 0.91; }
}
[data-motion="reduced"] [data-skin="noval"] .noval-edge,
:root[data-motion="reduced"] .noval-edge { animation: none; opacity: 0.91; }

/* ── Aurora. Four blurred bands drifting on independent cycles. ────────── */

/* No `will-change: transform` here, and its removal is the single largest
   performance change in this skin.

   `will-change` promotes each band to its own compositor layer. A promoted
   layer carrying a 72px blur and `mix-blend-mode: screen` makes the compositor
   read back the backdrop and re-composite a large blurred surface every frame,
   and there are four of them behind every screen in the product. Measured at
   1440x900 in headless Chrome: 25.6 fps with the promotion, 48.5 without. At
   2560x1440: 13.8 against 24.0. CPU throttling at 6x changed neither number,
   which is what says the cost is compositing rather than script.

   It is not a visual trade. With the bands frozen so the two are comparable,
   the promoted and unpromoted renders differ on 48% of pixels by a maximum of
   4 and a mean of 1.08 out of 255: rasterisation rounding inside a blurred
   gradient, invisible at any size.

   The next lever, if this ever needs to be faster: `filter: none` measures
   60.3 fps, so the blur is the remaining cost. Getting there means rebuilding
   the four bands as pre-blurred radial gradients that need no filter, which is
   a change to a protected effect and not one to make quietly. See
   agent_UI-fixes.md. */
[data-skin="noval"] .noval-aurora {
  position: absolute;
  filter: blur(var(--band-blur, 70px));
  mix-blend-mode: screen;
  pointer-events: none;
}

[data-skin="noval"] .noval-aurora--1 {
  top: -2%; left: 5%; width: 70%; height: 200px; --band-blur: 72px;
  background: linear-gradient(180deg, transparent 0%, rgb(6 182 212 / 0.26) 30%,
    rgb(6 182 212 / 0.38) 55%, rgb(6 182 212 / 0.20) 80%, transparent 100%);
  animation: nv-band1 22s ease-in-out infinite;
}
[data-skin="noval"] .noval-aurora--2 {
  top: 4%; left: 35%; width: 55%; height: 180px; --band-blur: 80px;
  background: linear-gradient(180deg, transparent 0%, rgb(168 85 247 / 0.28) 25%,
    rgb(236 72 153 / 0.30) 60%, rgb(168 85 247 / 0.18) 85%, transparent 100%);
  animation: nv-band2 30s ease-in-out infinite;
}
[data-skin="noval"] .noval-aurora--3 {
  top: -4%; left: 20%; width: 45%; height: 160px; --band-blur: 65px; opacity: 0.9;
  background: linear-gradient(180deg, transparent 0%, rgb(16 185 129 / 0.20) 35%,
    rgb(6 182 212 / 0.18) 65%, rgb(16 185 129 / 0.10) 85%, transparent 100%);
  animation: nv-band3 38s ease-in-out infinite reverse;
}
[data-skin="noval"] .noval-aurora--4 {
  top: 8%; left: 50%; width: 50%; height: 130px; --band-blur: 60px; opacity: 0.85;
  background: linear-gradient(180deg, transparent 0%, rgb(99 102 241 / 0.22) 30%,
    rgb(236 72 153 / 0.20) 65%, transparent 100%);
  animation: nv-band4 45s ease-in-out infinite;
}

@keyframes nv-band1 {
  0%   { transform: translateX(0%)   scaleY(1);    opacity: .85; }
  30%  { transform: translateX(-9%)  scaleY(1.18); opacity: 1;   }
  65%  { transform: translateX(7%)   scaleY(0.82); opacity: .75; }
  100% { transform: translateX(0%)   scaleY(1);    opacity: .85; }
}
@keyframes nv-band2 {
  0%   { transform: translateX(0%)   scaleY(1);    opacity: .80; }
  40%  { transform: translateX(12%)  scaleY(1.14); opacity: 1;   }
  75%  { transform: translateX(-8%)  scaleY(0.78); opacity: .70; }
  100% { transform: translateX(0%)   scaleY(1);    opacity: .80; }
}
@keyframes nv-band3 {
  0%   { transform: translateX(0%)   scaleY(1);    opacity: .70; }
  50%  { transform: translateX(-14%) scaleY(1.22); opacity: .90; }
  80%  { transform: translateX(9%)   scaleY(0.76); opacity: .60; }
  100% { transform: translateX(0%)   scaleY(1);    opacity: .70; }
}
@keyframes nv-band4 {
  0%   { transform: translateX(0%)   scaleY(1);    opacity: .65; }
  30%  { transform: translateX(8%)   scaleY(0.92); opacity: .80; }
  68%  { transform: translateX(-11%) scaleY(1.20); opacity: .55; }
  100% { transform: translateX(0%)   scaleY(1);    opacity: .65; }
}

/* ── Nova pulse on press. Rings expand from the pointer. ───────────────── */

[data-skin="noval"] .noval-nova {
  position: absolute;
  border-radius: 50%;
  border-style: solid;
  border-width: 9px;          /* v2 wrote `9` with no unit, so the ring never drew */
  width: 0;
  height: 0;
  background: transparent;
  filter: blur(9px);
  mix-blend-mode: screen;
  pointer-events: none;
  animation: nv-nova-ring var(--ring-duration, 1.2s) cubic-bezier(0.1, 0.4, 0.3, 1)
             var(--ring-delay, 0s) forwards;
}

[data-skin="noval"] .noval-flash {
  position: absolute;
  width: 4px;
  height: 4px;
  margin: -2px;
  border-radius: 50%;
  mix-blend-mode: screen;
  pointer-events: none;
  animation: nv-nova-flash 0.5s ease-out forwards;
}

@keyframes nv-nova-ring {
  0%   { width: 0; height: 0; margin: 0; opacity: 0.9; }
  100% { width: 150vmax; height: 150vmax; margin: -75vmax; opacity: 0; border-width: 1px; }
}

@keyframes nv-nova-flash {
  0%   { transform: scale(0.3); opacity: 0.85; }
  30%  { transform: scale(1);   opacity: 0.5; }
  100% { transform: scale(1.6); opacity: 0; }
}

/* ── The wordmark. Outfit 200 with the aurora gradient and a slow shimmer. */

[data-skin="noval"] .brand-title {
  font-family: var(--font-display);
  /* Declared as tokens on the skin below, so the universe crumb beside it can
     match the face and step one weight off it without hardcoding 200. */
  font-weight: var(--brand-weight);
  letter-spacing: var(--brand-tracking);
  background: linear-gradient(135deg,
    #22d3ee 0%, #a855f7 42%, #ec4899 72%, #f9a8d4 100%);
  background-size: 200% 200%;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  filter: drop-shadow(0 0 16px rgb(6 182 212 / 0.35))
          drop-shadow(0 0 32px rgb(168 85 247 / 0.18));
  animation: nv-title-shimmer 10s ease-in-out infinite;
}

@keyframes nv-title-shimmer {
  0%   { background-position: 0% 50%; }
  50%  { background-position: 100% 50%; }
  100% { background-position: 0% 50%; }
}

/* ── Reduced motion: every effect keeps a finished-looking static twin. ── */

@media (prefers-reduced-motion: reduce) {
  [data-skin="noval"] .noval-aurora,
  [data-skin="noval"] .brand-title { animation: none; }
  [data-skin="noval"] .noval-nova { display: none; }
}
[data-motion="reduced"] [data-skin="noval"] .noval-aurora,
:root[data-motion="reduced"] .noval-aurora,
:root[data-motion="reduced"] .brand-title { animation: none; }
:root[data-motion="reduced"] .noval-nova { display: none; }

/* The writing surface is sacred: ambient motion stops while the editor has
   focus, so nothing drifts in the corner of a writer's eye. */
body[data-writing] .noval-aurora,
body[data-writing] .noval-edge,
body[data-writing] .brand-title { animation-play-state: paused; }

/* ── DEPTH: the aura dims as you go in (THE THRESHOLD, 2026-10-01) ───────
 *
 * `core/router.js` puts `data-depth` on the root from the route table: 0 the
 * sky, 1 the hall, 2 the study, 3 the page. This skin's half of that is one
 * number, which is what `--edge-glow`'s own comment twenty lines up already
 * invited: *"One number for the whole effect, so a skin can dial it - or set
 * it to 0 and have no edge light at all - without touching a gradient."* The
 * hook was written before the idea that needed it.
 *
 * The step is 0.13 / 0.10 / 0.07 / 0.04 and it never reaches zero, because
 * the owner asked for the aura to be carried into the rebuild rather than
 * removed. At the manuscript it is a rumour of itself; it is still there.
 *
 * Specificity does the work: `--edge-glow` is declared on `[data-skin="noval"]`
 * at (0,1,0) and these are (0,2,0), so they win wherever they apply and the
 * base value stands on any surface the router has not given a depth.
 *
 * NOT TOUCHED HERE: the star field, the nova press pulse and `.brand-title`'s
 * shimmer, which are mechanism the owner protected and
 * `tests/e2e/supernoval-idcomponent.spec.js` pins. Depth dims the atmosphere; it
 * does not remove the idcomponent.
 */
[data-skin="noval"][data-depth="0"] { --edge-glow: 0.13; }
[data-skin="noval"][data-depth="1"] { --edge-glow: 0.10; }
[data-skin="noval"][data-depth="2"] { --edge-glow: 0.07; }
[data-skin="noval"][data-depth="3"] { --edge-glow: 0.04; }
