/*
 * extdir, design system.
 *
 * Order: tokens, primitives, then the few places that need something of their own.
 * Thirteen templates share these primitives, so nearly all visual work happens above
 * the "pages" heading and nothing below it invents its own spacing or colour.
 *
 * Three rules this file holds itself to:
 *
 * 1. Colour means something. Every colour outside the neutral ramp is a signal,
 *    compatible, caution, risk, informational, and nothing is tinted for decoration.
 *    That is not minimalism for its own sake: this site asks people to trust data
 *    about someone else's software, and an interface that decorates is an interface
 *    that might also be dressing up its data. Restraint is the trust signal.
 *
 * 2. Never colour alone. Anything colour distinguishes is also distinguished by text
 *    or shape, so it survives greyscale, colour-blindness and a bad monitor.
 *
 * 3. Depth comes from borders and background steps, never drop shadows. Shadows are
 *    what make a technical reference look like a consumer app.
 *
 * Shopware's brand blue is deliberately absent: the trademark rule forbids this
 * reading as an official Shopware property.
 *
 * Fonts are served from this repository, never a CDN. Embedding Google Fonts from
 * Google's servers was held to breach the GDPR in LG München I, 20 January 2022,
 * 3 O 17493/20, which began a wave of German warning letters, untenable for a site
 * whose own Impressum argues § 5 DDG. Geist is SIL OFL and assets/fonts/LICENSE-geist.txt
 * travels with it, exactly as the licence gate demands of everyone we index.
 */

/* ============================================================ FONTS */

@font-face {
    font-family: 'Geist';
    src: url("../fonts/geist-sans-latin-400-normal-NLJOu4p.woff2") format('woff2');
    font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
    font-family: 'Geist';
    src: url("../fonts/geist-sans-latin-500-normal-2P9n7FN.woff2") format('woff2');
    font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
    font-family: 'Geist';
    src: url("../fonts/geist-sans-latin-600-normal--pnXdja.woff2") format('woff2');
    font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
    font-family: 'Geist Mono';
    src: url("../fonts/geist-mono-latin-400-normal-Z03_AZS.woff2") format('woff2');
    font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
    font-family: 'Geist Mono';
    src: url("../fonts/geist-mono-latin-500-normal-DGNy37S.woff2") format('woff2');
    font-weight: 500; font-style: normal; font-display: swap;
}

/* ============================================================ TOKENS */

:root {
    /* --- neutral ramp -------------------------------------------------
       Named by role, not by value. A component asks for "the sunken
       surface", so re-tuning the ramp moves everything coherently. */
    --canvas: #fcfcfb;
    /* Gradient stops, bounded by contrast rather than by taste. Text sits on this
       field, so the extreme of each wash is the darkest (light) or lightest (dark)
       background any text will ever meet.

       The first version of these was bounded so tightly by that rule that the dark
       wash was a luminance delta of 0.0034 against the canvas -- present in the
       stylesheet, invisible on a screen. The constraint had optimised the feature
       away. The fix was to buy headroom in --ink-subtle rather than to keep
       shrinking the gradient: darker in light, brighter in dark, which leaves room
       for a wash roughly five times stronger and still clears 4.5:1 everywhere.

       Measured at these values: --ink-subtle holds 5.16:1 on the light cool wash
       and 5.38:1 on the dark blue wash, its two worst cases. */
    --wash-a: #e4ecf8;
    --wash-b: #fbeadb;
    --surface: #ffffff;
    --surface-sunk: #f4f4f2;
    --surface-raised: #ffffff;
    --border: #e4e4e1;
    --border-strong: #c8c7c2;

    --ink: #16161a;
    --ink-muted: #56565e;
    --ink-subtle: #616168;

    /* --- signals -------------------------------------------------------
       The only colours in the system. Each one answers a question the
       reader actually has, and each is paired with text or shape wherever
       it appears. */
    --signal-ok: #096b3f;          /* declared, maintained, permissive */
    --signal-ok-bg: #e8f5ee;
    --signal-caution: #8a5207;     /* open-ended, lagging, needs a look */
    --signal-caution-bg: #fcf3e4;
    --signal-risk: #a3161b;        /* unlicensed, abandoned, not redistributable */
    --signal-risk-bg: #fdeded;
    --signal-info: #0b4f8f;        /* interactive, selected, informational */
    --signal-info-bg: #e9f1fa;

    /* --- chart ramp -----------------------------------------------------
       One hue, light to dark, for magnitude. Charts need a scale the four
       signals cannot provide: a signal answers "is this fine", a ramp answers
       "how much", and borrowing a signal to mean a quantity would break the
       rule that colour means something specific.

       Five steps, because past five the neighbouring shades stop being
       rankable. Validated rather than picked: monotone lightness, every
       adjacent gap wide enough to see, the lightest step still clear of the
       surface, and one hue throughout. --chart-1 is always the low end. */
    --chart-1: #86b6ef;
    --chart-2: #5598e7;
    --chart-3: #2a78d6;
    --chart-4: #1c5cab;
    --chart-5: #104281;
    --chart-ink-lo: #16161a;
    --chart-ink-hi: #ffffff;

    /* Interaction. Deliberately not one of the four signals: "you are here"
       and "this is fine" are different statements and must not share a hue. */
    --accent: #1c1c22;
    --accent-ink: #ffffff;

    /* --- geometry ------------------------------------------------------
       An 8px grid. Half steps exist for optical work inside components. */
    --s-1: 0.25rem;
    --s-2: 0.5rem;
    --s-3: 0.75rem;
    --s-4: 1rem;
    --s-5: 1.5rem;
    --s-6: 2rem;
    --s-7: 3rem;
    --s-8: 4rem;
    --s-9: 6rem;

    --radius: 6px;
    --radius-sm: 4px;
    --wrap: 1240px;

    /* --- type ----------------------------------------------------------
       Every step carries its own line height and tracking. Large text needs
       tighter tracking and shorter leading than small text; letting the
       browser scale one setting across six sizes is the difference between
       typography and defaults. */
    --font-sans: 'Geist', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
    --font-mono: 'Geist Mono', ui-monospace, SFMono-Regular, Menlo, monospace;

    --t-micro: 0.6875rem;
    --t-small: 0.8125rem;
    --t-body: 0.9375rem;
    --t-lead: 1.0625rem;
    --t-h3: 1.1875rem;
    --t-h2: 1.5rem;
    --t-h1: 2.125rem;
    --t-display: 3rem;

    font-size: 16px;
    color-scheme: light;
}

/* --- dark palette ----------------------------------------------------
   Values defined once, applied from two places: the system preference for
   anyone who has not chosen, and an explicit data-theme for anyone who has.
   Writing the palette twice is how the two drift apart, and a theme that is
   subtly wrong in one path is worse than no theme at all. */
:root {
    --dark-canvas: #0d0d0f;
    --dark-wash-a: #20263a;
    --dark-wash-b: #261d38;
    --dark-surface: #151517;
    --dark-surface-sunk: #101012;
    --dark-surface-raised: #1c1c1f;
    --dark-border: #26262a;
    --dark-border-strong: #3d3d43;
    --dark-ink: #f2f2f3;
    --dark-ink-muted: #a2a2ab;
    --dark-ink-subtle: #9a9aa4;
    --dark-signal-ok: #4ade80;
    --dark-signal-ok-bg: #0e2318;
    --dark-signal-caution: #e3b341;
    --dark-signal-caution-bg: #251c0c;
    --dark-signal-risk: #ff7b72;
    --dark-signal-risk-bg: #2b1313;
    --dark-signal-info: #6cb6ff;
    --dark-signal-info-bg: #10202f;
    /* Not a flip of the light ramp. On a dark ground "near zero" has to recede
       into the surface and "a lot" has to be bright, so the steps run the other
       way and were chosen for this surface rather than inverted from the other
       one. --chart-1 stays the low end in both themes. */
    --dark-chart-1: #256abf;
    --dark-chart-2: #3987e5;
    --dark-chart-3: #6da7ec;
    --dark-chart-4: #9ec5f4;
    --dark-chart-5: #cde2fb;
    --dark-chart-ink-lo: #f2f2f3;
    --dark-chart-ink-hi: #0d0d0f;
    --dark-accent: #f2f2f3;
    --dark-accent-ink: #0d0d0f;
}

/* No stored choice: follow the operating system. This path is what a visitor
   with JavaScript disabled gets, so dark mode still works without scripting. */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme]) {
        --canvas: var(--dark-canvas);
        --wash-a: var(--dark-wash-a);
        --wash-b: var(--dark-wash-b);
        --surface: var(--dark-surface);
        --surface-sunk: var(--dark-surface-sunk);
        --surface-raised: var(--dark-surface-raised);
        --border: var(--dark-border);
        --border-strong: var(--dark-border-strong);
        --ink: var(--dark-ink);
        --ink-muted: var(--dark-ink-muted);
        --ink-subtle: var(--dark-ink-subtle);
        --signal-ok: var(--dark-signal-ok);
        --signal-ok-bg: var(--dark-signal-ok-bg);
        --signal-caution: var(--dark-signal-caution);
        --signal-caution-bg: var(--dark-signal-caution-bg);
        --signal-risk: var(--dark-signal-risk);
        --signal-risk-bg: var(--dark-signal-risk-bg);
        --signal-info: var(--dark-signal-info);
        --signal-info-bg: var(--dark-signal-info-bg);
        --chart-1: var(--dark-chart-1);
        --chart-2: var(--dark-chart-2);
        --chart-3: var(--dark-chart-3);
        --chart-4: var(--dark-chart-4);
        --chart-5: var(--dark-chart-5);
        --chart-ink-lo: var(--dark-chart-ink-lo);
        --chart-ink-hi: var(--dark-chart-ink-hi);
        --accent: var(--dark-accent);
        --accent-ink: var(--dark-accent-ink);
        color-scheme: dark;
    }
}

