/* ─────────────────────────────────────────────────────────────────────────
   Rune reactions — the shared visual language for the five reaction runes.

   Loaded by pages/live.html (where reactions are made) and pages/vod.html
   (where a replay shows the ones a broadcast collected). It lives on its own
   rather than inside live.css because a five-colour palette kept in two files
   by hand is a palette that drifts; css/identity.css is the same pattern.

   ᚲ Fire   ember, and heat comes off it
   ᛁ Ice    glacier cyan, thin and spaced open like frost
   ᚦ Storm  a white core in a charged halo, flickers once on hover
   ᛉ Shield warding green, squared into a plate, the heaviest stroke
   ᚨ Raven  matte purple, the only one of the five that does not shine

   Each rune carries its essence in colour AND in one non-colour cue, because
   colour alone is not a channel everybody has, and because Elder Futhark is
   not legible to anyone who has not learned it. A member should be able to
   read the mood of a row without knowing a single rune's name.
   ───────────────────────────────────────────────────────────────────────── */

/* Elder Futhark, self-hosted. The runic block (U+16A0-16F8) is absent from
   most default system stacks, so the glyphs fell back to whatever the browser
   could find and rendered as hairline marks that carried no weight and no
   colour worth seeing. 6.5KB, subset to the runic range only. Self-hosted
   rather than pulled from Google Fonts the way dashboard.html still does it:
   these pages should not open a third-party connection for five characters. */
@font-face {
    font-family: 'MLNF Runic';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('../fonts/noto-sans-runic-400.woff2') format('woff2');
    unicode-range: U+16A0-16F8;
}

/* ── The five essences, per surface ────────────────────────────────────────
   Hues are lifted from the combo bursts already in js/transaction-effects.js
   (frost-giant fire #ff6a00 and ice #3ea8ff, odins-sight violet #6a4c9c) and
   pushed to clear 4.5:1 on the ground they actually sit on. The -fill tokens
   are the RESTING wash of your own chip; plain rgba because that is the one
   value that must never be missing.

   Surfaces, not just themes. Some rows paint their own ground and ignore the
   page theme entirely — a superchat is a gold-to-bronze gradient (light even
   in dark mode), a tip is dark green (dark even in light mode), and the
   replay's chat panel in pages/vod.html is hardcoded #1F2937. Each of those
   joins the palette that suits the surface it IS rather than the surface the
   page is, which is why they appear in the selector lists below instead of
   restating the same ten hexes. */
:root {
    --rune-fire: #C2410C;
    --rune-fire-fill: rgba(194, 65, 12, 0.14);
    --rune-ice: #0E7490;
    --rune-ice-fill: rgba(14, 116, 144, 0.13);
    --rune-storm: #4338CA;
    --rune-storm-fill: rgba(67, 56, 202, 0.13);
    --rune-shield: #15803D;
    --rune-shield-fill: rgba(21, 128, 61, 0.13);
    --rune-raven: #7E22CE;
    --rune-raven-fill: rgba(126, 34, 206, 0.13);
}

/* A dark ground, whether the page is dark or the row simply is. */
[data-theme="dark"],
.chat-message.tip-message,
.vod-chat-track {
    --rune-fire: #FF8A3D;
    --rune-fire-fill: rgba(255, 138, 61, 0.16);
    --rune-ice: #7DD3FC;
    --rune-ice-fill: rgba(125, 211, 252, 0.14);
    --rune-storm: #A5AEFB;
    --rune-storm-fill: rgba(165, 174, 251, 0.15);
    --rune-shield: #6EE7A8;
    --rune-shield-fill: rgba(110, 231, 168, 0.14);
    --rune-raven: #CE86FF;
    --rune-raven-fill: rgba(206, 134, 255, 0.15);
}

/* The tip row is itself green, so the board's warding green would vanish into
   it. Warded steel-white instead, and a count that reads on that ground. */
.chat-message.tip-message {
    --rune-count: rgba(248, 244, 227, 0.65);
    --rune-shield: #E2E8F0;
    --rune-shield-fill: rgba(226, 232, 240, 0.16);
}

