@layer legacy {
/* Status LED - the two-ring session indicator, drawn as ONE element.
 *
 * Renders the markup from client/js/status-led.js. Everything here is
 * selected off the two data attributes that module emits:
 *
 *   [data-inner] - the chat's own status  -> the FILL colour of the dot
 *   [data-outer] - activity and attention -> the RING colour, its glow,
 *                  and whether that glow breathes
 *
 * The two are INDEPENDENT. No rule may read one to decide the other; the
 * whole point of two rings is that either can change without the other.
 * The one deliberate exception is the recessed centre, which two states
 * share and which is keyed on the RING - see that rule for why.
 *
 * STRUCTURE: ONE ELEMENT, NO PSEUDO-ELEMENT. A single inline-block
 * `<span>` carries both rings. The inner ring is its `background-color`.
 * The outer ring and the glow around it are LAYERS OF ONE `box-shadow`
 * on that same span: an optional hollow rim INSIDE the dot, a hard
 * `0 0 0 <ring-width>` spread ring just outside it, a feather that
 * softens that ring's own edge, and a soft blurred glow beyond it.
 *
 * WHY THAT MATTERS, AND WHAT IT REPLACES. The halo used to be an
 * `::after` - a real box, absolutely positioned, sized off the dot by a
 * scale factor. A box gets its position and its size PIXEL-SNAPPED by
 * the layout engine as two separate quantities, and it is snapped
 * independently of its parent's box. So whenever the dot itself landed
 * on a fractional x/y - which is routine inside a flex row, or when a
 * text baseline puts an inline box on a half pixel - the halo's box
 * rounded one way and the dot's rounded the other, and the two circles
 * came apart by a device pixel. Symmetric `inset` fixed the halo's own
 * internal symmetry and still could not fix this, because the drift was
 * between TWO BOXES, not inside one. The owner's report after that fix,
 * verbatim: "the circles are still not lining up properly."
 *
 * A box-shadow is not a box. It is painted from the element's own border
 * box, at the same subpixel position that box already has, so it CANNOT
 * be snapped to a different grid than the thing it surrounds. Concentric
 * is not something this rule arranges - it is the only geometry a
 * box-shadow can have. That is the whole reason for the rewrite, and it
 * is why nothing here may reintroduce a second box.
 *
 * TRANSPARENCY LIVES IN THE COLOUR, NOT IN `opacity`. The old halo was a
 * separate element, so it could carry its own `opacity` without touching
 * the dot. One element cannot: `opacity` would fade the state colour at
 * the centre too. Every alpha is therefore mixed into the shadow's own
 * colour with `color-mix(in srgb, <hue> <alpha>, transparent)`, which is
 * already used elsewhere in this app's stylesheets. The hue and the alpha
 * stay separate tokens so a theme can restyle one without the other.
 *
 * SIZING, AND THE ONE RULE THAT MATTERS: ONE LIT DIAMETER FOR EVERY
 * STATE. `--led-size` is the dot; the ring, its feather and the glow are
 * flat pixel values around it. Every one of those geometry tokens is
 * declared ONCE, in the block below, and NO PER-STATE RULE MAY CHANGE
 * ONE. States differ in colour, alpha and fill. They never differ in
 * size. That had to be stated because it was once false: the halo was
 * sized per state, so the LIT object came out at three different
 * diameters (9.0, about 14.7, and 15.3px) and only the two loud ones
 * were ever visible - in a sidebar where one session is working and the
 * rest are at rest, that paints one dot 60 percent wider than its
 * neighbours, which is exactly what the owner reported. ledHtml() may
 * set `--led-size` per instance; nothing else should.
 *
 * NO THEME MAY NEED TO OVERRIDE THIS FILE. The state colours are tokens
 * declared once in the block below, each falling back to a theme token
 * where one exists and to a literal only when it does not. A theme that
 * wants a different LED palette redefines --led-color-*, not these rules.
 */

.status-led {
    /* ---- the one place the state colours are named ------------------
     * Each is the hue for BOTH rings of that state - the ring is the
     * same colour as the fill, only outside it and softened, which is
     * what makes it read as one lit object rather than two shapes. The
     * one exception is the finished-turn ring, which is deliberately a
     * DIFFERENT colour from its centre; see --led-color-unread below.
     *
     * Every value defers to a theme token first, and every token used
     * here is one that all of client/css/themes already declares, so a
     * theme changes the LED palette for free. The literal after the
     * comma is only the no-theme fallback.
     *
     * FIVE COLOURS, 2026-09-08. The owner's rule, in their words: "if the
     * session is fully stopped waiting for a response, then yellow. if
     * it's still working but needs something from me, make it light
     * blue", over "red if the connection is disconnected, grey if the
     * session is idle, green if there is activity". Nine inner states
     * resolve onto those five hues; the state NAMES stay nine because
     * the accessible label still has to say which one it is, and colour
     * was never allowed to be the only signal here. The legend that
     * taught this vocabulary was the status-light legend, removed on
     * 2026-09-22; the tooltips on each light carry it now.
     *
     * GREEN - the session is moving. */
    --led-color-working: var(--color-success, #4ade80);

    /* YELLOW - fully stopped, waiting on a human. Two states land here: a
     * PermissionRequest that halted the agent mid-turn, and a pane parked
     * on the startup / folder-trust prompt. They keep separate tokens so
     * a theme can still tell them apart if it ever wants to, and separate
     * labels so a screen reader always can, but they paint the same
     * yellow because the user's answer to both is the same - go to this
     * session and respond.
     *
     * --color-warning (#fbbf24) is the palette's existing true yellow and
     * every theme under client/css/themes already declares it, so no
     * theme has to learn a new token. The permission orange this
     * replaced (--color-status-pending, #ffa500) sat too close to the red
     * the dead light takes; at nine pixels an orange and a red in the
     * same list read as one colour. */
    --led-color-waiting: var(--color-warning, #fbbf24);
    --led-color-permission: var(--color-warning, #fbbf24);

    /* LIGHT BLUE - still working, and asking for the user. A Notification
     * does not stop the agent, so it may not paint the "stopped" yellow,
     * and it is not resting, so it may not paint grey.
     *
     * WHY BLUE AND NOT A THIRD WARM HUE. This light has to be told apart
     * from green by people who cannot tell red from green: under both
     * protanopia and deuteranopia the green (#4ade80) desaturates toward
     * a pale yellow-khaki while a blue at this wavelength stays plainly
     * blue, so the pair survives. A warm hue would not have.
     * --color-info (#4fc1ff) is the palette's existing light blue. */
    --led-color-notice: var(--color-info, #4fc1ff);

    /* GREY - at rest, or not measured. Both take the same hue on purpose:
     * "idle" and "we did not find out" are equally uninteresting to look
     * at, and neither may be dressed up as a measured healthy state. They
     * are told apart by SHAPE, not colour - `unknown` is hollow and
     * `idle` is filled, see the recessed-centre rule below - which is the
     * trick the old .status-dot used, for the same reason.
     * --color-fg-muted rather than --color-fg-subtle: subtle is #666,
     * which at nine pixels on the default #1e1e1e background is nearly
     * invisible, and an indicator you cannot see is not an indicator. */
    --led-color-idle: var(--color-fg-muted, #8b8b8b);
    --led-color-unknown: var(--color-fg-muted, #8b8b8b);

    /* RED, and one exception. `dead` is a pane whose process exited;
     * `disconnected` is this browser's socket to the session being down,
     * which says nothing about the session itself. Both used to share the
     * same red - the tooltip and the aria-label were the only thing
     * separating them, see INNER_LABELS in client/js/status-led.js - until
     * the owner's 2026-09-25 ruling: a dead session is no longer red, it
     * is a flat dark grey, `#494c4f`, with no theme-token fallback chain
     * because this colour is not part of the existing palette. A dropped
     * WebSocket is unrelated to the pane's own health and stays red. */
    --led-color-dead: #494c4f;
    --led-color-disconnected: var(--color-danger, #ff4444);

    /* OVER ITS USAGE LIMIT (issue #1902): the red skull. The same danger token a dropped
     * socket wears (--oc-coral resolves to it in the web tree); the SKULL SHAPE and the
     * words tell the two apart, never the hue. */
    --led-color-limit: var(--color-danger, #ff4444);

    /* THE FINISHED-TURN RING, AND IT IS THE OWNER'S 2026-09-09 RULING.
     * Not a sixth colour: it is the green above, worn as a rim around a
     * RECESSED CENTRE. Two lines of this project fixed the same reported
     * defect - a ring pulsing on sessions with nothing running - in
     * opposite ways, one by retiring this state and moving unread onto
     * the inner dot, one by keeping the ring and making it still. The
     * owner picked the ring. Their exact words for how it should look:
     * "the green outline dot should look like this grey one on the
     * bottom, just the ring should be green instead of light gray" - so
     * it is the `unknown` dot's own construction wearing a different
     * hue, and nothing else about it changes.
     *
     * This treatment replaced the unread envelope ICON on both the
     * sidebar and the launchpad, so it is the only thing saying "there
     * is something here for you" and it has to be unmistakable at nine
     * pixels. The manual mark-unread CONTROL is a separate question and
     * still ships, behind `ui.show_mark_unread_control`. */
    --led-color-unread: var(--color-success, #4ade80);

    /* ---- geometry, and NO PER-STATE RULE MAY TOUCH ANY OF IT ---------
     * The owner's calibration, verbatim (2026-09-08): "glowing is still
     * to big. like 1 or 2 px larger than the front circle." Revisited
     * 2026-09-09, owner's follow-up: "lets make the border a little
     * larger and can we feather it?"
     *
     * --led-ring-width is the HARD ring: a zero-blur spread shadow, so
     * the lit ring's outer diameter is exactly size + 2 * ring-width. At
     * the 9px default every call site actually renders (the sidebar row
     * and the launchpad card both call dotHtml() with no `size`), 1.5px
     * puts that at 12px - three pixels larger than the dot, still an
     * INTEGER so the ring's outer edge keeps landing on the pixel grid
     * whenever the dot's does.
     *
     * --led-ring-feather-blur / --led-ring-feather-fraction answer "can
     * we feather it". A hard `0 0 0 <width>` shadow has a knife-edge - it
     * is either fully the ring colour or fully transparent, one device
     * pixel apart. Feathering means a SECOND shadow at the SAME spread
     * (so its unblurred edge would sit exactly on the hard ring's outer
     * edge) but with a small blur and a fraction of the ring's alpha, so
     * the transition happens over a few pixels instead of one. The
     * fraction multiplies --led-ring-alpha rather than standing alone, so
     * it AUTOMATICALLY goes to zero wherever the ring itself does.
     *
     * --led-glow-blur / --led-glow-spread are the soft halo beyond both
     * ring layers. A blurred shadow reaches spread + blur/2 past the
     * border box; raising the blur alone spreads the SAME amount of light
     * over a wider fade, which reads as "softer" rather than "bigger".
     *
     * THESE FIVE NUMBERS ARE THE LIT DIAMETER, and they are constant
     * across every state, which is what makes "one lit diameter" true by
     * construction rather than by every state rule remembering to agree.
     * A per-state rule that set --led-ring-width or a glow token would
     * silently reintroduce the size disparity described in the file
     * header; that is what `unread` used to do.
     *
     * PRIOR VALUES, for anyone tracing a regression:
     *   - 1px ring, 4px blur / 1.5px spread glow (the one-element
     *     rewrite, 2026-09-08) - no feather layer, about 16.2px across.
     *   - 1.3x halo scale, 1.5px spread (the ::after era) - correct SIZE,
     *     but two boxes, hence the drift.
     *   - 1.7x scale, 0.3x spread put the lit object at about 21px.
     *   - 2.6x scale, 0.62x spread put it at about 35px - larger than
     *     the row text, the owner's "breathing is way too big". */
    /* ---- 2026-09-21: THE OUTER RING AND THE GLOW ARE ZERO -----------
     * Owner's report, verbatim: "the filled in yellow / blue and green
     * dots are too big. they should all be the same size as the red /
     * dead dot."
     *
     * That is a precise description of what the ring and the glow were
     * doing. `dead` is the one state that draws neither (`[data-outer=
     * 'off']` zeroes both alphas), so it rendered at the bare 9px fill
     * while every lit state wore a 1.5px hard ring, a feather and a 6px
     * glow on top of it - about 15px of lit object against 9. The states
     * were the same SIZE and a different amount of LIGHT, and light reads
     * as size at nine pixels.
     *
     * Zeroing the geometry rather than the per-state alphas is what makes
     * "one lit diameter" true for every state at once: the alphas below
     * are multiplied by these, so there is no state rule that can put a
     * ring back by disagreeing. NOTHING WAS LOST WITH THEM. The ring
     * carried no fact the fill did not - `steady` and `active` both drew
     * it in the FILL's own hue, and `dim` drew it in the same grey
     * `unknown` already is - so no state pair became harder to tell
     * apart. The two treatments that DO carry a fact of their own are
     * both INSET (`--led-inset-ring`, used by `unknown` and by the green
     * finished-turn ring) and are untouched: an inset shadow is painted
     * inside the 9px box, so those two states were never the ones out of
     * size and still read exactly as they did.
     *
     * Motion moved with it. The breathing glow was the only thing saying
     * "this session is moving", so the fill now carries that instead -
     * see the keyframes at the foot of this file. Restoring a ring is a
     * matter of putting a non-zero value back here; every alpha rule
     * below is intact and would light up again unchanged. */
    --led-size: 9px;
    --led-ring-width: 0px;
    --led-ring-feather-blur: 0px;
    --led-ring-feather-fraction: 0;
    --led-glow-blur: 0px;
    --led-glow-spread: 0px;

    /* The trough of the breath, as a fraction of the glow's resting
     * spread AND of its alpha. One number drives both so the glow shrinks
     * and dims together, which is what reads as a glow swelling rather
     * than a light flickering. The RING is not multiplied by it - see the
     * keyframes. */
    --led-glow-rest: 0.4;

    /* Resolved per state by the [data-inner] / [data-outer] rules below.
     * Declared here so every LED always has a value for all of them, and
     * an unstyled state is impossible.
     *
     * --led-ink       the fill hue, from [data-inner]
     * --led-ring-ink  the ring hue, from [data-outer]
     * --led-ring-alpha  how solid the hard ring reads
     * --led-glow-alpha  how strong the soft glow reads at its peak */
    --led-ink: var(--led-color-unknown);
    --led-ring-ink: var(--led-color-unknown);
    --led-ring-alpha: 30%;
    --led-glow-alpha: 0%;

    /* WHAT THE CENTRE OF THE DOT IS PAINTED WITH, and it is a token
     * rather than a literal because TWO states need it to be nothing.
     * By default the centre is the state's own ink, i.e. a solid dot.
     * The recessed-centre rule below is the ONE place that turns it off,
     * for `unknown` and for the finished-turn ring together, so the two
     * hollow treatments cannot drift apart into two recipes. Reading it
     * through a variable rather than through a second `background`
     * declaration is also what keeps the legacy `.status-dot` compat
     * block at the foot of this file from having to re-state it once per
     * hollow state at a higher specificity. */
    --led-fill: var(--led-ink);

    /* The hollow rim, carried as a SHADOW LAYER rather than as its own
     * `box-shadow` declaration. There is exactly one box-shadow property
     * on this element and the outer ring needs it, so a state rule that
     * declared its own would silently erase the outer ring for that one
     * state. Default is a no-op layer, replaced by the recessed-centre
     * rules below. */
    --led-hollow-width: 2px;
    --led-inset-ring: inset 0 0 0 0 transparent;

    /* The three outer layers, named so the base rule and both keyframes
     * can share one definition instead of restating the whole list three
     * times. A custom property cannot be interpolated by an animation
     * unless it is registered with @property, so the keyframes below
     * still write a real `box-shadow`; what they write is these vars. */
    --led-ring-layer: 0 0 0 var(--led-ring-width)
        color-mix(in srgb, var(--led-ring-ink) var(--led-ring-alpha), transparent);
    --led-ring-feather-layer: 0 0 var(--led-ring-feather-blur) var(--led-ring-width)
        color-mix(
            in srgb,
            var(--led-ring-ink) calc(var(--led-ring-alpha) * var(--led-ring-feather-fraction)),
            transparent
        );
    --led-glow-layer: 0 0 var(--led-glow-blur) var(--led-glow-spread)
        color-mix(in srgb, var(--led-ring-ink) var(--led-glow-alpha), transparent);
    --led-glow-layer-rest: 0 0 var(--led-glow-blur)
        calc(var(--led-glow-spread) * var(--led-glow-rest))
        color-mix(
            in srgb,
            var(--led-ring-ink) calc(var(--led-glow-alpha) * var(--led-glow-rest)),
            transparent
        );

    display: inline-block;
    flex-shrink: 0;
    /* Lets the glyph pseudo-element below be positioned `inset: 0` over
     * this element's own box instead of the page's. No top/left/etc is
     * set here, so `position: relative` alone changes nothing about this
     * element's own size or place in the flow - it only gives a later
     * `position: absolute` descendant something to measure against. */
    position: relative;
    width: var(--led-size);
    height: var(--led-size);
    border-radius: var(--radius-full);
    background-color: var(--led-fill);
    /* THE ONE box-shadow. Four layers, painted back to front: the
     * optional hollow rim INSIDE the dot, the hard ring just outside it,
     * the feather that softens the ring's own edge, and the soft glow
     * beyond that. All four are painted from this element's own border
     * box, so all four are concentric with the fill by construction and
     * none of them can round to a different pixel than it. */
    box-shadow: var(--led-inset-ring), var(--led-ring-layer), var(--led-ring-feather-layer),
        var(--led-glow-layer);
}

/* ---- inner dot: the fill ---------------------------------------------
 * Colour only. Motion and the ring belong to [data-outer]. */

.status-led[data-inner='working'] {
    --led-ink: var(--led-color-working);
}

.status-led[data-inner='waiting-permission'] {
    --led-ink: var(--led-color-permission);
}

.status-led[data-inner='waiting-input'] {
    --led-ink: var(--led-color-waiting);
}

.status-led[data-inner='notice'] {
    --led-ink: var(--led-color-notice);
}

.status-led[data-inner='idle'] {
    /* READ AND AT REST. A solid grey fill. Unlike `unknown` below this is
     * a MEASUREMENT - the session has a Stop event and nobody owes it a
     * look - not the absence of one, so it stays filled rather than
     * hollow. Added 2026-09-09, owner's report verbatim: "i need the
     * lights to go idle, (i think thats gray) when i click on a tab.
     * there needs to be a read/idle color." It is the calmest thing on
     * the screen, which is the point: it is what a session looks like
     * once its green unread ring has gone. */
    --led-ink: var(--led-color-idle);
}

.status-led[data-inner='done'] {
    /* A FINISHED TURN. The grey is deliberate: `done` is only ever
     * rendered under the green unread ring (see ledStateFor), and the
     * owner asked for that centre to look like the `unknown` dot's, not
     * like a second green light. The recessed-centre rule below clears
     * it entirely whenever the ring is actually present. */
    --led-ink: var(--led-color-idle);
}

.status-led[data-inner='dead'] {
    --led-ink: var(--led-color-dead);
}

.status-led[data-inner='disconnected'] {
    --led-ink: var(--led-color-disconnected);
}

/* ---- GLYPH STATES: A SHAPE INSTEAD OF A DOT --------------------------
 * Two inner states stop being a plain filled circle. `working` becomes a
 * rotating arc - the owner's own artwork, an open ring rather than a
 * closed one, so spinning it reads as motion and not as a static ring
 * sitting still. `waiting-permission` and `waiting-input` both become a
 * question mark: they already resolve to the same --color-warning
 * yellow (see --led-color-permission / --led-color-waiting above) and
 * mean the same thing to the user - go answer this session - so one
 * glyph does for both.
 *
 * MECHANISM. ledHtml() in client/js/status-led.js emits ONE empty
 * <span>, contracted byte-identical across both frontend trees (see the
 * file header), so the shape has to come from CSS with no new markup.
 * A `::after` pseudo-element is masked into the glyph's shape with
 * `mask-image: url("data:image/svg+xml,...")` - CSP-legal, since
 * `img-src 'self' data: blob:` already covers a data: image
 * (src/security_headers.py) - and painted with
 * `background-color: var(--led-ink)`, the SAME token the plain-dot
 * states already use for their fill. A theme that redefines
 * --led-color-working or --led-color-waiting/--led-color-permission
 * therefore restyles the glyph for free; no colour is written twice.
 * `-webkit-mask-*` is declared alongside every unprefixed `mask-*` for
 * Safari, which has no unprefixed support yet.
 *
 * THE SOLID FILL IS SUPPRESSED FOR THESE GLYPH STATES ONLY (asleep joined for
 * issue #537, below), the same
 * `--led-fill: transparent` trick the recessed-centre rule below uses,
 * or the glyph would sit ON TOP of a solid dot instead of replacing it.
 * Nothing else moves: the ring, the glow and every geometry token in
 * the base rule are untouched, and `position: relative` on the base
 * rule (added above, no offsets of its own) is the only reason the
 * pseudo-element below can sit `inset: 0` over this element's own box
 * instead of the page's. */
.status-led[data-inner='working'],
.status-led[data-inner='waiting-permission'],
.status-led[data-inner='waiting-input'],
.status-led[data-inner='asleep'],
.status-led[data-inner='asleep-unread'],
.status-led[data-inner='limit'] {
    --led-fill: transparent;
}

.status-led[data-inner='working']::after,
.status-led[data-inner='waiting-permission']::after,
.status-led[data-inner='waiting-input']::after,
.status-led[data-inner='asleep']::after,
.status-led[data-inner='asleep-unread']::after,
.status-led[data-inner='limit']::after {
    content: '';
    display: block;
    position: absolute;
    inset: 0;
    pointer-events: none;
    background-color: var(--led-ink);
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-position: center;
    mask-position: center;
    -webkit-mask-size: contain;
    mask-size: contain;
}

.status-led[data-inner='working']::after {
    /* The owner's supplied artwork, unaltered: viewBox 0 0 24 24, an
     * open arc rather than a closed ring. Rotation is set where its
     * @keyframes status-led-spin is declared, in the motion section
     * below - not here, so the whole animation stays in one place. */
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='white' d='M12,23a9.63,9.63,0,0,1-8-9.5,9.51,9.51,0,0,1,6.79-9.1A1.66,1.66,0,0,0,12,2.81h0a1.67,1.67,0,0,0-1.94-1.64A11,11,0,0,0,12,23Z'/%3E%3C/svg%3E");
    mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='white' d='M12,23a9.63,9.63,0,0,1-8-9.5,9.51,9.51,0,0,1,6.79-9.1A1.66,1.66,0,0,0,12,2.81h0a1.67,1.67,0,0,0-1.94-1.64A11,11,0,0,0,12,23Z'/%3E%3C/svg%3E");
}

.status-led[data-inner='waiting-permission']::after,
.status-led[data-inner='waiting-input']::after {
    /* Drawn by hand, not set in a font: a font glyph is hinted and
     * kerned for reading paragraphs, not for surviving a mask at 9px,
     * and pulling in a font file would be an external request this
     * component's markup-free, CSP-safe contract does not make. The
     * hook is a rounded stroke and the dot below it a filled circle,
     * both inside the 0 0 24 24 box and spanning close to its full
     * height (hook top near y=2, dot bottom near y=23) so there is no
     * wasted padding at the smallest sizes this LED ships at - 9px in
     * a sidebar row, 12px on the launchpad. Checked at both against a
     * dark ground; see the led-check notes. */
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M8,7 C8,3.5 10.2,1.8 12.3,1.8 C15,1.8 17.3,3.6 17.3,6.8 C17.3,9.6 15.3,10.6 13.6,12 C12.4,13 12,13.9 12,15.6' fill='none' stroke='white' stroke-width='3.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3Ccircle cx='12' cy='21' r='2.05' fill='white'/%3E%3C/svg%3E");
    mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M8,7 C8,3.5 10.2,1.8 12.3,1.8 C15,1.8 17.3,3.6 17.3,6.8 C17.3,9.6 15.3,10.6 13.6,12 C12.4,13 12,13.9 12,15.6' fill='none' stroke='white' stroke-width='3.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3Ccircle cx='12' cy='21' r='2.05' fill='white'/%3E%3C/svg%3E");
}

/* ASLEEP (issue #537): a session the user put to sleep - its agent and tmux
 * session stopped to free memory, woken from its menu - draws a crescent
 * moon in place of the dot, in idle grey because nothing is running. It is
 * a member of the glyph group above (fill cleared, masked `::after`), so it
 * replaces the dot on every surface that draws this LED: sidebar rows, the
 * home screen's running sessions and terminal tabs. */
.status-led[data-inner='asleep'] {
    --led-ink: var(--led-color-idle);
}

/* ASLEEP AND FLAGGED FOR FOLLOW-UP (issue #749, Joe (ccsliinc) 2026-09-26:
 * "the moon coloured when flagged"): the same moon, inked in the unread
 * token the finished-turn ring wears, so every theme recolours it for free.
 * No ring and no rim: ledStateFor pairs it with `off`, because a green rim
 * around a moon would read as a second light. */
.status-led[data-inner='asleep-unread'] {
    --led-ink: var(--led-color-unread);
}

.status-led[data-inner='asleep']::after,
.status-led[data-inner='asleep-unread']::after {
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='white' d='M21.5 14.6A10 10 0 0 1 9.4 2.5a10 10 0 1 0 12.1 12.1Z'/%3E%3C/svg%3E");
    mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='white' d='M21.5 14.6A10 10 0 0 1 9.4 2.5a10 10 0 1 0 12.1 12.1Z'/%3E%3C/svg%3E");
}

/* OVER ITS USAGE LIMIT (issue #1902, Adam (Adoom666), 2026-10-17: "Whenever this error
 * shows up (Over limit) I want the progress indicator to turn to the red skull"): the
 * skull replaces the dot, a glyph state like the moon above. The silhouette is the
 * `skull` icon's own head outline (web/src/opscenter/ui/icons.ts) filled, eyes and
 * nose punched out. STATIC ON PURPOSE: no animation at all, since any running CSS
 * loop recomposites the whole window (issue #1821). No ring, ledStateFor pairs it
 * with `off`. */
.status-led[data-inner='limit'] {
    --led-ink: var(--led-color-limit);
}

.status-led[data-inner='limit']::after {
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='3 2.5 18 18'%3E%3Cpath fill='white' fill-rule='evenodd' d='M12 3.5c-4.4 0-7.5 3-7.5 7.2 0 2.3 1 4 2.5 5v2.8h10v-2.8c1.5-1 2.5-2.7 2.5-5 0-4.2-3.1-7.2-7.5-7.2ZM7.4 11a1.6 1.6 0 1 0 3.2 0a1.6 1.6 0 1 0-3.2 0ZM13.4 11a1.6 1.6 0 1 0 3.2 0a1.6 1.6 0 1 0-3.2 0ZM12 13.2l-.9 1.7h1.8Z'/%3E%3C/svg%3E");
    mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='3 2.5 18 18'%3E%3Cpath fill='white' fill-rule='evenodd' d='M12 3.5c-4.4 0-7.5 3-7.5 7.2 0 2.3 1 4 2.5 5v2.8h10v-2.8c1.5-1 2.5-2.7 2.5-5 0-4.2-3.1-7.2-7.5-7.2ZM7.4 11a1.6 1.6 0 1 0 3.2 0a1.6 1.6 0 1 0-3.2 0ZM13.4 11a1.6 1.6 0 1 0 3.2 0a1.6 1.6 0 1 0-3.2 0ZM12 13.2l-.9 1.7h1.8Z'/%3E%3C/svg%3E");
}

/* ---- THE RECESSED CENTRE: ONE RECIPE, TWO STATES --------------------
 * Both hollow treatments in this component clear the centre the same
 * way - the fill is dropped so the row background shows through, which
 * is what makes the middle read as dark and set back rather than as a
 * second colour. They are declared together, in this one rule, so a
 * later edit cannot give one of them a "dark" centre that is really a
 * grey it painted while the other keeps showing the true background.
 *
 * The two are told apart by the HUE of the rim, never by the centre.
 *
 * WHY `unread` AND NOT `done + unread`. The recessed centre belongs to
 * the RING, which is an outer treatment, so it is keyed on the ring.
 * `done` is the only inner state `ledStateFor` ever pairs with it, but a
 * matrix gallery may render others and all of them should show the one
 * ring the app actually ships. This is the single documented place where
 * an outer state reaches the fill; nothing else may. */
.status-led[data-inner='unknown'],
.status-led[data-outer='unread'] {
    --led-fill: transparent;
}

.status-led[data-inner='unknown'] {
    /* THE HOLLOW GREY DOT, carried over from `.status-dot--unknown` for
     * the reason recorded there: `unknown` is not a new severity, so it
     * may not take a louder colour, but it must not be pixel-identical
     * to a measured state under a theme that flattens the greys. Shape
     * separates them without ranking them. An inset shadow rather than a
     * border or a radius trick, so it stays hollow under the themes that
     * set --radius-full to 0 and turn every dot into a square - and it
     * goes in through --led-inset-ring rather than as its own
     * `box-shadow`, so it composes with the outer ring instead of
     * replacing it. */
    --led-inset-ring: inset 0 0 0 var(--led-hollow-width) var(--led-color-unknown);
    --led-ink: var(--led-color-unknown);
}

/* ---- outer ring: activity and attention ------------------------------
 * Hue, alpha and motion. NEVER size - see the geometry block above.
 *
 * These rules come AFTER the inner ones on purpose. `unread` sets the
 * rim colour, and on the one pair where both would (`unknown` under an
 * unread ring, reachable only from a matrix gallery) the RING is the
 * thing worth saying, so it must win the source-order tie. */

.status-led[data-outer='active'] {
    /* Moving, and THE ONLY BREATHING RING IN THE APP. Same hue as the
     * fill, so the whole thing reads as one lit object. */
    --led-ring-ink: var(--led-ink);
    --led-ring-alpha: 85%;
    --led-glow-alpha: 55%;
}

.status-led[data-outer='steady'] {
    /* Lit and STILL. Two things land here and both are "nothing is
     * running": a session at rest that has already been read, and any
     * live-but-stopped turn a future rung wants to mark without motion.
     * The ring takes the fill's own hue, so a resting grey session gets
     * a GREY ring - which is the owner's complaint stated as a rule, a
     * ring that is not grey means something is happening. */
    --led-ring-ink: var(--led-ink);
    --led-ring-alpha: 60%;
    --led-glow-alpha: 25%;
}

.status-led[data-outer='unread'] {
    /* A FINISHED TURN NOBODY HAS LOOKED AT: a crisp green rim around the
     * same recessed centre `unknown` draws, and NO glow.
     *
     * IT DOES NOT BREATHE, and that is the entire point of keeping it.
     * The defect both lines of this project were fixing was a ring that
     * pulsed on a session with nothing running in it - "the ring around
     * some of the leds are not gray, which means there should be
     * background tasks. i dont think those few have any background
     * tasks." Motion is reserved for `active`, so a light that MOVES is
     * a session that is moving. An outline that pulses also stops
     * reading as an outline at nine pixels.
     *
     * THE GREEN IS THE RIM, NOT THE OUTER RING. The rim is inset, so it
     * sits inside the 9px dot exactly where `unknown`'s grey rim does,
     * which is what makes the two constructions identical bar the hue -
     * the owner's requirement, verbatim, in --led-color-unread above.
     * The outer ring stays the faint grey `dim` wears, so the lit
     * diameter is the same as every other state's and no second ring
     * competes with the green one.
     *
     * IT MAY NOT TOUCH A GEOMETRY TOKEN. It used to size its own halo,
     * and that is how `unread` and `active` came to paint two different
     * diameters. Colour, alpha and fill only. */
    --led-inset-ring: inset 0 0 0 var(--led-hollow-width) var(--led-color-unread);
    --led-ink: var(--led-color-unread);
    --led-ring-ink: var(--led-color-unknown);
    --led-ring-alpha: 18%;
    --led-glow-alpha: 0%;
}

.status-led[data-outer='off'] {
    /* Nothing is running and nothing is pending: a dead pane, or a
     * transport we have lost. No ring and no glow at all - not dim ones.
     * A corpse must not glow. Both layers go fully transparent rather
     * than the whole box-shadow going `none`, because `none` would take
     * the hollow rim with it and make one dimension depend on the other.
     * The feather layer needs no rule of its own: it is a FRACTION of
     * --led-ring-alpha, so zeroing the ring already zeroes it. */
    --led-ring-alpha: 0%;
    --led-glow-alpha: 0%;
}

.status-led[data-outer='dim'] {
    /* Not measured. A faint grey ring and no glow: enough to read as a
     * ring, not enough to claim the session is lit. */
    --led-ring-ink: var(--led-color-unknown);
    --led-ring-alpha: 30%;
    --led-glow-alpha: 0%;
}

/* ---- legacy `.status-dot` compatibility -----------------------------
 * session-status-ui.js emits ONE element carrying both vocabularies:
 * the legacy `status-dot status-dot--<state>` classes (so existing
 * selectors and harnesses keep finding it) and this component's two data
 * attributes (which do the drawing).
 *
 * That means status-dot.css and this file would otherwise BOTH paint the
 * same element. This block is the referee.
 *
 * IT NO LONGER RESETS `box-shadow`, AND MUST NOT. That reset existed
 * when the LED's own ring lived on a pseudo-element and the element's
 * box-shadow was purely legacy. The ring is now ON THIS ELEMENT, so a
 * `.status-dot.status-led { box-shadow: none }` here - two classes, and
 * therefore more specific than every rule above - would erase the outer
 * ring on every LED in the app. The legacy shadows it used to cancel
 * (`.status-dot--dead`'s ring, `.status-dot--question`'s glow) are all
 * single-class rules in status-dot.css, which loads BEFORE this file, so
 * the base `.status-led` rule already beats them on source order at equal
 * specificity. That file-order requirement is load-bearing; see the
 * comment on the <link> tags in client/index.html.
 *
 * The `animation` reset is still needed - `status-dot-pulse` would flash
 * the fill on top of the ring's breathing and read as two lights - but it
 * is scoped OFF the breathing state rather than applied to all of them
 * and re-overridden. The breathing rule below is (0,2,0) and a blanket
 * `.status-dot.status-led` reset would tie with it and win on order,
 * silently killing the pulse for every LED in the app.
 *
 * This is a PAIR with the extraClass call in session-status-ui.js's
 * dotHtml. Deleting either one alone leaves the other wrong. status-dot.css
 * is still the sole styling for any `.status-dot` emitted WITHOUT the LED
 * (the fallback path when status-led.js fails to load), which is why that
 * file is not deleted. */

.status-dot.status-led:not([data-outer='active']) {
    animation: none;
}

.status-dot.status-led {
    /* Reads the FILL token, not the ink, so both hollow states keep
     * their recessed centre here for free. Restating `var(--led-ink)`
     * would refill them, and it would do it silently: this rule outranks
     * the recessed-centre rule on specificity and comes later in the
     * file, so the green ring would quietly go back to a grey blob on
     * every surface that emits the legacy class. */
    background-color: var(--led-fill);
}

/* ---- motion ----------------------------------------------------------
 *
 * The breathing glow. Animates the GLOW LAYER only - its spread and its
 * alpha - so the fill stays at full strength and legible at every point
 * in the cycle, and the hard ring stays put as the shape that says where
 * the LED ends.
 *
 * NOTHING HERE MOVES THE ELEMENT. No transform, no width, no margin, no
 * inset: box-shadow is a paint-only property, so the dot's own box is
 * byte-identical at every frame of the animation and the LED cannot
 * drift against the text it sits beside. That is the same guarantee the
 * one-element rewrite bought statically, held across time.
 *
 * The two keyframes differ in exactly one layer, and both restate the
 * whole four-layer list because a box-shadow animation interpolates
 * layer by layer and needs the same count and the same inset flags at
 * both ends. They restate it as the SAME vars the base rule uses, so
 * there is still one definition of each layer. */

.status-led[data-outer='active'] {
    animation: status-led-breathe 2s ease-in-out infinite;
}

/* THE FILL PULSES FOR `notice`, AND ONLY FOR `notice` NOW. Owner,
 * 2026-09-21: "for 'working' let's make it pulse in and out for a little
 * motion." That motion lived on `[data-outer='active']`, a DIFFERENT
 * axis from `[data-inner]` (see the file header) - `working` is not the
 * only inner state that carries `outer: active`, so the pulse actually
 * ran on four states: `working`, `waiting-permission`, `waiting-input`
 * and `notice` (client/js/status-led.js, ledStateFor). Three of those
 * four now draw the GLYPH STATES rule above instead of a plain fill, so
 * a fill-opacity pulse under a rotating arc or a static question mark
 * would be motion with nothing readable happening to it - the override
 * below turns the pulse off wherever a glyph is drawn. `notice` draws no
 * glyph and is unchanged: it is still a plain light-blue dot and it
 * still pulses exactly as it did before this rule existed.
 *
 * It used to be the GLOW that breathed, which is not available any more -
 * the glow is zero, for the size reason recorded in the geometry block
 * above. So the motion moved onto the one thing every plain-dot state
 * still has, its fill, and it is expressed as `opacity` because that
 * keeps the guarantee the whole component was rewritten for: opacity is
 * a PAINT-ONLY property, so the dot's box is byte-identical at every
 * frame and the light cannot drift against the text beside it. A
 * transform would scale the box and reintroduce exactly the per-frame
 * size disparity this file spends its geometry block preventing.
 *
 * It does not fade to nothing. 0.4 at the trough still reads as a lit
 * dot in a list of grey ones, so a glance during the dim half of the
 * cycle does not report a session as resting. */
@keyframes status-led-breathe {
    0%,
    100% {
        opacity: 1;
    }
    50% {
        opacity: 0.4;
    }
}

/* RETIRE THE PULSE WHEREVER A GLYPH REPLACED THE FILL. Two attribute
 * selectors beats the one on the rule above regardless of source order,
 * so this always wins without depending on staying after it in the
 * file. `working` gets its own motion instead - the spin below - and
 * NOTHING MAY BOTH SPIN AND PULSE: a rotating arc inside a fading dot
 * reads as flicker, not as two intentional signals. The two waiting
 * states get no motion at all; a static question mark is legible, one
 * that is fading in and out on a 2s cycle is a worse version of the
 * same glyph for no reason a user asked for. */
.status-led[data-outer='active'][data-inner='working'],
.status-led[data-outer='active'][data-inner='waiting-permission'],
.status-led[data-outer='active'][data-inner='waiting-input'] {
    animation: none;
}

/* THE WORKING LIGHT SPINS. The owner's artwork rotated about its own
 * centre (12, 12 of its 0 0 24 24 box), 0.75s, linear, infinite - the
 * exact spec supplied for it. This is a `transform: rotate(...)` on the
 * ::after pseudo-element ONLY, never on `.status-led` itself, and that
 * is a deliberate, narrow exception to "NOTHING HERE MOVES THE ELEMENT"
 * below: a transform never participates in layout, and rotating a
 * SQUARE, CENTRED box about its own centre leaves that box's bounding
 * box unchanged, so the exact failure mode the no-transform rule guards
 * against - a box whose apparent size or position drifts frame to frame
 * - cannot happen here. A scaling transform would still be exactly the
 * mistake that rule exists to prevent. */
@keyframes status-led-spin {
    to {
        transform: rotate(360deg);
    }
}

.status-led[data-inner='working']::after {
    animation: status-led-spin 0.75s linear infinite;
}

/* REDUCED MOTION. The glow stays - it is what makes the LED readable -
 * but it stops breathing and sits at its full lit value, which is
 * exactly what the base rule already paints, so killing the animation is
 * the whole of it. A user who has asked for no motion must still be able
 * to tell an active session from a resting one, so the distinction moves
 * entirely into the ring and glow alphas: `active` remains the brightest
 * ring on screen, it just holds still. `unread` never moved in the first
 * place, so a reduced-motion user and everyone else see the same green
 * ring.
 *
 * THE ARC AND THE QUESTION MARK STAY ON SCREEN, ONLY THE SPIN STOPS: a
 * static arc still reads as "working" by its shape and colour, same as
 * the glyph rule intends at rest, and the question mark never moved in
 * the first place. `.status-led::after` needs its own line here because
 * an animation on the host element does not touch its pseudo-element's
 * own `animation` property - they are two different boxes. */
@media (prefers-reduced-motion: reduce) {
    .status-led,
    .status-led::after {
        animation: none !important;
    }
}

/* ---- THE UNREAD-COUNT BADGE IS GONE ---------------------------------
 * A yellow numeric pill used to sit beside a summary LED on every group
 * header. It was removed on 2026-09-09 at the owner's request ("to be
 * clear remove the yello (1)"), and nothing replaced it: the roll-up LED
 * carries the same finished-turn ring the rows do, so unread is already
 * said once, in the same vocabulary, in the same place. Two indicators
 * for one fact is how they end up disagreeing.
 *
 * The plain count pill on the header (`.session-sidebar-group__count`,
 * in client/css/session-sidebar-groups.css) is a DIFFERENT control - it
 * says how many conversations a folded section is hiding - and stays. */
}