/* An explicit choice, restored from localStorage before first paint. It wins
   over the system preference in both directions, someone who picks light on a
   dark machine means it. */
:root[data-theme="dark"] {
        --canvas: var(--dark-canvas);
        --wash-a: var(--dark-wash-a);
        --wash-b: var(--dark-wash-b);
        --surface: var(--dark-surface);
        --surface-sunk: var(--dark-surface-sunk);
        --surface-raised: var(--dark-surface-raised);
        --border: var(--dark-border);
        --border-strong: var(--dark-border-strong);
        --ink: var(--dark-ink);
        --ink-muted: var(--dark-ink-muted);
        --ink-subtle: var(--dark-ink-subtle);
        --signal-ok: var(--dark-signal-ok);
        --signal-ok-bg: var(--dark-signal-ok-bg);
        --signal-caution: var(--dark-signal-caution);
        --signal-caution-bg: var(--dark-signal-caution-bg);
        --signal-risk: var(--dark-signal-risk);
        --signal-risk-bg: var(--dark-signal-risk-bg);
        --signal-info: var(--dark-signal-info);
        --signal-info-bg: var(--dark-signal-info-bg);
        --chart-1: var(--dark-chart-1);
        --chart-2: var(--dark-chart-2);
        --chart-3: var(--dark-chart-3);
        --chart-4: var(--dark-chart-4);
        --chart-5: var(--dark-chart-5);
        --chart-ink-lo: var(--dark-chart-ink-lo);
        --chart-ink-hi: var(--dark-chart-ink-hi);
        --accent: var(--dark-accent);
        --accent-ink: var(--dark-accent-ink);
    color-scheme: dark;
}

/* ============================================================ RESET */

*, *::before, *::after { box-sizing: border-box; }

/* The background field.
 *
 * Two soft washes anchored to the top corners, fading out well before the fold, over
 * a flat canvas. Content sits on solid surfaces above it, so the gradient reads as
 * depth behind the page rather than as colour on the page, which is the whole trick:
 * a directional field the eye registers without ever looking at.
 *
 * A fixed pseudo-element rather than background-attachment: fixed, which forces a
 * full repaint on every scroll frame on mobile Safari and Chrome. This composites on
 * its own layer and never repaints.
 */
body::before {
    content: '';
    position: fixed;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background:
        radial-gradient(130% 95% at 8% -15%, var(--wash-a) 0%, transparent 62%),
        radial-gradient(115% 80% at 92% -8%, var(--wash-b) 0%, transparent 58%),
        var(--canvas);
}

/* Anyone who has asked their system for more contrast is telling us the field is not
   worth the loss, however small. */
@media (prefers-contrast: more) {
    body::before { background: var(--canvas); }
}

body {
    margin: 0;
    background: var(--canvas);
    color: var(--ink);
    font-family: var(--font-sans);
    font-size: var(--t-body);
    line-height: 1.55;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    font-feature-settings: 'cv11', 'ss01';
}

a { color: inherit; text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 2px; }

h1, h2, h3, h4 { margin: 0; font-weight: 600; }
h1 { font-size: var(--t-h1); line-height: 1.15; letter-spacing: -0.025em; }
h2 { font-size: var(--t-h2); line-height: 1.2; letter-spacing: -0.02em; }
h3 { font-size: var(--t-h3); line-height: 1.3; letter-spacing: -0.015em; }

p { margin: 0 0 var(--s-4); }
p:last-child { margin-bottom: 0; }

/* ============================================================ STATES
 *
 * One treatment for each state, applied everywhere. Interfaces read as
 * unfinished when the same gesture produces a different response depending on
 * which element received it, so hover, focus and selection are defined once,
 * here, and components inherit rather than reinvent.
 */

:where(a, button, input, select, summary, [tabindex]):focus-visible {
    outline: 2px solid var(--signal-info);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}

:where(button, [role='button']):disabled,
:where(button, [role='button'])[aria-disabled='true'] {
    opacity: 0.5;
    cursor: not-allowed;
}

@media (prefers-reduced-motion: no-preference) {
    .btn, .row, .chip, .token, .facet a, .view-toggle a {
        transition: background-color 0.12s ease, border-color 0.12s ease, color 0.12s ease;
    }
}

/* ============================================================ UTILITIES */

/* padding-inline, not the shorthand: every companion class on a .wrap element, .masthead, .page, .header-nav-inner, sets its own vertical padding, and a
   `padding: X 0` shorthand in any of them silently reset this to zero. The gutter
   vanished on every page; on a desktop the max-width left slack so nobody noticed,
   and on a phone the content sat flush against the edge of the screen. */
.wrap { max-width: var(--wrap); margin: 0 auto; padding-inline: var(--s-5); }

/* 24px of each side out of a 320px screen is a lot of the screen. */
@media (max-width: 480px) {
    .wrap { padding-inline: var(--s-4); }
}
.mono { font-family: var(--font-mono); }
.num { font-variant-numeric: tabular-nums; }
.muted { color: var(--ink-muted); }
.subtle { color: var(--ink-subtle); }
.small { font-size: var(--t-small); }
.lead { font-size: var(--t-lead); color: var(--ink-muted); line-height: 1.6; }

/* Micro-labels. Uppercase at this size needs tracking or it sets as a smear. */
.label {
    font-size: var(--t-micro);
    text-transform: uppercase;
    letter-spacing: 0.09em;
    font-weight: 500;
    color: var(--ink-subtle);
}

.visually-hidden {
    position: absolute; width: 1px; height: 1px;
    padding: 0; margin: -1px; overflow: hidden;
    clip-path: inset(50%); white-space: nowrap; border: 0;
}

.skip-link {
    position: absolute; left: -9999px; top: 0; z-index: 100;
    background: var(--accent); color: var(--accent-ink);
    padding: var(--s-3) var(--s-4);
}
.skip-link:focus { left: 0; }

.link { color: var(--signal-info); }
.link-arrow { color: var(--signal-info); font-weight: 500; }
.link-arrow::after { content: ' \2192'; }

/* ============================================================ CONTROLS */

.btn, .input, .select {
    font: inherit;
    font-size: var(--t-body);
    line-height: 1.2;
    padding: 0.5625rem var(--s-4);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--ink);
}

/* Any component rule that sets `display` silently defeats the `hidden` attribute,
   which is how both halves of the remote-media toggle ended up on screen at once.
   One declaration, near the top of the component layer, rather than remembering it
   per component. */
[hidden] { display: none !important; }

.btn { cursor: pointer; font-weight: 500; display: inline-flex; align-items: center; gap: var(--s-2); }
.btn:hover { background: var(--surface-sunk); text-decoration: none; }

.btn--primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.btn--primary:hover { opacity: 0.88; background: var(--accent); }

.btn--sm { padding: var(--s-1) var(--s-2); font-size: var(--t-small); }

.btn--risk { color: var(--signal-risk); border-color: color-mix(in srgb, var(--signal-risk) 40%, transparent); }
.btn--risk:hover { background: var(--signal-risk-bg); }

.input::placeholder { color: var(--ink-subtle); }
.select { padding-right: var(--s-6); cursor: pointer; }

/* ============================================================ MASTHEAD */

.site-header { background: var(--surface); border-bottom: 1px solid var(--border); }

.masthead {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    gap: var(--s-5);
    padding-block: var(--s-4);
}

.brand { display: inline-flex; align-items: center; gap: var(--s-2); }
.brand:hover { text-decoration: none; }
.brand-icon { width: 1.25rem; height: 1.25rem; display: block; }
.brand-mark {
    font-family: var(--font-mono);
    font-weight: 500; font-size: 1.0625rem;
    letter-spacing: -0.05em;
}
.brand-mark b { font-weight: 500; color: var(--ink-subtle); }

.header-search { display: flex; gap: var(--s-2); min-width: 0; max-width: 30rem; position: relative; }
.header-search .input { flex: 1; min-width: 0; padding-right: var(--s-8); }

/* The keyboard hint is the affordance. Developers look for it; without it the
   shortcut may as well not exist. */
.kbd {
    font-family: var(--font-mono); font-size: var(--t-micro);
    border: 1px solid var(--border-strong); border-bottom-width: 2px;
    border-radius: var(--radius-sm);
    padding: 0.0625rem 0.3125rem;
    color: var(--ink-subtle); background: var(--surface-sunk);
}
.header-search .kbd {
    position: absolute; right: 5.5rem; top: 50%; transform: translateY(-50%);
    pointer-events: none;
}

