/* ux.md §7.3: one accent, everything else greyscale. §7.4: dark mode follows the OS. */
:root {
    color-scheme: light dark;
    --bg: #fdfdfc;
    --fg: #1b1b19;
    --muted: #6b6b66;
    --rule: #e2e2dd;
    --accent: #1c5d99;
    --card: #ffffff;
    /* A RECESSED surface, where --card is a raised one. A result row sits ON the page and
       is lighter than it; the footer sits UNDER the page and is darker. Both directions
       invert in dark mode, which is why this cannot just be "grey". */
    --sunk: #f1f0ea;}

@media (prefers-color-scheme: dark) {
    :root {
        --bg: #16171a;
        --fg: #e6e6e3;
        --muted: #9a9a94;
        --rule: #2b2d31;
        --accent: #7cb0e0;
        --card: #1c1e22;
        --sunk: #1b1d21;
    }
}

* { box-sizing: border-box; }

/* A COLUMN THE HEIGHT OF THE VIEWPORT, so the footer sits at the bottom of the window on
   a short page instead of floating halfway up it. `main` takes the slack.
   `100dvh` because on a phone `100vh` is the height with the address bar HIDDEN, so a
   viewport-tall page scrolls by exactly the height of the bar. `100vh` first for anything
   that does not know the dynamic units. */
body {
    margin: 0;
    background: var(--bg);
    color: var(--fg);
    font: 15px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
    min-height: 100vh;
    min-height: 100dvh;
    display: flex;
    flex-direction: column;
}

a { color: var(--accent); }

header.site {
    display: flex;
    /* CENTRE, NOT BASELINE, and the mark is why. An inline-flex wordmark containing an
       SVG has no text baseline of its own to align on -- a flex container takes its
       baseline from its first item, and an <svg> has none -- so under `baseline` the
       whole wordmark aligned on its bottom edge and sat a few pixels below the nav. */
    align-items: center;
    gap: 1.5rem;
    padding: 0.75rem 1.25rem;
    border-bottom: 1px solid var(--rule);
}

.wordmark {
    font-weight: 600;
    text-decoration: none;
    color: var(--fg);
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
}

/* `flex: none` so the mark keeps its 20px however narrow the header gets; the wordmark
   text is what should give way, and it does, because it is the flexible item. */
.wordmark .mark {
    flex: none;
    display: block;
}

main {
    max-width: 52rem;
    /* `auto` still centres horizontally as a flex item in a column. */
    margin: 0 auto;
    padding: 1.5rem 1.25rem 4rem;
    /* Takes the slack in the body column, which is what pins the footer to the bottom of
       a short page. ONE `main` RULE, not two: a second block setting only the flex
       properties works and is exactly the split that later gets edited in one place. */
    flex: 1 0 auto;
}

h1 { font-size: 1.5rem; margin: 0 0 0.25rem; }
h2 {
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted);
    margin: 2rem 0 0.5rem;
}

.count, .crumb { color: var(--muted); font-size: 0.85rem; margin: 0 0 1.25rem; }
.crumb { margin: 0 0 1rem; }

/* Result rows: name, then the shape, then the electronics (ux.md §2.3). */
ul.results { list-style: none; margin: 0; padding: 0; }

ul.results li {
    padding: 0.85rem 1rem;
    border: 1px solid var(--rule);
    border-radius: 6px;
    background: var(--card);
    margin-bottom: 0.5rem;
}

.name { font-weight: 600; font-size: 1.05rem; text-decoration: none; }
.name:hover { text-decoration: underline; }

/* The middot separators are drawn, not typed, so no facet has to carry punctuation. */
.facts { margin: 0.3rem 0 0; color: var(--muted); font-size: 0.9rem; }
.facts span + span::before { content: " · "; }

.summary { margin: 1rem 0 0; }

/* ux.md §5.3: ignorance gets its own rendering, never a blank cell. */
.unrecorded { color: var(--muted); font-style: italic; }

dl.spec {
    display: grid;
    grid-template-columns: 12rem 1fr;
    gap: 0.35rem 1rem;
    margin: 0;
}

dl.spec dt { color: var(--muted); }
dl.spec dd { margin: 0; }

ul.layouts, ul.sources { margin: 0; padding-left: 1.1rem; }
ul.sources { word-break: break-all; }

@media (max-width: 34rem) {
    dl.spec { grid-template-columns: 1fr; gap: 0 0; }
    dl.spec dd { margin-bottom: 0.6rem; }
}

/* ux.md §3.5: the silhouette is the only image the product has. It inherits
   currentColor, so dark mode costs nothing. Common scale means the width varies
   between designs on purpose — a 34-key board is meant to look smaller than a
   42-key one, so never stretch one to fill its box. */