/* Gold is darker than the light theme's panel, so the light set loses contrast
   on it; this is the light palette pushed one step down. The count goes near
   black for the same reason .superchat .chat-text already goes navy. */
.chat-message.superchat {
    --rune-count: rgba(26, 26, 26, 0.7);
    --rune-fire: #9A3412;
    --rune-fire-fill: rgba(154, 52, 18, 0.16);
    --rune-ice: #155E75;
    --rune-ice-fill: rgba(21, 94, 117, 0.15);
    --rune-storm: #3730A3;
    --rune-storm-fill: rgba(55, 48, 163, 0.15);
    --rune-shield: #14532D;
    --rune-shield-fill: rgba(20, 83, 45, 0.15);
    --rune-raven: #6B21A8;
    --rune-raven-fill: rgba(107, 33, 168, 0.15);
}

/* Rune reactions. The row is deliberately IRC-tight (see .chat-content
   above), so an always-visible reactions strip under every message
   would bloat a fast-moving log - :empty collapses it to zero height
   on the (common) case of a message nobody's reacted to yet.

   inline, not block (changed 2026-09-09). A block strip took a whole
   new line under every reacted message for two chips ~40px wide, which
   in a fast pane is most of a row spent on nothing. .chat-content is
   display:inline and .chat-msg-body is a plain block, so an inline
   strip flows straight on from the last word of the message and only
   wraps when the line is genuinely full. The same rule carries the tip
   and superchat rows, whose .chat-content is inline too.

   The strip is display:inline rather than inline-flex on purpose: an
   inline-flex box is atomic, so five chips would have to find room for
   all five at once and otherwise jump to a new line together. Plain
   inline lets each chip break like a word, which is the denser answer
   and the one that keeps a long reaction run beside its message. */
.chat-msg-reactions {
    display: inline;
    margin-left: 4px;
}

.chat-msg-reactions:empty {
    display: none;
}

/* No box at rest (2026-09-09, Al: "the outline box and the background
   should be more discrete, so the rune stands more on its own"). The
   first pass gave every chip a pill and a tinted ground, which put a
   second shape around a mark that is already a shape - five of them in
   a row read as buttons first and runes second. The chrome is now a
   hover affordance only, the way .chat-msg-reply-open and
   .chat-msg-react-open already behave in this pane: at rest there is
   the glyph in its own colour and a quiet count, nothing else. */
.chat-rune-chip {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 999px;
    /* Fallbacks because pages/vod.html does not load live.css. */
    color: var(--rune-count, var(--live-text-muted, var(--gray-400)));
    font-size: 0.66rem;
    font-weight: 600;
    padding: 0 3px;
    line-height: 1.4;
    margin-right: 4px;
    vertical-align: baseline;
    cursor: pointer;
    transition: background-color 0.12s, border-color 0.12s;
}

/* Runic strokes are thin - at the chip's own small font-size they
   read as almost nothing (verified with a screenshot, not assumed).
   With the pill gone the glyph is the whole chip, so it takes another
   step up; the count stays small and recessive beside it. */
.chat-rune-chip .rune-glyph {
    font-family: 'MLNF Runic', 'Noto Sans Runic', 'Segoe UI Symbol', serif;
    font-size: 1.05rem;
    font-weight: 700;
    line-height: 1;
    color: var(--live-text, var(--gray-200));
}

/* ── Reading a rune without knowing runes ──────────────────────────
   Elder Futhark is not legible to anyone who has not learned it, so a
   row of five identical grey glyphs told a member nothing about what
   anyone had said. Each rune now carries its essence in colour and in
   one non-colour cue, because colour alone is not a channel everybody
   has: ᚲ Fire glows, ᛁ Ice is thin and spaced open like frost, ᚦ Storm
   has a hard white core in a charged halo, ᛉ Shield squares off its
   corners into a plate and thickens its border, ᚨ Raven stays matte
   and unlit against the four that shine.

   Colour lives on the GLYPH, so the essence survives with no box around
   it at all. The wash and the edge only appear under the pointer. */