.header-meta { display: flex; align-items: center; gap: var(--s-4); }

/* Provenance in the chrome. The sceptic's first question is how current this is,
   and answering it before being asked is most of the trust argument. */
.data-status {
    display: inline-flex; align-items: center; gap: var(--s-2);
    font-size: var(--t-micro); color: var(--ink-subtle);
    font-family: var(--font-mono);
}
.data-status::before {
    content: ''; width: 6px; height: 6px; border-radius: 999px;
    background: var(--signal-ok);
}
.data-status.is-stale::before { background: var(--signal-caution); }

.header-nav-bar { border-top: 1px solid var(--border); background: var(--surface-sunk); }
.header-nav-inner {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--s-4); flex-wrap: wrap; padding-block: var(--s-2);
}
/* Wrapping, because signing in changes how many links there are. Three fit on a
   phone; six, Extensions, Ranking, About, Your extensions, Moderate, Sign out, do
   not, and a nowrap flex row is 474px wide whatever the viewport, so the whole page
   scrolled sideways with Sign out off the edge. Only a signed-in moderator ever saw
   it, which is why every sweep so far missed it: those pages need a session.

   min-width: 0 as well, so the nav can shrink as a flex item rather than forcing its
   parent open, the same default that has caused this three times now. */
.header-nav { display: flex; flex-wrap: wrap; min-width: 0; gap: var(--s-3) var(--s-5); }

@media (max-width: 480px) {
    /* Tighter on a phone so six links need two rows rather than three. */
    .header-nav { gap: var(--s-2) var(--s-4); }
}
.header-nav a {
    font-size: var(--t-micro); text-transform: uppercase; letter-spacing: 0.09em;
    font-weight: 500; color: var(--ink-subtle);
    padding: var(--s-1) 0;
    border-bottom: 1px solid transparent;
}
.header-nav a:hover { color: var(--ink); text-decoration: none; }
.header-nav a.is-current { color: var(--ink); border-bottom-color: var(--ink); }

.header-disclaimer { font-size: var(--t-micro); color: var(--ink-subtle); margin: 0; }

@media (max-width: 900px) {
    .masthead { grid-template-columns: minmax(0, 1fr) auto; row-gap: var(--s-3); }
    .header-search { grid-column: 1 / -1; max-width: none; order: 3; }
    .header-search .kbd { display: none; }
    .header-disclaimer { display: none; }
}

/* ============================================================ PAGE */

.page { padding-block: var(--s-7) var(--s-9); }
.page--narrow { max-width: 48rem; }

.page-head { margin-bottom: var(--s-6); }
.page-head h1 { margin-bottom: var(--s-2); }
.page-head > p { color: var(--ink-muted); max-width: 66ch; }

.catalog { display: grid; grid-template-columns: 220px minmax(0, 1fr); gap: var(--s-7); align-items: start; }
@media (max-width: 900px) { .catalog { grid-template-columns: minmax(0, 1fr); gap: var(--s-5); } }

/* ============================================================ FACETS */

.facets { position: sticky; top: var(--s-4); }
@media (max-width: 900px) { .facets { position: static; } }

.facet { margin-bottom: var(--s-5); }
.facet h2 { font-size: var(--t-micro); text-transform: uppercase; letter-spacing: 0.09em;
    font-weight: 500; color: var(--ink-subtle); margin-bottom: var(--s-2); }
.facet ul { list-style: none; margin: 0; padding: 0; }
.facet a {
    display: flex; justify-content: space-between; gap: var(--s-2);
    padding: var(--s-1) var(--s-2);
    border-radius: var(--radius-sm);
    font-size: var(--t-small); color: var(--ink-muted);
}
.facet a:hover { background: var(--surface-sunk); color: var(--ink); text-decoration: none; }
.facet a.is-active { background: var(--signal-info-bg); color: var(--signal-info); font-weight: 500; }
.facet .count { color: var(--ink-subtle); font-variant-numeric: tabular-nums; font-family: var(--font-mono); font-size: var(--t-micro); }
.facet a.is-active .count { color: var(--signal-info); }

.facet-more summary {
    cursor: pointer; list-style: none;
    font-size: var(--t-small); color: var(--signal-info);
    padding: var(--s-1) var(--s-2);
}
.facet-more summary::-webkit-details-marker { display: none; }
.facet-more summary::before { content: '+ '; }
.facet-more[open] summary::before { content: '\2212 '; }

/* Active filters as removable tokens above the results. A sidebar tick tells you
   what is selected only if you look at the sidebar; this states the current query
   where the results are. */
.tokens { display: flex; flex-wrap: wrap; gap: var(--s-2); align-items: center; margin-bottom: var(--s-4); }
.token {
    display: inline-flex; align-items: center; gap: var(--s-2);
    font-size: var(--t-small);
    padding: var(--s-1) var(--s-2);
    border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
    background: var(--surface); color: var(--ink);
}
.token:hover { border-color: var(--signal-risk); color: var(--signal-risk); text-decoration: none; }
.token .token-key { color: var(--ink-subtle); font-size: var(--t-micro); text-transform: uppercase; letter-spacing: 0.06em; }
.token::after { content: '\00d7'; color: var(--ink-subtle); }
.token:hover::after { color: var(--signal-risk); }

/* ============================================================ RESULTS
 *
 * A reference table, not a feed of cards. Someone comparing forty shipping
 * extensions needs the licence in the same place on every line; a card gives each
 * entry its own internal layout and destroys that. Hairlines separate rows because a
 * border around every row frames each one as an object rather than a line in a list.
 */

.results-head {
    display: flex; justify-content: space-between; align-items: center;
    gap: var(--s-4); margin-bottom: var(--s-4); flex-wrap: wrap;
}
.results-count { color: var(--ink-muted); margin: 0; }
.results-count strong { color: var(--ink); font-family: var(--font-mono); font-weight: 500; }
.results-controls { display: flex; align-items: center; gap: var(--s-4); flex-wrap: wrap; }

.view-toggle { display: inline-flex; border: 1px solid var(--border-strong); border-radius: var(--radius); overflow: hidden; }
.view-toggle a { padding: var(--s-2) var(--s-3); font-size: var(--t-small); color: var(--ink-muted); background: var(--surface); }
.view-toggle a + a { border-left: 1px solid var(--border-strong); }
.view-toggle a:hover { background: var(--surface-sunk); color: var(--ink); text-decoration: none; }
.view-toggle a.is-active { background: var(--accent); color: var(--accent-ink); font-weight: 500; }

.sort-form { display: flex; align-items: center; gap: var(--s-2); font-size: var(--t-small); }
.sort-form label { color: var(--ink-subtle); }
.sort-form .select { padding: var(--s-2) var(--s-5) var(--s-2) var(--s-3); font-size: var(--t-small); }

.rows {
    --cols: minmax(0, 1fr) 13rem 7rem 4.5rem 2.25rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;
}

.rows-head {
    display: grid; grid-template-columns: var(--cols); gap: var(--s-4);
    padding: var(--s-2) var(--s-3);
    border-bottom: 1px solid var(--border-strong);
    background: var(--surface-sunk);
    font-size: var(--t-micro); text-transform: uppercase; letter-spacing: 0.09em;
    color: var(--ink-subtle); font-weight: 500;
}
.rows-head .ta-r { text-align: right; }

.row {
    display: grid; grid-template-columns: var(--cols); gap: var(--s-1) var(--s-4);
    align-items: baseline;
    padding: var(--s-3);
    border-bottom: 1px solid var(--border);
}
.row:hover { background: var(--surface-sunk); }
/* Keyboard navigation needs a visible cursor that is not the focus ring, because the
   row itself is not focused, the reader is moving a selection through a list. */
.row.is-selected { background: var(--signal-info-bg); box-shadow: inset 2px 0 0 var(--signal-info); }

.row-main { min-width: 0; }
.row-title { font-size: var(--t-body); font-weight: 500; }
.row-title a:hover { color: var(--signal-info); }
.row-package {
    display: block; font-family: var(--font-mono); font-size: var(--t-micro);
    color: var(--ink-subtle); word-break: break-all;
}
.row-desc { margin: var(--s-2) 0 0; color: var(--ink-muted); font-size: var(--t-small); max-width: 72ch; }

.row-versions { display: flex; flex-wrap: wrap; gap: var(--s-1); }
.row-touched { text-align: right; font-size: var(--t-small); color: var(--ink-muted); font-family: var(--font-mono); }
.row-touched .is-stale { color: var(--signal-caution); }
.row-none { color: var(--ink-subtle); font-size: var(--t-small); }
.row-flags { grid-column: 1 / -1; margin-top: var(--s-1); }
.row-copy { justify-self: end; }

.rows--compact .row { padding: var(--s-2) var(--s-3); align-items: center; }

@media (max-width: 900px) {
    .rows { --cols: minmax(0, 1fr) auto; }
    .rows-head { display: none; }
    .row { gap: var(--s-2); padding: var(--s-4) var(--s-2); }
    .row-main { grid-column: 1 / -1; }
    .row-versions, .row-licence, .row-touched { grid-column: auto; text-align: left; }
    .row-copy { justify-self: start; }
}

