/*
 * Multiverse Mages — the prototype theme, in two.
 * Copyright (C) 2026 Ann Kelner
 * SPDX-License-Identifier: AGPL-3.0-or-later
 *
 * Two themes, not three directions. `ui/glow/` originally compared three
 * grounds — vellum with bloom, vellum with dark insets, and full dark — as
 * competing directions. Two of those are just light mode and dark mode, and
 * the hybrid was answering a question nobody asked. So:
 *
 *     light  vellum, and the god's light blooms downward
 *     dark   ink, and the god's light is emitted
 *
 * ## Why one token can mean opposite things
 *
 * On a dark ground "charged" is light emitted from the control. On vellum it
 * cannot be: a cyan fill bright enough to read measured 1.01:1 against the
 * latent state — a hue difference with no luminance at all, invisible to a
 * colourblind player and on any poor panel. So on paper, charge blooms
 * *downward*: ink drawn to the control rather than light coming off it.
 *
 * That divergence lives here and nowhere else. A prototype writes
 * `box-shadow: var(--glow-charged)` and gets a ring on vellum and a glow on
 * ink, without knowing which theme it is in. If component CSS ever needs to
 * ask, this file has failed.
 *
 * ## Three states, because the viewer has three
 *
 * An explicit choice stamps `data-theme` on the root element. The default —
 * "system" — stamps nothing, and only `prefers-color-scheme` separates the two.
 * So the bare `:root` carries the complete light palette, the media query
 * redefines tokens guarded by `:not([data-theme="light"])` so an explicit light
 * choice beats a dark OS, and `[data-theme="dark"]` redefines them again so the
 * toggle wins in the other direction. Never declare a colour only inside a
 * media or attribute block; it will not apply in the unstamped state.
 *
 * ## The rule the colours encode
 *
 * Cyan is the god — potential, affordance, and the moment of touch. The form
 * hues are the colour of magic that exists in the world. Nothing glows that the
 * god cannot currently do, so absence of light is the legality mask rather than
 * a red warning. See docs/design/interface-findings.md §6.1, and sound-design
 * §1.3 for the precedent that an allocation like this is a contract rather than
 * a palette.
 */

/* ============================================================ LIGHT — vellum */