.chat-rune-chip[data-rune-kind] .rune-glyph { color: var(--rune-hue); }
.chat-rune-chip[data-rune-kind]:hover {
    background: var(--rune-wash);
    border-color: color-mix(in srgb, var(--rune-hue) 45%, transparent);
}
.chat-rune-chip[data-rune-kind]:hover,
.chat-rune-chip[data-rune-kind]:focus-visible { color: var(--rune-hue); }
.chat-rune-chip[data-rune-kind]:focus-visible { outline: 2px solid var(--rune-hue); outline-offset: 1px; }

.chat-rune-chip[data-rune-kind="fire"],
.rune-picker-choice[data-rune-kind="fire"] { --rune-hue: var(--rune-fire); --rune-wash: var(--rune-fire-fill); }
.chat-rune-chip[data-rune-kind="ice"],
.rune-picker-choice[data-rune-kind="ice"] { --rune-hue: var(--rune-ice); --rune-wash: var(--rune-ice-fill); }
.chat-rune-chip[data-rune-kind="storm"],
.rune-picker-choice[data-rune-kind="storm"] { --rune-hue: var(--rune-storm); --rune-wash: var(--rune-storm-fill); }
.chat-rune-chip[data-rune-kind="shield"],
.rune-picker-choice[data-rune-kind="shield"] { --rune-hue: var(--rune-shield); --rune-wash: var(--rune-shield-fill); }
.chat-rune-chip[data-rune-kind="raven"],
.rune-picker-choice[data-rune-kind="raven"] { --rune-hue: var(--rune-raven); --rune-wash: var(--rune-raven-fill); }

/* Fire: heat comes off it. */
.chat-rune-chip[data-rune-kind="fire"] .rune-glyph,
.rune-picker-choice[data-rune-kind="fire"] {
    text-shadow: 0 0 6px var(--rune-fire-fill), 0 0 2px var(--rune-fire-fill);
}

/* Ice: brittle. Lighter stroke, letters held apart like frost. */
.chat-rune-chip[data-rune-kind="ice"] .rune-glyph,
.rune-picker-choice[data-rune-kind="ice"] {
    font-weight: 400;
    letter-spacing: 0.06em;
}

/* Storm: a white core inside a charged halo. */
.chat-rune-chip[data-rune-kind="storm"] .rune-glyph,
.rune-picker-choice[data-rune-kind="storm"] {
    text-shadow: 0 0 5px var(--rune-storm), 0 0 1px var(--rune-storm);
}

/* Shield: the one hue on the board that means safe rather than
   elemental. Steel grey was tried first and read as a disabled control
   sitting among four live ones - a warding green is the reading anyone
   already has. Its non-colour cue is the heaviest stroke of the five
   plus a squared surface wherever a surface appears at all (hover, and
   your own reaction); with no box at rest, the weight carries it. */
.chat-rune-chip[data-rune-kind="shield"] { border-radius: 3px; }
.rune-picker-choice[data-rune-kind="shield"] { border-radius: 3px; }
.chat-rune-chip[data-rune-kind="shield"] .rune-glyph { font-weight: 900; }

/* Raven: matte. The only one of the five that does not shine. */
.chat-rune-chip[data-rune-kind="raven"] .rune-glyph { text-shadow: none; }

/* Yours. The only chip that carries a ground at rest, and it is the
   rune's own colour rather than gold - gold used to erase the essence
   the moment you joined in. No border: one quiet wash is enough to
   separate your reaction from everyone else's now that nothing else on
   the row has a box. */
.chat-rune-chip.mine {
    background: var(--rune-wash, rgba(212, 175, 55, 0.12));
    color: var(--rune-hue, var(--gold));
    font-weight: 700;
}

.chat-rune-chip:hover {
    border-color: var(--rune-hue, var(--gold));
}