/* ============================================================ CHIPS
 *
 * A chip carries its own label. An earlier version used ● and ◐ in fixed columns,
 * which meant tracing every mark back to a distant header to learn which version it
 * referred to, on a page where two thirds of the cells were empty. "6.7" needs
 * neither header nor legend.
 */

.chip {
    display: inline-flex; align-items: center; gap: var(--s-1);
    font-size: var(--t-micro); line-height: 1.5;
    padding: 0.0625rem var(--s-2);
    border-radius: var(--radius-sm);
    border: 1px solid var(--border);
    background: var(--surface-sunk); color: var(--ink-muted);
    white-space: nowrap;
}

.chip--version { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-weight: 500; }
.chip--version.is-explicit {
    color: var(--signal-ok); background: var(--signal-ok-bg);
    border-color: color-mix(in srgb, var(--signal-ok) 30%, transparent);
}
/* An open-ended constraint claims releases published after the extension was last
   touched. Dashed edge plus a ˚ in the label: two signals, so it survives greyscale. */
.chip--version.is-open {
    color: var(--signal-caution); background: transparent;
    border-style: dashed; border-color: color-mix(in srgb, var(--signal-caution) 50%, transparent);
}

.chip--risk {
    color: var(--signal-risk); background: var(--signal-risk-bg);
    border-color: color-mix(in srgb, var(--signal-risk) 35%, transparent); font-weight: 500;
}
.chip--disclosure { color: var(--signal-info); border-color: var(--signal-info); background: transparent; }

.badges { display: flex; flex-wrap: wrap; gap: var(--s-2); align-items: center; }
.badge { display: inline-flex; align-items: center; gap: var(--s-1);
    font-size: var(--t-small); padding: 0.125rem var(--s-2);
    border-radius: var(--radius-sm); border: 1px solid var(--border);
    color: var(--ink-muted); background: var(--surface-sunk); white-space: nowrap; }
a.badge:hover { border-color: var(--signal-info); color: var(--signal-info); text-decoration: none; }
.badge--ok { color: var(--signal-ok); background: var(--signal-ok-bg); border-color: color-mix(in srgb, var(--signal-ok) 30%, transparent); }
.badge--warn { color: var(--signal-caution); background: var(--signal-caution-bg); border-color: color-mix(in srgb, var(--signal-caution) 30%, transparent); }
.badge--danger { color: var(--signal-risk); background: var(--signal-risk-bg); border-color: color-mix(in srgb, var(--signal-risk) 35%, transparent); font-weight: 500; }
.badge--disclosure { color: var(--signal-info); border-color: var(--signal-info); background: transparent; font-weight: 500; }

/* Copy-to-clipboard. The most frequent action an agency developer performs here is
   taking the composer line, and it was previously only available by selecting text on
   the detail page. */
.copy-btn {
    font: inherit; font-size: var(--t-micro); font-family: var(--font-mono);
    cursor: pointer; padding: var(--s-1); line-height: 1;
    background: transparent; border: 1px solid transparent; border-radius: var(--radius-sm);
    color: var(--ink-subtle);
}
.copy-btn:hover { border-color: var(--border-strong); color: var(--ink); background: var(--surface-sunk); }
.copy-btn.is-copied { color: var(--signal-ok); border-color: color-mix(in srgb, var(--signal-ok) 35%, transparent); }

/* ============================================================ DETAIL
 *
 * A spec sheet. This is the page an agency developer arrives at from a search engine,
 * so everything needed to decide, compatibility, licence, maintenance, install,
 * resolves without scrolling, and each value states where it came from.
 */

.detail-head { border-bottom: 1px solid var(--border); padding-bottom: var(--s-5); margin-bottom: var(--s-6); }
.detail-title-row { display: flex; align-items: baseline; gap: var(--s-3); flex-wrap: wrap; }
.detail-package { font-family: var(--font-mono); font-size: var(--t-small); color: var(--ink-subtle); }
.detail-lede { margin-top: var(--s-3); max-width: 70ch; }

/* The facts, as a table rather than prose. Four columns on desktop so the whole
   answer is one glance. */
.spec {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(min(11rem, 100%), 1fr));
    gap: var(--s-5); margin-top: var(--s-5);
    padding-top: var(--s-5); border-top: 1px solid var(--border);
}
.spec-item { min-width: 0; }
.spec-key { display: block; margin-bottom: var(--s-2); }
.spec-value { display: flex; flex-wrap: wrap; gap: var(--s-1); align-items: center; font-size: var(--t-body); }
.spec-note { display: block; margin-top: var(--s-1); font-size: var(--t-micro); color: var(--ink-subtle); }

.install { margin-top: var(--s-5); }
.install-row { display: flex; gap: var(--s-2); align-items: stretch; }
.install-row pre { flex: 1; margin: 0; }

pre.code {
    background: var(--surface-sunk); border: 1px solid var(--border);
    border-radius: var(--radius); padding: var(--s-3) var(--s-4);
    overflow-x: auto; font-size: var(--t-small); line-height: 1.7;
    font-family: var(--font-mono);
}
code { font-family: var(--font-mono); font-size: 0.9em; }

.detail-grid { display: grid; grid-template-columns: minmax(0, 1fr) 300px; gap: var(--s-7); align-items: start; }
@media (max-width: 900px) { .detail-grid { grid-template-columns: minmax(0, 1fr); gap: var(--s-5); } }

/* A grid track defaults to minmax(auto, 1fr), so its *content* can force it wider
 * than the viewport, a releases table or a 69-character package name does exactly
 * that, and the page then scrolls sideways with every element clipped, not just the
 * long one. Measured at a 420px viewport: the document was 755px wide.
 *
 * The desktop rules already guard with minmax(0, ...); the narrow-viewport overrides
 * had quietly dropped it, which is why this only showed up on a phone. This is the
 * backstop for any grid child that is missed. */
.detail-grid > *,
.catalog > *,
.footer-grid > * { min-width: 0; }

/* Long names must break rather than push. Labels are free text from composer.json
 * and run to 86 characters, sometimes with no spaces to break at. */
.row-title, .alt-card-title, .page-head h1, .detail-head h1, .panel h2 {
    overflow-wrap: anywhere;
}

/* ============================================================ PANELS, TABLES, PROSE */

.panel { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
    padding: var(--s-5); margin-bottom: var(--s-4); }
.panel h2 { font-size: var(--t-body); margin-bottom: var(--s-3); }
.panel > :last-child { margin-bottom: 0; }

.notice { border: 1px solid var(--border); border-left-width: 3px; border-radius: var(--radius);
    padding: var(--s-4); margin-bottom: var(--s-5); background: var(--surface); }
.notice--ok { border-left-color: var(--signal-ok); background: var(--signal-ok-bg); }
.notice--warn { border-left-color: var(--signal-caution); background: var(--signal-caution-bg); }
.notice--info { border-left-color: var(--signal-info); background: var(--signal-info-bg); }

.kv { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--s-2) var(--s-4); font-size: var(--t-small); }
.kv dt { color: var(--ink-subtle); }
/* The track is already guarded with minmax(0, ...), so the column can shrink, but the
   value inside it still needs permission to break. A plugin's technical name is one
   CamelCase token of thirty-odd characters with no space, hyphen or slash to break
   at, so without this it simply draws straight through the side of the panel. */
.kv dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }

table.data { width: 100%; border-collapse: collapse; font-size: var(--t-small); }
table.data th, table.data td { text-align: left; padding: var(--s-3); border-bottom: 1px solid var(--border); }
table.data th { color: var(--ink-subtle); font-weight: 500; font-size: var(--t-micro);
    text-transform: uppercase; letter-spacing: 0.07em; }
table.data tbody tr:last-child td { border-bottom: 0; }
table.data td.num, table.data th.num { text-align: right; font-variant-numeric: tabular-nums; font-family: var(--font-mono); }
.table-scroll { overflow-x: auto; }

.weight-bar { display: block; height: 3px; border-radius: 999px; background: var(--signal-info); margin-top: var(--s-1); }

.prose { max-width: 70ch; }
.prose h2 { font-size: var(--t-h3); margin: var(--s-6) 0 var(--s-3); }
.prose h3 { font-size: var(--t-body); margin: var(--s-5) 0 var(--s-2); }
.prose p, .prose li { color: var(--ink-muted); }
.prose a { color: var(--signal-info); text-decoration: underline; text-underline-offset: 2px; }
.prose ul, .prose ol { padding-left: var(--s-5); }
.prose li { margin-bottom: var(--s-1); }
.prose address { font-style: normal; color: var(--ink); }
.prose code { background: var(--surface-sunk); padding: 0.1em 0.35em; border-radius: var(--radius-sm); }

.steps { list-style: none; margin: 0; padding: 0; counter-reset: step; }
.steps > li { counter-increment: step; position: relative; padding-left: var(--s-7);
    padding-bottom: var(--s-5); border-left: 1px solid var(--border); margin-left: var(--s-3); }