ul.results li {
    display: flex;
    align-items: center;
    gap: 1rem;
}

/* Forgejo #13. ux.md §2.7 renders the whole page per facet click and swaps it in with
   hx-select, so every click pays parse plus layout for every silhouette in the corpus
   — measured at 471 ms on a 4x-throttled profile at 300 designs, against a 120 ms
   budget. `content-visibility: auto` lets the browser skip layout and paint for rows
   that are offscreen, and the swap drops to 164 ms.

   It is a rendering hint and nothing depends on it: every row is in the DOM either
   way, so a browser without support renders the list exactly as before, only slower.
   That is why this is a stylesheet change and not a template one.

   The intrinsic sizes are MEASURED, and they are CONTENT-box heights. That distinction
   is the whole bug this rule shipped with once: `contain-intrinsic-size` sizes the
   contents, so `box-sizing: border-box` does not apply to it and the 29px of padding
   and border are added on top. Feeding it the 107px border-box height over-estimated
   every row by 29px and the page lost 14.5% of its height as rows were laid out for
   real — a scrollbar that shrinks under your thumb. The content box is 78px, uniform
   across all 300 rows because .thumb reserves 208px; below the breakpoint the row
   restacks and content heights spread 102–164px, so the mobile rule takes their median.
   `auto` means the browser prefers the real height once it has laid a row out and falls
   back to these only before it ever has — so a wrong number costs scrollbar stability,
   never content. Measured drift after this correction is in the test below. */
ul.results li {
    content-visibility: auto;
    contain-intrinsic-size: auto 78px;
}

.thumb {
    flex: 0 0 auto;
    /* Reserve the widest a capped silhouette can be, so the text column starts at
       the same x on every row. Without this, common scale gives a ragged left edge
       on the names and the list becomes hard to scan. */
    width: 208px;
    display: flex;
    justify-content: center;
    /* Same strength as the detail page. The silhouette was briefly muted here on the
       theory that the names should lead, and that fought ux.md §3.5: at row size its
       whole job is instant shape recognition, and dimming it is exactly the wrong
       place to spend contrast. It is also the only image the product has, so it
       should not look like a disabled control on the page where people scan. */
    color: var(--fg);
}

.row-text { min-width: 0; }

.thumb svg { display: block; max-width: 100%; height: auto; }

.thumb.detail {
    width: auto;
    justify-content: flex-start;
    margin: 1rem 0 0;
}

@media (max-width: 40rem) {
    ul.results li {
        flex-direction: column;
        align-items: flex-start;
        /* Restacked rows are taller and no longer uniform: content heights 102–164px, median 134. */
        contain-intrinsic-size: auto 134px;
    }
    .thumb { width: auto; }
}

/* ux.md §2.7's paging links. Plain anchors, because they are the no-JS path and
   v0.5 loads no script at all — nothing here may depend on one. The exhausted end
   keeps its place rather than disappearing, so the row does not reflow between
   pages and "Next" stays where the reader last clicked it. */
nav.pages {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    margin: 1.5rem 0 0;
    padding-top: 1rem;
    border-top: 1px solid var(--rule);
}

nav.pages .where { color: var(--muted); font-size: 0.9rem; }
nav.pages .spent { color: var(--rule); }

/* Ticket 54's star count on a result row. Muted and after the name, because it explains
   the sort order rather than being a fact about the keyboard -- the facts live in the
   chips below. Small enough to be skipped by someone scanning names. */
.name-line {
    margin: 0;
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    flex-wrap: wrap;
}