.rune-picker {
    position: absolute;
    top: 100%;
    right: 4px;
    z-index: 200;
    display: flex;
    gap: 2px;
    background: var(--live-panel);
    border: 1px solid var(--live-border);
    border-radius: var(--radius-md);
    padding: 4px;
    box-shadow: 0 6px 20px -6px rgba(0,0,0,0.5);
}


/* Opened off a row at the bottom of the pane, where #chatMessages
   (overflow-y: auto) would cut it off. openRunePicker measures and
   adds this; see the comment there for why the bottom row is the
   common case rather than the edge case. */
.rune-picker.opens-up {
    top: auto;
    bottom: 100%;
}

/* The choice that is already yours. Same wash as your chip on the
   message, so the menu and the row agree without a second visual
   language for "mine". */
.rune-picker-choice.mine {
    background: var(--rune-wash, rgba(212, 175, 55, 0.12));
    border-color: var(--rune-hue, var(--gold));
}

/* The picker is where the colour language is learned: every choice
   wears the exact hue and cue its chip will wear on the message, so a
   member picks a feeling rather than a symbol they cannot read. */
.rune-picker-choice {
    font-family: 'MLNF Runic', 'Noto Sans Runic', 'Segoe UI Symbol', serif;
    border: 1px solid transparent;
    background: transparent;
    border-radius: var(--radius-sm);
    font-size: 1.15rem;
    font-weight: 700;
    line-height: 1;
    width: 30px;
    height: 30px;
    cursor: pointer;
    color: var(--rune-hue, var(--live-text));
    transition: background-color 0.12s, border-color 0.12s;
}

.rune-picker-choice:hover {
    background: var(--rune-wash, var(--live-panel-alt));
    border-color: var(--rune-hue, var(--gold));
    color: var(--rune-hue, var(--gold));
}

/* Focus is a ring, not a fill. openRunePicker focuses the first choice
   on open, and while focus shared the .mine treatment that first rune
   looked selected - two runes reading as "yours" in a menu where only
   one ever can be. Screenshot caught it; the states are separate now:
   filled means yours, ringed means where the keyboard is. */
.rune-picker-choice:focus-visible {
    outline: 2px solid var(--rune-hue, var(--gold));
    outline-offset: 1px;
}

/* Storm is the one rune whose essence is an event rather than a state,
   so it is the one that moves - and only while the pointer is on it,
   never in the resting log. */
@media (prefers-reduced-motion: no-preference) {
    .chat-rune-chip[data-rune-kind="storm"]:hover .rune-glyph,
    .rune-picker-choice[data-rune-kind="storm"]:hover {
        animation: runeStormFlicker 0.5s steps(1, end) 2;
    }
}

@keyframes runeStormFlicker {
    0%, 100% { opacity: 1; }
    35%      { opacity: 0.35; }
    55%      { opacity: 1; }
    70%      { opacity: 0.5; }
}

/* Touch has no hover, and the chip's box is a hover affordance - on a phone
   that is an affordance which never arrives, leaving five coloured marks with
   nothing to say they can be tapped. They get the quiet ground back at rest,
   not the old pill. */
@media (hover: none) {
    .chat-rune-chip[data-rune-kind] {
        background: var(--rune-wash);
        border-color: color-mix(in srgb, var(--rune-hue) 30%, transparent);
    }
}

/* A replay's chips are a record, not a control: pages/vod.html renders them
   from the reactions the broadcast actually collected and there is nothing to
   click, so they lose the pointer and the hover state but keep every essence
   cue exactly as it read live. */
.chat-msg-reactions.is-replay {
    /* Quieter than live: on a replay the message is what you came for and the
       reactions are marginalia. The glyph keeps its full essence colour; only
       the count steps back. */
    --rune-count: rgba(226, 232, 240, 0.5);
}

.chat-msg-reactions.is-replay .chat-rune-chip {
    cursor: default;
}

.chat-msg-reactions.is-replay .chat-rune-chip[data-rune-kind]:hover {
    background: transparent;
    border-color: transparent;
}

.chat-msg-reactions.is-replay .chat-rune-chip.mine[data-rune-kind]:hover {
    background: var(--rune-wash);
}