.steps > li:last-child { border-left-color: transparent; padding-bottom: 0; }
.steps > li::before {
    content: counter(step); position: absolute; left: calc(-1 * var(--s-4)); top: 0;
    width: var(--s-6); height: var(--s-6); display: grid; place-items: center;
    border-radius: 999px; background: var(--surface); color: var(--ink);
    border: 1px solid var(--border-strong);
    font-family: var(--font-mono); font-size: var(--t-micro); font-weight: 500;
}
.steps h2 { font-size: var(--t-body); margin-bottom: var(--s-2); }

/* Separates the German legal text from its English translation. Only one half is
   authoritative, so a reader who scrolls past the join unaware is reading the wrong
   one. */
.rule { margin: var(--s-7) 0; border: 0; border-top: 1px solid var(--border); }

.stack-sm { margin-top: var(--s-3); }
.stack { margin-top: var(--s-4); }
.page-note { margin-top: var(--s-6); }
.text-danger { color: var(--signal-risk); }
.list-plain { list-style: none; padding: 0; margin: 0; }

/* ============================================================ STATES, PAGINATION, FOOTER */

.pagination { display: flex; gap: var(--s-2); align-items: center; justify-content: center; margin-top: var(--s-6); }
.pagination a, .pagination span { padding: var(--s-2) var(--s-4); border: 1px solid var(--border);
    border-radius: var(--radius); font-size: var(--t-small); background: var(--surface); }
.pagination a:hover { border-color: var(--border-strong); background: var(--surface-sunk); text-decoration: none; }
.pagination span { color: var(--ink-subtle); font-family: var(--font-mono); }

.empty { text-align: center; padding: var(--s-9) var(--s-4); color: var(--ink-muted);
    border: 1px dashed var(--border-strong); border-radius: var(--radius); }

.flash { margin: var(--s-4) 0 0; padding: var(--s-3) var(--s-4); border-radius: var(--radius);
    border: 1px solid var(--border); border-left-width: 3px; }
.flash--success { color: var(--signal-ok); background: var(--signal-ok-bg); border-left-color: var(--signal-ok); }
.flash--error { color: var(--signal-risk); background: var(--signal-risk-bg); border-left-color: var(--signal-risk); }
.flash--warning { color: var(--signal-caution); background: var(--signal-caution-bg); border-left-color: var(--signal-caution); }

.site-footer { border-top: 1px solid var(--border); background: var(--surface); padding: var(--s-7) 0; margin-top: var(--s-9); }
.footer-grid { display: grid; grid-template-columns: minmax(0, 2fr) repeat(3, minmax(0, 1fr)); gap: var(--s-6); }
@media (max-width: 800px) { .footer-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); } }
.footer-brand { font-family: var(--font-mono); font-weight: 500; margin-bottom: var(--s-2); letter-spacing: -0.05em; }
.footer-heading { font-size: var(--t-micro); text-transform: uppercase; letter-spacing: 0.09em;
    color: var(--ink-subtle); margin-bottom: var(--s-2); font-weight: 500; }
.site-footer ul { list-style: none; margin: 0 0 var(--s-3); padding: 0; font-size: var(--t-small); }
.site-footer li { margin-bottom: var(--s-1); }
.site-footer a { color: var(--ink-muted); }
.site-footer a:hover { color: var(--ink); }

/* Operator credit. The ownership model is explicit that the platform belongs to the
   extdir org and runio.dev appears as operator and maintainer, so this credits the
   operator without implying ownership of the code. */
.footer-base {
    display: flex; justify-content: space-between; align-items: center; gap: var(--s-4);
    flex-wrap: wrap;
    margin-top: var(--s-6); padding-top: var(--s-4); border-top: 1px solid var(--border);
    font-size: var(--t-small); color: var(--ink-subtle);
}
.powered-by { display: inline-flex; align-items: center; gap: var(--s-2); margin: 0; }
.powered-by a { color: var(--ink); font-weight: 500; }

/* ============================================================ ERROR PAGES */

.error-page { text-align: center; padding: var(--s-9) 0; }
.error-code { font-family: var(--font-mono); font-size: var(--t-display); color: var(--ink-subtle);
    margin-bottom: var(--s-2); letter-spacing: -0.05em; }
.error-search { display: flex; gap: var(--s-2); max-width: 26rem; margin: var(--s-6) auto 0; }
/* A flex item defaults to min-width: auto, so the input refuses to shrink below the
   width of its own placeholder and pushes the row, and the page, 29px wider than a
   320px screen. The same default that made the detail grid overflow, one layout mode
   over. */
.error-search .input { flex: 1 1 auto; min-width: 0; }
.error-links { display: flex; flex-wrap: wrap; gap: var(--s-4); justify-content: center; margin-top: var(--s-5); font-size: var(--t-small); }

/* ============================================================ IMPRINT REVEAL */

/* The address is fetched after a countdown rather than rendered into the page, see
   LegalController for the reasoning and what it trades away. Framed deliberately:
   asking someone to wait a minute is unusual enough that it should look considered. */
.reveal { margin: var(--s-4) 0 var(--s-5); padding: var(--s-4);
    border: 1px solid var(--border); border-left: 3px solid var(--signal-info);
    border-radius: var(--radius); background: var(--surface); max-width: 32rem; }
.reveal-btn { font: inherit; font-size: var(--t-small); font-weight: 500; cursor: pointer;
    background: var(--surface); color: var(--ink);
    border: 1px solid var(--border-strong); border-radius: var(--radius); padding: var(--s-2) var(--s-4); }
.reveal-btn:hover { background: var(--surface-sunk); }
.reveal-progress { margin-top: var(--s-3); }
.reveal-track { height: 3px; border-radius: 999px; background: var(--border); overflow: hidden; }
.reveal-bar { height: 100%; width: 0; background: var(--signal-info); border-radius: 999px; }
.reveal-error { color: var(--signal-caution); }
.reveal address { font-style: normal; margin-top: var(--s-3); }

@media (prefers-reduced-motion: reduce) {
    .reveal-bar, .row, .btn, .chip, .token { transition: none; }
}

/* ============================================================ CARRIED-OVER CLASSES
 *
 * Used by the maintainer and legal templates. Kept as thin aliases on the system
 * tokens rather than left dangling, which is how a stylesheet ends up with rules
 * nothing matches and templates styled by accident.
 */

.code-inline { font-family: var(--font-mono); font-size: var(--t-small); color: var(--ink-subtle); word-break: break-all; }
.eyebrow { font-size: var(--t-micro); text-transform: uppercase; letter-spacing: 0.09em;
    color: var(--ink-subtle); margin-bottom: var(--s-2); font-weight: 500; }
.detail-badges { margin-top: var(--s-5); }
.facet-clear { font-size: var(--t-small); color: var(--signal-info); font-weight: 500; }
.facet-clear-wrap { margin-bottom: var(--s-4); }
.error-note { margin-top: var(--s-6); }

/* ============================================================ THEME TOGGLE */

.header-nav-end { display: flex; align-items: center; gap: var(--s-4); flex-wrap: wrap; }

.theme-toggle {
    display: inline-flex;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    overflow: hidden;
}
.theme-btn {
    font: inherit; cursor: pointer;
    font-size: var(--t-micro); text-transform: uppercase; letter-spacing: 0.07em;
    font-weight: 500;
    padding: 0.1875rem var(--s-2);
    background: var(--surface); color: var(--ink-subtle);
    border: 0;
}
.theme-btn + .theme-btn { border-left: 1px solid var(--border); }
.theme-btn:hover { color: var(--ink); background: var(--surface-sunk); }
/* aria-pressed carries this for assistive technology; the class carries it visually.
   Neither is sufficient alone. */
.theme-btn.is-active { background: var(--accent); color: var(--accent-ink); }

@media (max-width: 760px) {
    .header-nav-end { width: 100%; justify-content: space-between; }
}

/* ============================================================ MODERATION */

.report-form label { display: block; margin-bottom: var(--s-1); }
.report-form .input, .report-form .select { width: 100%; }
.report-form textarea.input { resize: vertical; font-family: inherit; }
.report-form p { margin-bottom: var(--s-5); }

.section-head { margin: var(--s-8) 0 var(--s-4); font-size: var(--t-h3); }

.queue { display: grid; gap: var(--s-3); }

.queue-item {
    background: var(--surface);
    border: 1px solid var(--border);
    border-left: 3px solid var(--border-strong);
    border-radius: var(--radius);
    padding: var(--s-4);
}
/* Past the seven days the takedown policy commits to. Marked with a border and a
   word, never colour alone, the one state a moderator must not scroll past. */
.queue-item.is-overdue { border-left-color: var(--signal-risk); }

.queue-head { display: flex; justify-content: space-between; gap: var(--s-4); flex-wrap: wrap; align-items: baseline; }
.queue-title { font-weight: 500; margin: 0 var(--s-2); }
.queue-age { font-size: var(--t-small); color: var(--ink-muted); font-family: var(--font-mono); white-space: nowrap; }
.is-overdue .queue-age { color: var(--signal-risk); font-weight: 500; }

.queue-reporter { margin: var(--s-2) 0 0; }
.queue-body { margin: var(--s-2) 0 var(--s-4); color: var(--ink-muted); font-size: var(--t-small); max-width: 80ch; }