.stars {
    color: var(--muted);
    font-size: 0.85rem;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* Applied filters, and the zero-recovery block beneath them. ux.md §2.6 Risk 1 makes an
   empty result a main path on this corpus rather than an edge case, so it is styled as
   content rather than as an error: no red, no warning icon, just the way onward. */
/* `center`, not `baseline`: a chip now contains an icon with no text baseline of its own,
   and under `baseline` the pills sat a couple of pixels off each other. */
.chips { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; margin: 0 0 1rem; }
.chip {
    background: var(--card);
    border: 1px solid var(--rule);
    border-radius: 999px;
    /* Less padding on the right than the left: the x brings its own. */
    padding: 0.15rem 0.3rem 0.15rem 0.7rem;
    font-size: 0.9rem;
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
}

/* ALWAYS VISIBLE, never revealed on hover. There is no hover on a touch screen, and a
   control that only appears to a mouse is a control half the readers never find. */
.chip-drop {
    display: inline-flex;
    color: var(--muted);
    border-radius: 999px;
    /* Padding out, margin back in: the tap target grows without the pill growing with it. */
    padding: 0.3rem;
    margin: -0.2rem 0;
}
.chip-drop:hover, .chip-drop:focus-visible { color: var(--fg); background: var(--rule); }
.chip-drop svg { display: block; }
.chips .clear { font-size: 0.85rem; color: var(--muted); }

.recover { border: 1px solid var(--rule); border-radius: 6px; padding: 1rem 1.2rem; }
.recover p { margin: 0 0 0.6rem; }
.recover ul { margin: 0; padding-left: 1.2rem; }
.recover li { margin: 0.25rem 0; }
.recover em { font-style: normal; font-weight: 600; }

/* The filter rail. Two columns above 60rem, stacked below — ux.md §2.8 turns filters
   into their own page on mobile, which is a later issue; until then they stack. */
.search { display: grid; grid-template-columns: 1fr; gap: 1.5rem; }
@media (min-width: 60rem) { .search { grid-template-columns: 15rem 1fr; } }

.rail { display: flex; flex-direction: column; gap: 1rem; }
fieldset.facet { border: 1px solid var(--rule); border-radius: 6px; margin: 0; padding: 0.6rem 0.8rem 0.8rem; }
fieldset.facet legend { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); padding: 0 0.3rem; }
fieldset.facet ul { list-style: none; margin: 0; padding: 0; }
fieldset.facet li { display: flex; justify-content: space-between; gap: 0.5rem; padding: 0.1rem 0; font-size: 0.92rem; }
fieldset.facet .n { color: var(--muted); font-variant-numeric: tabular-nums; }

/* Disabled, not hidden: legible but plainly inert, and it keeps its count so the row
   says "this exists and your other filters exclude it" rather than vanishing. */
fieldset.facet li.off { color: var(--muted); opacity: 0.55; cursor: not-allowed; }
fieldset.facet a.opt { text-decoration: none; }
fieldset.facet a.opt:hover { text-decoration: underline; }
fieldset.facet [aria-current="true"] { font-weight: 600; }

/* The divider between facts about the keyboard and facts about our database. Dashed,
   because it is a change of subject rather than a separator between peers. */
hr.divider { border: 0; border-top: 1px dashed var(--rule); margin: 0.5rem 0; }

/* The rail, folded away on a phone.
   ------------------------------------------------------------------------------
   `.search` is one column below 60rem and `.rail` precedes `.results-col` in the
   DOM, so on a narrow screen the reader met the whole rail first: measured at
   ~1,540px, which is roughly four screenfuls of filters before the first keyboard.
   Licence alone is 15 options.

   The order below is deliberate and is the whole fix: the CONTROL comes first, then
   the rail it opens, then the results. Collapsed -- the default -- that is one row
   of chrome and then keyboards. Expanded, the rail sits directly under the control
   that opened it rather than after 100 results.

   CSS only, because v0.5 loads no script. A `<details>` would be the better element
   but cannot be reliably forced open by CSS for the desktop side, where this control
   must disappear entirely and the rail must always show. */
.rail-toggle { position: absolute; opacity: 0; pointer-events: none; }
.rail-open { display: none; }

@media (max-width: 59.999rem) {
    .rail-open {
        order: 1;
        display: inline-flex;
        align-items: center;
        gap: 0.4rem;
        align-self: start;
        padding: 0.4rem 0.75rem;
        border: 1px solid var(--rule);
        border-radius: 6px;
        background: var(--card);
        color: var(--fg);
        font-size: 0.92rem;
        cursor: pointer;
    }
    .rail-open .n {
        color: var(--muted);
        font-variant-numeric: tabular-nums;
    }
    .rail-open .chev { transition: transform 0.12s ease; }
    .rail-toggle:checked ~ .rail-open .chev { transform: rotate(90deg); }

    /* Hidden, not merely moved: a rail below the results is still 1,540px the reader
       has to scroll past to reach the pager. */
    .rail { order: 2; display: none; }
    .rail-toggle:checked ~ .rail { display: flex; }

    .results-col { order: 3; }

    /* The control is the one thing that must stay reachable while scrolling a long
       result list -- otherwise refining a search means scrolling back to the top. */
    .rail-open { position: sticky; top: 0.5rem; z-index: 2; }
}

/* Keyboard focus must be visible on the label, since the input it drives is offscreen. */
.rail-toggle:focus-visible ~ .rail-open { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The search box. Sits above the two-column grid because it applies to both halves,
   and full-width because a name is the one thing a reader arrives already knowing. */
form.find {
    display: flex;
    gap: 0.5rem;
    margin: 0 0 1.25rem;
}
form.find input[type="search"] {
    flex: 1 1 auto;
    min-width: 0;
    padding: 0.45rem 0.7rem;
    border: 1px solid var(--rule);
    border-radius: 6px;
    background: var(--card);
    color: var(--fg);
    font: inherit;
}
form.find input[type="search"]:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}
form.find button {
    padding: 0.45rem 0.9rem;
    border: 1px solid var(--rule);
    border-radius: 6px;
    background: var(--card);
    color: var(--fg);
    font: inherit;
    cursor: pointer;
}
form.find button:hover { border-color: var(--accent); }