:root {
  color-scheme: light;

  /* surfaces, paper-ward */
  --ground:   #E4DECD;
  --plate:    #F2EEE4;
  --panel:    #EFEADD;
  --sunk:     #D8D1BD;
  --edge:     #CBC3AE;
  --line:     #CBC3AE;
  --rule-hi:  #ABA189;

  /* ink */
  --ink:      #1C1913;
  --soft:     #4B4536;
  --faint:    #4E4A3A;

  /* the god. On paper these are dark: the light is drawn in, not given off. */
  --god:      #0B4B51;
  --god-lit:  #04454E;
  --god-deep: #B7DCDF;

  /* control states — the whole light/dark divergence, in six tokens */
  --ctl-charged-bg:     #0B5C66;
  --ctl-charged-fg:     #F2FBFB;
  --ctl-charged-border: #04454E;
  --ctl-latent-bg:      transparent;
  --ctl-latent-fg:      #5F5947;
  --ctl-latent-border:  #BDB49E;
  --ctl-denied-fg:      #635B48;
  --ctl-denied-border:  #BCB29A;

  /* "glow" on paper is a ring. There is no luminance left to spend upward. */
  --glow-charged: 0 0 0 3px color-mix(in srgb, #12B8C6 22%, transparent);
  --glow-strong:  0 0 0 4px color-mix(in srgb, #12B8C6 34%, transparent);
  --glow-soft:    0 0 0 2px color-mix(in srgb, #12B8C6 16%, transparent);
  --glow-form:    none;

  /* form hues, darkened to hold on paper */
  --animal:   #7A6B3E; --aquam:    #2B5E7A; --auram:    #6E7A82;
  --corpus:   #7A3E3E; --herbam:   #3E6B2B; --ignem:    #B85C2A;
  --imaginem: #6B5B8A; --mentem:   #1B5745; --terram:   #7A4322;
  --vim:      #5A5A6A; --umbra:    #3A3A50; --fatum:    #6A5A3A;
  --limen:    #4A3585; --nomen:    #2C4C7C;

  /* semantic. Separate from the accent on purpose. */
  --loss:   #8C2B19;
  --loss-bg:#F0DBD4;
  --warn:   #614503;
  --ok:     #15583F;
  --ruin:   #6F2F8C;
  --gift:   #614503;
  --summit: #614503;
  --foe:    #8C2B19;

  /* knowledge vessels */
  --mind:   #0B4B51;
  --book:   #614503;
  --lib:    #15583F;
  --palace: #6F2F8C;


  /* named states some prototypes use directly */
  --summit-lit: #6B4C05;
  --disp:   #15583F;
  --inter:  #8C2B19;
  --empty:  #C0B79F;
  --mine:   #15583F;
  --theirs: #8C2B19;
  --deny:   #8C2B19;

  /* The ground a scroll region fades its content into. Declared once, in the
     light block only: it points at --panel, which each theme redefines on the
     same :root, so the reference resolves to whichever theme is winning. A
     region sitting on a different surface overrides it locally. */
  --scroll-bg: var(--panel);

  --mono:  ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
  --sans:  ui-sans-serif, -apple-system, "Segoe UI", system-ui, sans-serif;
  --serif: "Hoefler Text", "Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua", Georgia, serif;
}

/* ============================================== DARK — ink, and light given off */

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;

    --ground:   #0A0D0F;
    --plate:    #12181A;
    --panel:    #0E1315;
    --sunk:     #141B1D;
    --edge:     #1D2729;
    --line:     #1D2729;
    --rule-hi:  #33474A;

    --ink:      #DDE7E8;
    --soft:     #94A3A5;
    --faint:    #8B9799;

    --god:      #12B8C6;
    --god-lit:  #6FF0FA;
    --god-deep: #06525C;

    --ctl-charged-bg:     color-mix(in srgb, #12B8C6 10%, #0A1013);
    --ctl-charged-fg:     #6FF0FA;
    --ctl-charged-border: #12B8C6;
    --ctl-latent-bg:      #0C1214;
    --ctl-latent-fg:      #6FB6BD;
    --ctl-latent-border:  #1F3E42;
    --ctl-denied-fg:      #93A0A2;
    --ctl-denied-border:  #2A3538;

    --glow-charged: 0 0 0 1px color-mix(in srgb, #12B8C6 28%, transparent),
                    0 0 16px -2px color-mix(in srgb, #12B8C6 58%, transparent);
    --glow-strong:  0 0 0 1px #12B8C6,
                    0 0 26px -1px color-mix(in srgb, #12B8C6 80%, transparent);
    --glow-soft:    0 0 12px -3px color-mix(in srgb, #12B8C6 60%, transparent);
    --glow-form:    0 0 13px -3px currentColor;

    --animal: #B8A866; --aquam:  #5BA0C8; --auram:    #9AAAB4;
    --corpus: #C87070; --herbam: #6BA84E; --ignem:    #E89050;
    --imaginem:#A494C4;--mentem: #46A98D; --terram:   #CC8450;
    --vim:    #9090A4; --umbra:  #7070A0; --fatum:    #B8A870;
    --limen:  #A692EC; --nomen:  #7099D8;

    --loss:   #DE7059;
    --loss-bg:#30150F;
    --warn:   #C9A034;
    --ok:     #4DBE97;
    --ruin:   #C88AE8;
    --gift:   #C9A034;
    --summit: #D7B45A;
    --foe:    #B4574A;

    --mind:   #6FF0FA;
    --book:   #C9A034;
    --lib:    #4DBE97;
    --palace: #C08BE8;

    --summit-lit: #FFE29B;
    --disp:   #4DBE97;
    --inter:  #DE7059;
    --empty:  #263436;
    --mine:   #4DBE97;
    --theirs: #DE7059;
    --deny:   #DE7059;
  }
}

/* The toggle has to win in the other direction too. Same values as the media
   block; a preprocessor would deduplicate these and a static file cannot. */
:root[data-theme="dark"] {
  color-scheme: dark;

  --ground:   #0A0D0F;
  --plate:    #12181A;
  --panel:    #0E1315;
  --sunk:     #141B1D;
  --edge:     #1D2729;
  --line:     #1D2729;
  --rule-hi:  #33474A;

  --ink:      #DDE7E8;
  --soft:     #94A3A5;
  --faint:    #8B9799;

  --god:      #12B8C6;
  --god-lit:  #6FF0FA;
  --god-deep: #06525C;

  --ctl-charged-bg:     color-mix(in srgb, #12B8C6 10%, #0A1013);
  --ctl-charged-fg:     #6FF0FA;
  --ctl-charged-border: #12B8C6;
  --ctl-latent-bg:      #0C1214;
  --ctl-latent-fg:      #6FB6BD;
  --ctl-latent-border:  #1F3E42;
  --ctl-denied-fg:      #93A0A2;
  --ctl-denied-border:  #2A3538;

  --glow-charged: 0 0 0 1px color-mix(in srgb, #12B8C6 28%, transparent),
                  0 0 16px -2px color-mix(in srgb, #12B8C6 58%, transparent);
  --glow-strong:  0 0 0 1px #12B8C6,
                  0 0 26px -1px color-mix(in srgb, #12B8C6 80%, transparent);
  --glow-soft:    0 0 12px -3px color-mix(in srgb, #12B8C6 60%, transparent);
  --glow-form:    0 0 13px -3px currentColor;

  --animal: #B8A866; --aquam:  #5BA0C8; --auram:    #9AAAB4;
  --corpus: #C87070; --herbam: #6BA84E; --ignem:    #E89050;
  --imaginem:#A494C4;--mentem: #46A98D; --terram:   #CC8450;
  --vim:    #9090A4; --umbra:  #7070A0; --fatum:    #B8A870;
  --limen:  #A692EC; --nomen:  #7099D8;

  --loss:   #DE7059;
  --loss-bg:#30150F;
  --warn:   #C9A034;
  --ok:     #4DBE97;
  --ruin:   #C88AE8;
  --gift:   #C9A034;
  --summit: #D7B45A;
  --foe:    #B4574A;

  --mind:   #6FF0FA;
  --book:   #C9A034;
  --lib:    #4DBE97;
  --palace: #C08BE8;

  --summit-lit: #FFE29B;
  --disp:   #4DBE97;
  --inter:  #DE7059;
  --empty:  #263436;
  --mine:   #4DBE97;
  --theirs: #DE7059;
  --deny:   #DE7059;
}

/* ================================================================ base */

* { box-sizing: border-box; }

body {
  margin: 0;
  /* Explicit, always. The host paints its own ground behind the page, so a
     transparent body silently borrows the other theme's background. */
  background: var(--ground);
  color: var(--ink);
  font: 15px/1.55 var(--sans);
  -webkit-font-smoothing: antialiased;
}

code, .mono { font-family: var(--mono); }

/* ===================================== a scroll region that admits it scrolls
 *
 * Four prototypes cap a list with `max-height` and `overflow-y:auto`, and on a
 * platform with overlay scrollbars the result is a row sliced through the
 * middle of its own sentence with nothing on screen saying why. Measured on
 * `ruleset/`, `targets/` and `knowledge/`: the last visible row was cut at
 * mid-x-height and read as a rendering fault rather than as "there is more".
 *
 * **The scrollbar cannot be the signal on this platform, and that was
 * measured rather than assumed.** With overlay scrollbars — the macOS default
 * of "show when scrolling", and what headless Chromium runs — `offsetWidth -
 * clientWidth` is 0 for a scrollable box styled with `scrollbar-width: thin`,
 * with `::-webkit-scrollbar { width }`, and with the `-webkit-appearance: none`
 * variant alike. All three draw nothing until the user already knows to
 * scroll. Only `scrollbar-gutter: stable` reserved anything, at 15px, and a
 * reserved lane with no thumb in it is not an affordance either.
 *
 * So the sticky fade is the load-bearing signal: it puts a soft edge where the
 * cut lands, which reads as "there is more" rather than as a rendering fault.
 * The gutter and the `::-webkit-scrollbar` rules stay because they cost
 * nothing and do carry on a platform whose scrollbars are laid out.
 *
 * The classic `background-attachment: local` shadow — visible only when there
 * is more, gone at the end — was tried first and does not work here: every one
 * of these lists paints an opaque `--sunk` on the rows themselves, so a
 * background layer on the container renders *behind* the content it is meant
 * to cover. The fade is therefore unconditional, which is its one dishonesty.
 *
 * A region on a surface other than `--panel` sets `--scroll-bg` itself.
 *
 * **Apply it only where the list is long enough for the cut to be the
 * problem.** CSS cannot ask whether there is anything past the edge, so the
 * fade paints over a short list too. Tried on `raid/`'s exposure lists and cast
 * log — three regions whose reference state is a two-line "nothing has happened
 * yet" — it faded the second line of that message to near-invisible, i.e. it
 * made an empty state harder to read in order to fix a problem those regions do
 * not have. Reverted there.
 */
.mm-scroll {
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-gutter: stable;
}
.mm-scroll::after {
  content: '';
  display: block;
  flex: none;
  position: sticky;
  bottom: -1px;
  height: 1.35rem;
  margin-top: -1.35rem;
  pointer-events: none;
  background: linear-gradient(transparent, var(--scroll-bg));
}
.mm-scroll::-webkit-scrollbar { width: 9px; }
.mm-scroll::-webkit-scrollbar-thumb {
  background: var(--rule-hi);
  border-radius: 9px;
  border: 3px solid transparent;
  background-clip: content-box;
}
.mm-scroll::-webkit-scrollbar-track { background: transparent; }

/* ================================ an essay is not a column of a control panel
 *
 * Every prototype ends with a prose section — "What operating these settles" —
 * capped at 74ch so the measure stays readable. In a 84rem page that leaves
 * the right half of the screen empty for six to eight hundred pixels, which
 * reads as a pane that failed to render rather than as a margin. Eight of the
 * eleven did this. Two columns keep the same measure and use the space; the
 * `break-inside` guard stops a paragraph being sawn across the gutter, which
 * is the one way multi-column prose gets worse rather than better.
 */
@media (min-width: 68rem) {
  .mm-essay { columns: 2; column-gap: 2.6rem; }
  .mm-essay > * { break-inside: avoid; }
  .mm-essay > h2, .mm-essay > h3 { column-span: all; }
}

/* ============================================== the god's controls, themed once */

.ctl {
  font: 12px var(--mono);
  letter-spacing: .05em;
  text-transform: uppercase;
  padding: .5rem .8rem;
  border-radius: 3px;
  cursor: pointer;
  border: 1px solid var(--ctl-latent-border);
  background: var(--ctl-latent-bg);
  color: var(--ctl-latent-fg);
  transition: box-shadow 160ms ease, background 160ms ease,
              border-color 160ms ease, color 160ms ease;
}
.ctl[data-s="charged"] {
  background: var(--ctl-charged-bg);
  color: var(--ctl-charged-fg);
  border-color: var(--ctl-charged-border);
  box-shadow: var(--glow-charged);
}
.ctl[data-s="charged"]:hover { box-shadow: var(--glow-strong); }
/* Denied is carried by the dashed edge and the absence of glow, never by
   dimming the label — `opacity` measured 2.42:1 on dark, under WCAG's 4.5. */
.ctl[data-s="denied"] {
  border-style: dashed;
  border-color: var(--ctl-denied-border);
  background: transparent;
  color: var(--ctl-denied-fg);
  box-shadow: none;
  cursor: not-allowed;
}
.ctl:focus-visible { outline: 2px solid var(--god); outline-offset: 2px; }

/* The touch: a flare that decays, never a steady state. Interventions resolve
   on the tick they are issued and the light should behave the same way. */
@keyframes mm-flare {
  from { box-shadow: var(--glow-strong); }
  to   { box-shadow: var(--glow-charged); }
}
.ctl.touched { animation: mm-flare 620ms ease-out; }

/* ==================================================== the theme control itself */

.mm-theme {
  display: inline-flex;
  gap: 2px;
  padding: 2px;
  border: 1px solid var(--edge);
  border-radius: 4px;
  background: var(--panel);
}
.mm-theme button {
  font: 10px var(--mono);
  letter-spacing: .1em;
  text-transform: uppercase;
  padding: .3rem .5rem;
  border: 0;
  border-radius: 2px;
  background: transparent;
  color: var(--faint);
  cursor: pointer;
}
.mm-theme button[aria-pressed="true"] {
  color: var(--ctl-charged-fg);
  background: var(--ctl-charged-bg);
}
.mm-theme button:focus-visible { outline: 2px solid var(--god); outline-offset: 1px; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}