.queue-actions { display: flex; gap: var(--s-4); flex-wrap: wrap; }
.act-form { display: flex; gap: var(--s-2); align-items: center; flex: 1 1 22rem; }
.act-form .input { flex: 1; min-width: 0; font-size: var(--t-small); padding: var(--s-2) var(--s-3); }

/* ============================================================ ALTERNATIVES */

.alt-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-3); }

.alt {
    display: flex; justify-content: space-between; gap: var(--s-4);
    align-items: baseline; flex-wrap: wrap;
    padding-bottom: var(--s-3);
    border-bottom: 1px solid var(--border);
}
.alt:last-child { border-bottom: 0; padding-bottom: 0; }

.alt-main { min-width: 0; flex: 1 1 16rem; }
.alt-title { font-weight: 500; display: block; }
.alt-title:hover { color: var(--signal-info); }
/* The stated basis for the suggestion. A "similar extensions" list with no reason
   given is one nobody can check, which is the opposite of what this site argues. */
.alt-why { margin: var(--s-1) 0 0; }
.alt-meta { display: flex; align-items: center; gap: var(--s-2); white-space: nowrap; }

/* ============================================================ DETAIL FOOTER

   The maintainer and badge panels sit side by side under the grid rather than
   stacked in the sidebar, where six panels against three ran the column far past
   the content beside it. */
.detail-actions {
    display: grid;
    /* min(..., 100%) rather than a bare 20rem: a fixed minimum track cannot shrink, so
       on a 320px screen a 20rem floor is wider than the content column and the panels
       hang 16px past the edge of the page. */
    grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr));
    gap: var(--s-4);
    margin-top: var(--s-4);
}

/* Links inside panels, notices and tables.
 *
 * The reset sets `a { color: inherit }` and only .prose restyled it, so every link
 * in a panel rendered as plain body text, "reported here" in the maintainer panel
 * was indistinguishable from the sentence around it, and "How this score is
 * calculated" looked like a caption. A link nobody can see is a link nobody clicks.
 *
 * Underlined as well as coloured, because colour alone is not a distinguishing mark
 * for a reader who cannot see the difference. */
.panel :where(p, li, dd) a:not(.btn):not(.chip):not(.link-arrow),
.notice a:not(.btn),
table.data a {
    color: var(--signal-info);
    text-decoration: underline;
    text-underline-offset: 2px;
}
.panel :where(p, li, dd) a:not(.btn):not(.chip):not(.link-arrow):hover,
.notice a:not(.btn):hover,
table.data a:hover { text-decoration-thickness: 2px; }

/* Compact the alternatives now that they have room: the reason belongs on one line
   beside the name rather than wrapped beneath it. */
@media (min-width: 900px) {
    .detail-alternatives .alt { align-items: baseline; }
    .detail-alternatives .alt-main { display: flex; align-items: baseline; gap: var(--s-3); flex-wrap: wrap; flex: 1 1 auto; }
    .detail-alternatives .alt-why { margin: 0; }
}

/* ============================================================ CARD RAIL
 *
 * A native scroll container with snap points, not a carousel. Everything is in the
 * DOM and reachable by trackpad, shift+wheel, touch or tab; the arrows are an
 * enhancement that hides itself when the rail already fits.
 */

.rail { position: relative; }

.rail-track {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: minmax(15rem, 1fr);
    gap: var(--s-3);
    overflow-x: auto;
    scroll-snap-type: x proximity;
    /* Room for the focus ring, which a plain overflow container clips. */
    padding: var(--s-1) var(--s-1) var(--s-4);
    margin: calc(-1 * var(--s-1));
    scrollbar-width: thin;
}
@media (min-width: 900px) {
    .rail-track { grid-auto-columns: minmax(17rem, 1fr); }
}

.alt-card {
    scroll-snap-align: start;
    display: flex;
    flex-direction: column;
    gap: var(--s-2);
    min-width: 0;
    padding: var(--s-4);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}
.alt-card:hover { border-color: var(--border-strong); }

.alt-card-title { font-weight: 500; line-height: 1.3; }
.alt-card-title:hover { color: var(--signal-info); }
.alt-card-package {
    font-family: var(--font-mono); font-size: var(--t-micro);
    color: var(--ink-subtle); word-break: break-all;
}
.alt-card-versions { display: flex; flex-wrap: wrap; gap: var(--s-1); }
.alt-card-why {
    margin: 0; font-size: var(--t-micro); color: var(--ink-subtle);
    /* The reason is context, not the headline, capped so one verbose entry cannot
       make every card in the rail taller. */
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
    overflow: hidden;
}
.alt-card-foot {
    margin-top: auto; padding-top: var(--s-2);
    display: flex; align-items: center; justify-content: space-between; gap: var(--s-2);
}

.rail-nav {
    position: absolute; top: 38%;
    width: 2rem; height: 2rem;
    display: grid; place-items: center;
    font: inherit; font-size: 1.25rem; line-height: 1; cursor: pointer;
    background: var(--surface); color: var(--ink);
    border: 1px solid var(--border-strong); border-radius: 999px;
}
.rail-nav:hover { background: var(--surface-sunk); }
.rail-nav--prev { left: calc(-1 * var(--s-4)); }
.rail-nav--next { right: calc(-1 * var(--s-4)); }
@media (max-width: 700px) {
    /* No room to sit outside the track, and a thumb does the job anyway. */
    .rail-nav { display: none; }
}

/* ============================================================ VENDORS */

.vendor-list {
    list-style: none; margin: 0; padding: 0;
    columns: 2; column-gap: var(--s-7);
    border-top: 1px solid var(--border);
}
@media (min-width: 900px) { .vendor-list { columns: 3; } }

.vendor-row {
    display: flex; align-items: baseline; gap: var(--s-2);
    padding: var(--s-2) 0;
    border-bottom: 1px solid var(--border);
    /* Without this a row can be split across the column boundary mid-line. */
    break-inside: avoid;
}
.vendor-name { flex: 1; min-width: 0; }
.vendor-name:hover { color: var(--signal-info); }
.vendor-count { color: var(--ink-subtle); font-family: var(--font-mono); font-size: var(--t-small); }

/* ============================================================ EXTENSION MARKS
 *
 * The monogram is generated from the package name and always renders, so a reader
 * who never allows remote media still gets a complete page rather than a row of
 * gaps, and it stays underneath as the fallback when an icon 404s. */

.ext-icon-slot {
    position: relative;
    flex: 0 0 auto;
    width: 1.75rem; height: 1.75rem;
    display: inline-grid; place-items: center;
    border-radius: var(--radius-sm);
    overflow: hidden;
    font-size: .6875rem; font-weight: 600; letter-spacing: -.02em;
    /* Derived hue, fixed saturation and lightness, so every mark sits at the same
       weight and no extension shouts louder than its neighbour. */
    background: hsl(var(--icon-hue, 0) 32% 88%);
    color: hsl(var(--icon-hue, 0) 45% 28%);
    user-select: none;
}
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .ext-icon-slot {
        background: hsl(var(--icon-hue, 0) 24% 22%);
        color: hsl(var(--icon-hue, 0) 45% 78%);
    }
}
:root[data-theme="dark"] .ext-icon-slot {
    background: hsl(var(--icon-hue, 0) 24% 22%);
    color: hsl(var(--icon-hue, 0) 45% 78%);
}

.ext-icon {
    position: absolute; inset: 0;
    width: 100%; height: 100%;
    object-fit: contain;
    padding: 2px;
    /* A light tile in both themes, deliberately not var(--surface). Plugin icons are
       drawn for the Shopware admin, which is light, and a large share of them are
       dark marks on transparency, on a dark card those disappear entirely. A fixed
       pale tile renders every logo the way its author drew it. */
    background: #f4f5f7;
}

.row-main { display: flex; align-items: flex-start; gap: var(--s-3); min-width: 0; }
.row-text { min-width: 0; }

.ext-icon-lg .ext-icon-slot { width: 2.5rem; height: 2.5rem; font-size: .9375rem; border-radius: var(--radius); }

.alt-card .ext-icon-slot { width: 1.5rem; height: 1.5rem; }

.remote-media-control { display: inline-flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; }

/* ================================================================ SCREENSHOTS
 *
 * Thumbnails only, linked to the original on the forge. Fixed-height boxes with
 * `contain` rather than `cover`: these are screenshots of an interface, and cropping
 * one to fill a tile removes the part that shows what it does. */

.gallery-note { margin-top: calc(var(--s-2) * -1); }

.gallery-track { gap: var(--s-3); }

.shot {
    position: relative;
    display: grid; place-items: center;
    /* Width comes from the track's grid-auto-columns, like every other rail item;
       only the height is set here, to hold a roughly 16:9 screenshot. */
    height: 9.5rem;
    scroll-snap-align: start;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;
    /* Same pale tile as the icons, for the same reason: a screenshot of a light
       admin interface on a dark card reads as a hole in the page. */
    background: #f4f5f7;
    text-decoration: none;
}
.shot:hover { border-color: var(--accent); }
.shot:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Visible only in the moment between the reader allowing images and the file
   arriving, and again if one never does. */