/* ---------------------------------------------------------------------------
   The home page. ux.md §2.10 designs it as three doors; two of the three are
   not buildable in v0.5 (Door C is out of scope, contribute waits on the
   report surface), so what is here is one door done properly plus evidence.
   --------------------------------------------------------------------------- */

.lede {
    font-size: 1.75rem;
    line-height: 1.15;
    letter-spacing: -0.02em;
    margin: 0 0 0.35rem;
    text-wrap: balance;
}

.lede-sub {
    color: var(--muted);
    margin: 0 0 1.5rem;
    font-size: 1.05rem;
}

.home-find { margin-bottom: 0.9rem; }

/* The entry points. Words, not counts -- a count printed here goes stale on the
   next import, and the rail one click away carries live self-excluded ones. */
.doors {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.4rem 0.75rem;
    margin: 0 0 2.5rem;
    font-size: 0.95rem;
}
.doors-label { color: var(--muted); }
.doors a { text-decoration: none; border-bottom: 1px solid currentColor; }
.doors a:hover { border-bottom-width: 2px; }

.strip-head {
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted);
    font-weight: 600;
    margin: 0 0 0.75rem;
}

/* AUTO-FIT RATHER THAN A FIXED COLUMN COUNT, because the silhouettes are wildly
   different widths -- a 12-key macropad and a 130-key Unison sit in the same
   strip -- and a fixed grid would give the macropad a card of mostly air. */
.strip {
    list-style: none;
    margin: 0 0 1.5rem;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
    gap: 0.75rem;
}

.strip-card {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    height: 100%;
    padding: 0.85rem 0.9rem 0.9rem;
    border: 1px solid var(--rule);
    border-radius: 6px;
    background: var(--card);
    color: var(--fg);
    text-decoration: none;
}
.strip-card:hover { border-color: var(--accent); }

/* The whole card is one link, so the silhouette must not swallow the pointer as
   a separate target -- and `.thumb`'s 208px reservation is for the list's shared
   left edge, which a grid of cards does not have.

   NOTHING IS SCALED TO FIT, and that is the point rather than an omission. ux.md:551
   requires a "consistent scale within a viewport": every silhouette is drawn at a
   fixed 8px per key unit, so a 12-key macropad IS smaller than a 60-key split and
   the sizes are comparable at a glance. Fitting each one to its card would have made
   Kobold (33x32) render exactly as tall as Sofle (132x46) and quietly destroyed that.
   Measured across the eight featured: 33-139px wide, 32-49px tall, all inside the
   ~179px of card and the 56px box, so no scaling is needed to make them fit. */
.strip-card .thumb {
    width: auto;
    justify-content: center;
    align-items: center;
    min-height: 56px;
    pointer-events: none;
}

.strip-name { font-weight: 600; font-size: 0.95rem; }

.strip-facts {
    color: var(--muted);
    font-size: 0.82rem;
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem 0.6rem;
}

.strip-more { margin: 0; }
.more { font-weight: 600; text-decoration: none; }
.more:hover { text-decoration: underline; }


/* The correction route, on every page. ux.md §2.10 wants a real report-a-problem
   surface as the third door; this is the cheap stand-in until that exists, and a
   mailto is deliberate -- a form needs decisions (per-field or per-design, where
   reports land, anti-spam) that a mailto does not. */
/* FULL-BLEED BAND, CONSTRAINED CONTENT. Bordering only the 52rem column made the rule
   stop mid-page and read as another divider inside the article rather than as the end of
   it. A footer has to reach both edges to be one. */
.site-foot {
    /* No top margin: on a short page the space above the footer is `main` growing, and on
       a long one the content already ends with its own spacing. A margin here added a gap
       on top of both. */
    flex: none;
    background: var(--sunk);
    border-top: 1px solid var(--rule);
    color: var(--muted);
    font-size: 0.85rem;
}

.site-foot > div {
    max-width: 52rem;
    margin: 0 auto;
    padding: 1.1rem 1.25rem;
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem 1.25rem;
}

/* The address is the only thing here anyone clicks, so it is the only thing that is not
   muted -- but it stays the foreground rather than the accent, because a bright link in
   a recessed band pulls the eye off the page content above it. */
.site-foot a { color: var(--fg); }
.site-foot a:hover { color: var(--accent); }



/* Who published a design. Sits under the name, above the drawing, in the muted register
   the facts use -- it is context for the keyboard, not a fact about the hardware. */
.byline { margin: 0.1rem 0 0; color: var(--muted); font-size: 0.9rem; }
.byline a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.byline a:hover { color: var(--accent); }