.shot-index {
    font-size: 1.25rem; font-weight: 600;
    color: var(--ink-subtle);
}

.shot-img {
    position: absolute; inset: 0;
    width: 100%; height: 100%;
    object-fit: contain;
    background: #f4f5f7;
}

/* ================================================================== LIGHTBOX
 *
 * The native <dialog>, so Escape, focus trapping and returning focus to the thumbnail
 * come from the browser rather than from listeners that get two thirds of it right. */

.lightbox {
    width: min(94vw, 1100px);
    max-height: 92vh;
    padding: 0;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--ink);
    overflow: hidden;
}

/* Scoped to [open]: a bare `display: flex` on the dialog would override the
   browser's own `display: none` and leave it on screen permanently. */
.lightbox[open] { display: flex; flex-direction: column; }
.lightbox::backdrop {
    /* Dark in both themes: a screenshot is being judged against it, and a light
       scrim behind a light UI screenshot hides where the image ends. */
    background: rgb(0 0 0 / 0.72);
}

.lightbox-frame {
    display: grid; place-items: center;
    /* Takes whatever is left after the bar. min-height:0 is what actually allows it
       to shrink, without it a tall screenshot pushes the controls off screen, which
       is how the close button ended up below the fold the first time. */
    flex: 1 1 auto;
    min-height: 0;
    background: #f4f5f7;
}
.lightbox-frame img {
    display: block;
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
}

.lightbox-bar {
    flex: 0 0 auto;
    display: flex; align-items: center; gap: var(--s-3);
    padding: var(--s-3) var(--s-4);
    border-top: 1px solid var(--border);
}
.lightbox-caption {
    flex: 1 1 auto; min-width: 0;
    margin: 0;
    font-size: var(--t-small); color: var(--ink-subtle);
    display: flex; flex-wrap: wrap; gap: var(--s-1) var(--s-3);
}

.lightbox-nav, .lightbox-close {
    flex: 0 0 auto;
    width: 2rem; height: 2rem;
    display: grid; place-items: center;
    border: 1px solid var(--border); border-radius: var(--radius-sm);
    background: var(--surface); color: var(--ink);
    font-size: 1.125rem; line-height: 1; cursor: pointer;
}
.lightbox-nav:hover, .lightbox-close:hover { border-color: var(--accent); color: var(--accent); }

/* The thumbnail is a link but opens a dialog, so it should read as pressable. */
.shot { cursor: zoom-in; }

/* ============================================================ FILTER DRAWER
 *
 * A control only on the widths where the filter column is in the way. On desktop the
 * sidebar is beside the results, not above them, so there is nothing to collapse and
 * the toggle would be furniture. */

.facet-drawer-toggle { display: none; }

@media (max-width: 900px) {
    .facet-drawer-toggle {
        display: flex; align-items: center; gap: var(--s-2);
        padding: var(--s-3) var(--s-4);
        border: 1px solid var(--border); border-radius: var(--radius);
        background: var(--surface);
        font-size: var(--t-small); font-weight: 500;
        cursor: pointer;
        list-style: none;
    }
    /* Safari draws its own triangle through ::-webkit-details-marker. */
    .facet-drawer-toggle::-webkit-details-marker { display: none; }

    .facet-drawer-toggle::after {
        content: '▾';
        margin-left: auto;
        color: var(--ink-subtle);
        transition: transform 120ms ease;
    }
    .facet-drawer[open] > .facet-drawer-toggle::after { transform: rotate(180deg); }

    .facet-drawer[open] > .facet-drawer-toggle { margin-bottom: var(--s-4); }
}

.chip--count { background: var(--signal-info-bg); color: var(--signal-info); border-color: transparent; }

/* ============================================================== TAP TARGETS
 *
 * WCAG 2.2 asks for 24x24 CSS pixels on anything you are meant to hit (SC 2.5.8,
 * AA). Measured on a phone, the controls that matter most were all just under: the
 * extension title in a listing row was 17px tall, the copy button 21, the theme
 * buttons and every row on the vendor list 23.
 *
 * A link inside a sentence is exempt, its size is set by the line-height of the
 * text around it, and padding it out would wreck the paragraph. So this touches only
 * the things that behave as controls: row and card titles, and actual buttons.
 *
 * Padding rather than height, so the text keeps its position and only the hit area
 * grows. On a desktop the change is invisible; on a phone it is the difference
 * between hitting the extension you meant and the one below it. */

.row-title a,
.alt-card-title,
.vendor-name {
    display: inline-block;
    padding-block: 0.25rem;
}

.copy-btn,
.theme-btn {
    min-height: 1.75rem;
}

/* Boards ------------------------------------------------------------------ */

/* Three boards abreast on a wide screen, one on a phone. min() on the track so a
   long vendor name cannot push the grid wider than the viewport, the same guard
   the other auto-fit grids here carry, and the fix for the same bug four times over. */
.board-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(17rem, 100%), 1fr));
    gap: var(--s-4);
    margin-bottom: var(--s-6);
}

.board {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    padding: var(--s-4);
    min-width: 0;
}

.board-title { font-size: var(--t-body); }
.board-question { color: var(--ink-muted); font-size: var(--t-small); margin: var(--s-1) 0 var(--s-3); }

.board-rows {
    list-style: none;
    margin: 0;
    padding: 0;
    counter-reset: board;
}

/* Rank as a counter rather than a column: the position is the list's own numbering,
   so a screen reader announces it from the <ol> and it is never stated twice. */
.board-row {
    counter-increment: board;
    display: grid;
    grid-template-columns: 1.5rem minmax(0, 1fr) auto;
    align-items: baseline;
    gap: var(--s-1) var(--s-2);
    padding-block: var(--s-2);
    border-bottom: 1px solid var(--border);
    min-width: 0;
}

.board-row:last-child { border-bottom: 0; }

.board-row::before {
    content: counter(board);
    color: var(--ink-muted);
    font-size: var(--t-small);
    font-variant-numeric: tabular-nums;
}

/* Long package and vendor names are common here and must wrap rather than widen the
   track. anywhere is right for a slug like vendor/some-very-long-plugin-name, which
   has no spaces to break at. */
.board-name {
    min-width: 0;
    overflow-wrap: anywhere;
    font-weight: 500;
}

.board-sublabel {
    grid-column: 2;
    font-size: var(--t-small);
    color: var(--ink-muted);
    overflow-wrap: anywhere;
}

.board-value { font-variant-numeric: tabular-nums; white-space: nowrap; }

.board-row .weight-bar { grid-column: 2 / -1; }

.board-empty { font-size: var(--t-small); }

.board-rule {
    margin: var(--s-3) 0 0;
    font-size: var(--t-small);
    color: var(--ink-muted);
    border-top: 1px solid var(--border);
    padding-top: var(--s-2);
}

.board-rule-label {
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-size: 0.6875rem;
    color: var(--ink-subtle, var(--ink-muted));
    margin-right: var(--s-1);
}

.board .chip { vertical-align: middle; }

/* ============================================================ STATS

   Charts drawn as boxes wherever a box will do: a bar is a width, a column is a
   height, a heat cell is a background. Only the adoption curve is SVG, because a
   diagonal is the one shape this approach cannot make.

   Drawing them as elements keeps every label at a real font size instead of
   scaling it down with the picture, keeps the numbers in the document for a
   reader with no scripting, and means the whole page re-themes from the tokens
   with nothing listening for the change. */

.kpi-row {
    list-style: none;
    margin: 0 0 var(--s-5);
    padding: 0;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(10rem, 100%), 1fr));
    gap: var(--s-3);
}

.kpi {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: var(--s-4);
}

/* Proportional figures, not tabular. Equal-width digits make a large standalone
   number look loose; tabular-nums belongs in the columns that align vertically. */
.kpi-value {
    display: block;
    font-size: var(--t-h2);
    font-weight: 600;
    line-height: 1.1;
}

.kpi-label {
    display: block;
    margin-top: var(--s-1);
    font-size: var(--t-small);
    color: var(--ink-muted);
}

/* Cards in a row share a height, and the rule is pinned to the bottom of each.
   Left to size themselves they end ragged, because the rule under one chart runs to
   four lines and under the next to one, and a row of cards ending at four different
   heights reads as a layout accident rather than as a set. */
.chart-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(22rem, 100%), 1fr));
    gap: var(--s-4);
}

.chart { display: flex; flex-direction: column; }

.chart .board-rule { margin-top: auto; }

.chart--wide { grid-column: 1 / -1; }

.chart-question {
    margin: calc(var(--s-2) * -1) 0 var(--s-4);
    font-size: var(--t-small);
    color: var(--ink-muted);
}

/* --- columns ------------------------------------------------------- */

.chart-plot { margin-bottom: var(--s-3); }

.chart-columns {
    display: flex;
    align-items: flex-end;
    gap: 2px;
    height: 150px;
}

.chart-col {
    flex: 1 1 0;
    min-width: 0;
    height: 100%;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    position: relative;
}

.chart-col-fill {
    height: var(--fill, 0%);
    background: var(--chart-3);
    border-radius: var(--radius-sm) var(--radius-sm) 0 0;
}

.chart-col:hover .chart-col-fill,
.chart-col:focus-visible .chart-col-fill { background: var(--chart-5); }

/* A year still running is short for a reason that is not about the ecosystem.
   Hatching says "incomplete" without inventing a sixth colour, and the axis
   label below it is muted to match. */
.chart-col.is-partial .chart-col-fill {
    background: repeating-linear-gradient(
        135deg,
        var(--chart-2) 0 4px,
        transparent 4px 8px
    );
    border: 1px solid var(--chart-2);
    border-bottom: 0;
}

/* Values on demand, plus the tallest column always. A number over every bar goes
   unread; a number over none leaves the eye nothing to calibrate against. */
.chart-col-value {
    font-size: var(--t-micro);
    text-align: center;
    color: var(--ink-muted);
    font-variant-numeric: tabular-nums;
    margin-bottom: 2px;
    opacity: 0;
}

.chart-col.is-peak .chart-col-value,
.chart-col:hover .chart-col-value,
.chart-col:focus-visible .chart-col-value { opacity: 1; color: var(--ink); }

.chart-axis {
    display: flex;
    gap: 2px;
    margin-top: var(--s-2);
    border-top: 1px solid var(--border);
    padding-top: var(--s-2);
}

.chart-axis-label {
    flex: 1 1 0;
    min-width: 0;
    text-align: center;
    font-size: var(--t-micro);
    color: var(--ink-muted);
    font-variant-numeric: tabular-nums;
}

.chart-axis-label.is-partial { opacity: 0.6; }

/* --- horizontal bars ------------------------------------------------ */

.chart-bars { list-style: none; margin: 0; padding: 0; }

.chart-bar {
    display: grid;
    grid-template-columns: minmax(6rem, 9rem) 1fr auto;
    align-items: center;
    gap: var(--s-3);
    padding: 3px 0;
}

.chart-bar-label {
    font-size: var(--t-small);
    overflow-wrap: anywhere;
}

.chart-bar-track {
    background: var(--surface-sunk);
    border-radius: 999px;
    height: 8px;
    overflow: hidden;
}

.chart-bar-fill {
    display: block;
    height: 100%;
    background: var(--chart-3);
    border-radius: 999px;
}

.chart-bar-value {
    font-size: var(--t-small);
    color: var(--ink-muted);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* The denominator behind a percentage, on the same line and a step quieter.
   A share with no count under it is not a measurement: eighty-nine percent of nine
   and eighty-nine percent of four hundred are different claims, and the reader
   cannot tell which one they are looking at from the bar. Quieter than the value
   because it is context, not the subject, but never absent. */
.chart-bar-context {
    color: var(--ink-muted);
    font-size: 0.6875rem;
    margin-left: 0.4em;
    opacity: 0.75;
}

/* --- stacked bar ---------------------------------------------------- */

/* A 2px gap between segments rather than a border around each. A border adds ink
   that reads as data; a gap in the surface colour separates without drawing. */
.chart-stack {
    display: flex;
    gap: 2px;
    height: 14px;
    margin-bottom: var(--s-4);
}

.chart-stack-seg { border-radius: 2px; }
.chart-stack-seg:first-child { border-radius: var(--radius-sm) 2px 2px var(--radius-sm); }
.chart-stack-seg:last-child { border-radius: 2px var(--radius-sm) var(--radius-sm) 2px; }

.chart-legend { list-style: none; margin: 0; padding: 0; }

.chart-legend li {
    display: grid;
    grid-template-columns: auto 1fr auto auto;
    align-items: center;
    gap: var(--s-2);
    padding: 2px 0;
    font-size: var(--t-small);
}

.chart-swatch {
    width: 10px;
    height: 10px;
    border-radius: 2px;
}

/* Legends mirror the mark: a rect for a filled bar, a line for a line chart. */
.chart-key {
    width: 14px;
    height: 2px;
    border-radius: 999px;
}

.chart-legend-label { overflow-wrap: anywhere; }
.chart-legend-value { font-variant-numeric: tabular-nums; }
.chart-legend-share { font-variant-numeric: tabular-nums; color: var(--ink-muted); min-width: 3.5rem; text-align: right; }

.tone--ok { background: var(--signal-ok); }
.tone--caution { background: var(--signal-caution); }
.tone--risk { background: var(--signal-risk); }
.tone--info { background: var(--signal-info); }
.tone--muted { background: var(--border-strong); }

/* --- line chart ----------------------------------------------------- */

.chart-lines { display: flex; gap: var(--s-2); align-items: stretch; }

.chart-yaxis {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    font-size: var(--t-micro);
    color: var(--ink-muted);
    text-align: right;
    min-width: 2.5rem;
}

.chart-tick { line-height: 1; transform: translateY(-0.4em); }

.chart-lines-plot { position: relative; flex: 1; min-width: 0; }

/* The box carries the viewBox's own ratio, which is what keeps the scale uniform.
   The plot then grows taller as the card grows wider, and the y-axis column beside
   it stretches to match rather than being pinned to a height the SVG no longer has. */
.chart-svg {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 640 / 220;
}

.chart-grid-line { stroke: var(--border); stroke-width: 1; }

.chart-line { stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }

.chart-halo {
    stroke: var(--surface);
    stroke-width: 6;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.chart-crosshair { stroke: var(--border-strong); stroke-width: 1; }

/* Each mark takes only the property it actually uses. Grouping the three selectors
   and setting stroke, fill and background together also gave every line a fill, and
   a filled open path paints the area back to its own start: the lines rendered as
   wedges that thickened mid-span, which reads as two series merging. The .chart-line
   fill:none meant to prevent that lost on specificity to the .step-N rule. */
.chart-line.step-1 { stroke: var(--chart-1); }
.chart-line.step-2 { stroke: var(--chart-2); }
.chart-line.step-3 { stroke: var(--chart-3); }
.chart-line.step-4 { stroke: var(--chart-4); }
.chart-line.step-5 { stroke: var(--chart-5); }

.chart-dot.step-1 { fill: var(--chart-1); }
.chart-dot.step-2 { fill: var(--chart-2); }
.chart-dot.step-3 { fill: var(--chart-3); }
.chart-dot.step-4 { fill: var(--chart-4); }
.chart-dot.step-5 { fill: var(--chart-5); }

.chart-key.step-1 { background: var(--chart-1); }
.chart-key.step-2 { background: var(--chart-2); }
.chart-key.step-3 { background: var(--chart-3); }
.chart-key.step-4 { background: var(--chart-4); }
.chart-key.step-5 { background: var(--chart-5); }

.chart-line, .chart-halo { fill: none; }

/* A 2px ring in the surface colour where a dot sits on a line it overlaps. */
.chart-dot { stroke: var(--surface); stroke-width: 2; }

.chart-xaxis {
    display: flex;
    justify-content: space-between;
    margin: var(--s-2) 0 0 3rem;
    font-size: var(--t-micro);
    color: var(--ink-muted);
    font-variant-numeric: tabular-nums;
}

.chart-xlabel {
    margin: var(--s-1) 0 var(--s-4) 3rem;
    font-size: var(--t-micro);
    color: var(--ink-subtle);
}

.chart-tooltip {
    position: absolute;
    top: var(--s-2);
    left: 0;
    background: var(--surface-raised);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    padding: var(--s-2) var(--s-3);
    font-size: var(--t-micro);
    pointer-events: none;
    white-space: nowrap;
    z-index: 2;
}

.chart-tooltip dl { margin: 0; display: grid; grid-template-columns: auto auto; gap: 0 var(--s-2); }
.chart-tooltip dt { color: var(--ink-muted); }
.chart-tooltip dd { margin: 0; font-variant-numeric: tabular-nums; text-align: right; }
.chart-tooltip-head { font-weight: 600; margin-bottom: 2px; }

/* --- heatmap -------------------------------------------------------- */

.heatmap td.heat {
    text-align: center;
    font-variant-numeric: tabular-nums;
    border-left: 2px solid var(--surface);
}

/* Zero is drawn as nothing rather than as the palest step. "None of this
   category" and "the least of it" are different facts and must not share ink. */
.heatmap td.step-0 { color: var(--ink-subtle); }
.heatmap td.step-1 { background: var(--chart-1); color: var(--chart-ink-lo); }
.heatmap td.step-2 { background: var(--chart-2); color: var(--chart-ink-lo); }
.heatmap td.step-3 { background: var(--chart-3); color: var(--chart-ink-hi); }
.heatmap td.step-4 { background: var(--chart-4); color: var(--chart-ink-hi); }
.heatmap td.step-5 { background: var(--chart-5); color: var(--chart-ink-hi); }

/* --- the table twin ------------------------------------------------- */

.chart-numbers { margin-top: var(--s-4); }

.chart-numbers summary {
    font-size: var(--t-small);
    color: var(--ink-muted);
    cursor: pointer;
}

.chart-numbers[open] summary { margin-bottom: var(--s-3); }

@media (max-width: 560px) {
    .chart-bar { grid-template-columns: minmax(4.5rem, 6rem) 1fr auto; gap: var(--s-2); }
    .chart-axis-label { font-size: 0.625rem; }
}
