﻿/*
   Stellaris Empire Manager.

   A dark palette in the game's register, without imitating its interface: this is a tool for
   working on empires, not a reproduction of the game's own designer.

   Everything that can live here does. The fourteen style attributes left in the components are all
   geometry computed at render — a flag's cut and emblem placement, a portrait's layer offsets, a
   room's banner, a budget bar's fill, an icon grid's track size — numbers that come from the game's
   own files per empire and cannot be written down in advance. They are not leftovers to tidy away.
*/

:root {
    --bg: #0d1117;
    --bg-panel: #151b24;
    --bg-raised: #1c242f;
    --bg-hover: #232d3a;
    --border: #2a3543;
    --border-strong: #3a4757;

    --text: #dbe2ea;
    --text-dim: #8b97a6;
    --text-faint: #5d6875;

    --accent: #4a9fd4;
    --accent-dim: #2d6a91;
    --gold: #e0c14a;
    --good: #5ec46a;
    --good-dim: #35743d;
    --bad: #d95c5c;
    --warn: #d9a45c;

    /*
       Three radii, which is one per kind of thing rather than one per author. The badges had been
       8px, 8px, 8px, 9px and 999px — five roundings for one shape, of which only the last was
       actually a pill. --radius-pill makes them all one, and makes them right.
    */
    --radius-sm: 3px;      /* a swatch, a pip, anything under about twenty pixels */
    --radius: 6px;         /* a card, a panel, a tile */
    --radius-pill: 999px;  /* a badge or a chip, rounded to its own height */

    /*
       Spacing, named the way the type scale is so the two read alike.

       The set is closed: a value that is not on it is either an optical adjustment below about six
       pixels — the 1px that centres a chip's text against its dot, the 3px offset under a blocked
       option's reason — or true geometry, meaning an aspect ratio or a border. Everything else is a
       step here or a control size below.

       These came in by a third. This is a dense tool that was not laid out like one: the header and
       the content-pack bar took 112 pixels of a 900-pixel screen before a single control appeared,
       and a text field cost 57 of them to show one line and its label. Nothing was individually
       wrong; there was simply a step more air in every one of a hundred and sixty places, and it
       compounds down a page.
    */
    --space-2xs: 2px;
    --space-xs: 3px;
    --space-sm: 4px;
    --space-md: 6px;
    --space-lg: 10px;
    --space-xl: 12px;
    --space-2xl: 18px;
    --space-3xl: 28px;

    /*
       What a control is, as opposed to the space around it.

       These used to be literals, on the argument that a control's geometry answers a specific shape
       rather than stepping through a rhythm. That was true and it was also what made the interface
       impossible to retune: fifty-five hard-coded pixel sizes do not move when the spacing does, so
       tightening the gaps alone would have drawn the same buttons closer together and looked worse
       than either end. Named here, the density of the whole application is these eleven numbers.

       They are not on the spacing scale and should not be read as though they were. A swatch is 24
       because a swatch is 24, not because it is two steps of something.

       The heights are stated rather than left to fall out of padding, which is how they used to
       work. A text box was 35 pixels because its padding happened to be 6 — so the height of every
       control in the application was a side effect of a spacing token, and a later change to the
       spacing would have quietly retuned all of them. Stated, a row of controls is level by
       construction and a field's total height is a sum you can write down.
    */
    --control-h: 29px;      /* a text box or a select: one line of the body, and the unit of a form */
    --label-line: 16px;     /* the caption above one, which is one line by construction */
    --tab-h: 30px;          /* an item in the header, and in the content-pack bar beside the packs */
    --icon-btn: 28px;       /* a button that is only a glyph */
    --icon: 24px;           /* an option's icon, beside its name */
    --icon-sm: 18px;        /* the same icon inside a chip, where the text leads */
    --tile: 34px;           /* an icon on its own as the whole control: an ethic, a civic */
    --swatch: 24px;         /* one of two hundred and sixteen flag colours */
    --dlc: 28px;            /* a content pack in the bar across the top */
    --combo-toggle: 24px;   /* the arrow inside a text box, which the box leaves room for */
    --stepper: 24px;        /* the arrows either side of a numbered choice */
    --round-btn: 30px;      /* the die, and the four gender buttons */
    --track-h: 18px;        /* the bar a budget is drawn in */
    --face-w: 40px;         /* a species' portrait in the empire list */
    --face-h: 50px;
    --face-lg-w: 80px;      /* and in the panel that shows one empire, where it stretches to the text */
    --face-lg-h: 96px;

    /* The distance between one section and the next, and the most-used spacing in the sheet. */
    --gap: var(--space-lg);

    /*
       How tall the header is, and therefore how far down the page anything sticky has to stop.

       The header sticks at the very top, so a panel sticking at --gap stuck underneath it and spent
       the whole scroll half-covered by the brand. Stated here and used by both the header and the
       things that clear it, so the two cannot drift; the header takes this as its height rather than
       growing to its contents, which is what let them drift in the first place.

       Derived from what it holds rather than asserted beside it. The header carries four tabs and,
       on a phone, the burger — no icon buttons, which live in the showcase — so its height is a tab
       plus a step of space above and below. Asserted, the two could disagree and the header would
       clip its own buttons rather than grow, because it takes this as a height and not a minimum.
    */
    --header-height: calc(var(--tab-h) + var(--space-lg) * 2);
    --sticky-top: calc(var(--header-height) + var(--gap));

    /*
       Two more surfaces that are not steps in the elevation ladder. --bg-void is below the page
       rather than on it: the empty space behind a portrait or a room, where the game shows nothing
       at all. --shadow-popup lifts a menu off whatever it covers.
    */
    --bg-void: #05070c;
    --shadow-popup: 0 6px 20px rgba(0, 0, 0, 0.45);

    /*
       Six steps, and every one an integer number of pixels at the default root. A ratio would be
       tidier on paper and worse on a screen: at these sizes 11px is crisper than 10.7px, and the
       difference between 11 and 12 is doing real work.

       There had been sixteen sizes, seven of them inside a two-pixel band, which is not a scale but
       a series of separate decisions. Worse, they were wrong in the same direction: see the note on
       the root below.

       Re-derived when the root came down a pixel, so the whole numbers stayed whole. The bottom step
       is now 10px, which is small — it is worth saying plainly. It carries a cost pip, a badge count
       and an overline, all of which are a digit or a word read next to the thing they belong to,
       never a sentence. Nothing anyone has to read at length is set below 11.
    */
    --text-xs: 0.769rem;    /* 10px — overlines, badge counts, cost pips */
    --text-sm: 0.846rem;    /* 11px — anything subordinate to the body */
    --text-md: 1rem;        /* 13px — the body; the middle of the scale */
    --text-lg: 1.154rem;    /* 15px — a panel's title, or a readout's */
    --text-xl: 1.308rem;    /* 17px — a heading over a region of the page */
    --text-2xl: 1.538rem;   /* 20px — the page's own title */
}

/*
   Every rem in this file is measured from here, and until now nothing set it — so they were all
   measured from the browser's 16px while the body sat at 14px. Nine rules asked for 0.9rem meaning
   "smaller than the body" and got 14.4px, which is larger. The hints were the worst of it: every
   one of them was bigger than the text it was subordinate to.

   A percentage rather than 14px, so that a reader who has raised their browser's own base size
   still gets a proportionally larger application rather than being overruled. That is also why the
   compaction moved this number rather than replacing it: a pixel here would have quietly overruled
   everyone who had asked for something larger.
*/
html {
    font-size: 81.25%;
}

* {
    box-sizing: border-box;
}

body {
    margin: 0;
    background: var(--bg);
    color: var(--text);
    font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
    font-size: var(--text-md);
    line-height: 1.5;
}

/*
   The margin stays in em so that it scales with each heading's own size rather than being a fixed
   distance under a variable thing.
*/
h1, h2, h4, h5 {
    margin: 0 0 0.5em;
    font-weight: 600;
    letter-spacing: 0.01em;
}

/*
   h3 has no rule because the app has no h3. It had one, sized like the rest, which was a size for
   an element that is never written — and a heading that appears later would then inherit a decision
   nobody made for it. The four that exist are here; a fifth would want a reason first.

   h1 is the 404 page's, and the only place --text-2xl is reached.
*/
h1 { font-size: var(--text-2xl); }
h2 { font-size: var(--text-xl); }

/*
   h4 is seventeen of this app's twenty-three headings and had no size rule at all, so it rendered
   four different ways depending on what it happened to sit inside. This is its size; the overline
   treatment further down overrides it where a heading is labelling a group of controls rather than
   naming a thing.
*/
h4 { font-size: var(--text-lg); }
h5 { font-size: var(--text-sm); }

/*
   The overline: a small capitalised label naming the group of controls beneath it, rather than the
   title of a thing. Six places had reinvented it independently — four sizes, two letter-spacings
   and two colours for one idea — so it is written once here and the two places that want a
   different colour say only that.
*/
.sem-problem .area,
.sem-effects-penalty .heading,
.sem-combined h4,
.sem-subsections > section > h4,
.sem-subsections > section > .heading > h4,
.sem-heading-row > h4,
.sem-showcase-chips .heading {
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-dim);
}

a {
    color: var(--accent);
    text-decoration: none;
}

a:hover {
    text-decoration: underline;
}

code {
    font-family: "Cascadia Mono", Consolas, monospace;
    font-size: 0.9em;
    color: var(--text-dim);
}

/* ---------------------------------------------------------------- layout */

.sem-app {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
}

.sem-header {
    display: flex;
    align-items: center;
    gap: var(--gap);
    height: var(--header-height);
    padding: 0 var(--space-xl);
    background: var(--bg-panel);
    border-bottom: 1px solid var(--border);
    position: sticky;
    top: 0;
    z-index: 10;
}

.sem-header .brand {
    font-weight: 600;
    letter-spacing: 0.02em;
    color: var(--text);
    margin-right: auto;
}

/*
   The file's own controls, at the end of the header.

   They wrap rather than overflow, and the header grows with them: at a phone's width the brand takes
   one line and four controls take the next, which is better than a row that scrolls sideways or one
   that clips whichever button happens to be last.
*/
.sem-header-actions {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: var(--space-md);
}

/*
   ---- the window's own furniture, where the app draws the window

   The desktop has no title bar, so the three things a title bar did are here instead: a band that
   moves the window, three buttons at the corner, and strips at the edges that resize it. None of
   these elements is rendered in a browser at all, so none of this needs a host to switch it off.
*/

/* Whatever the header is not using, which is the same shape a title bar was. */
.sem-drag-region {
    flex: 1 1 auto;
    align-self: stretch;
    min-width: 0;
}

/*
   Beside the header rather than inside it, and fixed to the corner.

   Inside, they could not have been raised: the header is a stacking context at z-index 10 and every
   dialog sits at 100, so an open question would have covered the only way this window has of being
   closed. Out here they stay reachable, which is the difference between a dialog and a dead end.
*/
.sem-window-buttons {
    position: fixed;
    top: 0;
    right: 0;
    z-index: 300;
    display: flex;
    height: var(--header-height);
}

/* And the header makes room, since the buttons no longer take any in its own row. */
.sem-app.framed .sem-header { padding-right: calc(3 * 46px + var(--space-md)); }

/*
   Written through the container, which is what carries them over the general button rules further
   down the file: those reach a hover at the same weight, and this one is written first.
*/
.sem-window-buttons > .sem-window-button {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 46px;
    padding: 0;
    background: none;
    border: 0;
    border-radius: 0;
    color: var(--text-dim);
    cursor: pointer;
}

.sem-window-buttons > .sem-window-button:hover {
    background: var(--bg-hover);
    color: var(--text);
}

/* The one control in the app worth colouring, in the one colour every window agrees on. */
.sem-window-buttons > .sem-window-button.close:hover {
    background: #c42b1c;
    color: #fff;
}

/*
   The borders.

   Thin, because the right-hand strip lies over the scrollbar and a wide one would make the bar hard
   to take hold of; the corners are larger, as a window's corners have always been. Above everything,
   including a dialog, since a window that cannot be resized while a question is open would be the
   same trap the buttons above are out here to avoid.
*/
.sem-resize > * {
    position: fixed;
    z-index: 1000;
}

.sem-resize > .n { top: 0; left: 0; right: 0; height: 4px; cursor: ns-resize; }
.sem-resize > .s { bottom: 0; left: 0; right: 0; height: 4px; cursor: ns-resize; }
.sem-resize > .w { top: 0; bottom: 0; left: 0; width: 4px; cursor: ew-resize; }
.sem-resize > .e { top: 0; bottom: 0; right: 0; width: 4px; cursor: ew-resize; }

.sem-resize > .nw,
.sem-resize > .ne,
.sem-resize > .sw,
.sem-resize > .se {
    z-index: 1001;
    width: 12px;
    height: 12px;
}

.sem-resize > .nw { top: 0; left: 0; cursor: nwse-resize; }
.sem-resize > .ne { top: 0; right: 0; cursor: nesw-resize; }
.sem-resize > .sw { bottom: 0; left: 0; cursor: nesw-resize; }
.sem-resize > .se { bottom: 0; right: 0; cursor: nwse-resize; }

/* A file that would not open. Full width under the header, since it is a sentence and not a chip. */
.sem-header-error,
.sem-header-note {
    margin: 0;
    padding: var(--space-md) var(--space-xl);
    background: var(--bg-panel);
    border-bottom: 1px solid var(--border);
}

.sem-header-error { color: var(--bad); }

/*
   A way out of a problem, offered in the sentence describing it. A whole button beside a line of
   text would read as a separate thing to consider; this reads as the end of the sentence, which is
   what it is - "connect again" is the rest of "your sign-in has ended".
*/
.sem-header-error > button.sem-inline {
    margin-left: var(--space-sm);
    padding: 0;
    background: none;
    border: none;
    color: inherit;
    font: inherit;
    text-decoration: underline;
    cursor: pointer;
}

/*
   The same line, for something that is true rather than wrong: a file downloaded because this
   browser has no save dialog, or one opened under a name the game will not recognise. Both used to
   be red, which told the player they had made a mistake they had not made — and left real failures
   with nothing to distinguish them.
*/
.sem-header-note { color: var(--text-dim); }

/*
   The burger, which exists only on a narrow screen: four items and a brand do not fit one phone-wide
   line, and wrapping them onto a second made the header two rows tall on every page.

   A checkbox rather than a script. The menu has one piece of state, it is "open", and the platform
   already has a control that holds exactly that — which means it works before the app has finished
   loading and needs nothing torn down afterwards.
*/
.sem-header-burger { display: none; }

/*
   The checkbox itself is never seen, and must still be reachable. display:none takes an input out of
   the tab order and out of the accessibility tree altogether, which left the whole menu — Create,
   Import, Export — openable by pointer and by nothing else. Hidden by size instead, so it keeps its
   place in the tab order and a screen reader still finds it.
*/
.sem-visually-hidden,
.sem-header-toggle,
.sem-dlc-toggle {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: 0;
    padding: 0;
    border: 0;
    overflow: hidden;
    white-space: nowrap;
    clip-path: inset(50%);
}

/* And the label it drives shows focus, since the box that has the focus cannot be seen. */
.sem-header-toggle:focus-visible + .sem-header-burger,
.sem-dlc-toggle:focus-visible + .sem-dlc-summary {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/*
   Import is a label over a hidden file input, so the focus lands on the input and the ring has to be
   drawn on the label in front of it. The label precedes the input in the markup, which is why this
   reaches backwards.
*/
.sem-header-actions:has(#designs-file:focus-visible) label[for="designs-file"] {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

@media (max-width: 720px) {
    .sem-header {
        height: auto;
        flex-wrap: wrap;
        padding: var(--space-md) var(--space-lg);

        /* Two rows tall when open, so nothing below may stick to --header-height here. */
        position: static;
    }

    .sem-header-burger {
        display: flex;
        align-items: center;
        justify-content: center;
        width: var(--tab-h);
        height: var(--tab-h);
        border: 1px solid var(--border);
        border-radius: var(--radius);
        color: var(--text);
        cursor: pointer;
    }

    .sem-header-burger:hover { background: var(--bg-hover); }

    .sem-header-actions {
        display: none;
        width: 100%;
        flex-direction: column;
        align-items: stretch;
        gap: var(--space-xs);
        padding-top: var(--space-md);
    }

    .sem-header-toggle:checked ~ .sem-header-actions { display: flex; }

    /* Full-width rows, because a column of centred tabs reads as a list of one-word riddles. */
    .sem-header-actions > * {
        width: 100%;
        text-align: left;
    }

    /*
       Save is wrapped now, so the rule above reaches the wrapper and stops one short of the button
       inside it. Both are given the width, which leaves this column exactly as it was - and the
       chip is never here anyway, since only a host with a file to keep in step draws one.
    */
    .sem-save > button.sem-tab {
        width: 100%;
        padding-right: var(--space-xl);
    }
}

/*
    Wide, because there is a lot to show and the page was leaving a third of a modern screen empty.
    Capped rather than unbounded: prose still has to be readable, and every list inside is a grid
    that would otherwise keep growing columns until a row of civics ran off the eye's reach.
*/
.sem-main {
    flex: 1;
    padding: var(--space-xl);
    max-width: 1800px;
    width: 100%;
    margin: 0 auto;
}

.sem-row {
    display: flex;
    gap: var(--gap);
    align-items: center;
    flex-wrap: wrap;
}

/*
    Two things a row is asked for often enough to be worth naming.

    .top for a row whose items are different heights and should line up along their tops rather than
    their middles — a portrait beside a column of fields. .nowrap for a pair that means nothing apart
    and should overflow together rather than stack: the two halves of a homeworld's coordinates.
*/
.sem-row.top { align-items: flex-start; }
.sem-row.nowrap { flex-wrap: nowrap; }

/*
    An input with a die beside it, inside a field that is one column of a name row. The row's own
    gap is the distance between two fields, and spending it between an input and the button attached
    to it made the homeworld and star names visibly narrower than the plain select next to them.
    Close the gap and let the input have everything the button does not.
*/
.sem-field > .sem-row.nowrap {
    gap: var(--space-xs);
}

.sem-field > .sem-row.nowrap > :first-child {
    flex: 1;
    min-width: 0;
}

/*
    A wider gap where the row holds two labelled groups of controls rather than two controls. The
    space between the groups has to beat the space inside them or they read as one long strip of
    buttons — gender and leader class, side by side, are six icons that mean two different things.
*/
.sem-row.wide { gap: var(--space-2xl); }

/* ---------------------------------------------------------------- controls */

button, .sem-button {
    font: inherit;
    color: var(--text);
    background: var(--bg-raised);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    padding: var(--space-sm) var(--space-lg);
    cursor: pointer;
    transition: background 0.12s, border-color 0.12s;
}

button:hover:not(:disabled), .sem-button:hover {
    background: var(--bg-hover);
    border-color: var(--accent-dim);
}

button:disabled {
    opacity: 0.45;
    cursor: not-allowed;
}

button.primary {
    background: var(--accent-dim);
    border-color: var(--accent);
}

button.primary:hover:not(:disabled) {
    background: var(--accent);
}

/*
   Save, and only Save. The accent is the app's ordinary emphasis and it is spent all over the
   editor - on the way across, on a chosen option, on the focus ring - so the one button that
   commits the work was the same blue as the one that changed pages. Green says which of them puts
   the empire in the file.
*/
button.good {
    background: var(--good-dim);
    border-color: var(--good);
}

button.good:hover:not(:disabled) {
    background: var(--good);
    color: var(--bg);
}

/*
    Marked at rest, not only under the pointer. There is one of these — Delete — and it was
    indistinguishable from "← Empires" and "Copy link" beside it until the cursor was already on
    top of it, which is the wrong moment to find out.
*/
button.danger {
    border-color: var(--bad);
    color: var(--bad);
}

button.danger:hover:not(:disabled) {
    background: var(--bad);
    border-color: var(--bad);
    color: var(--bg);
}

/*
    One focus ring for everything. There had been two rules across some eight hundred buttons, and
    one of them turned the outline off and substituted a background identical to :hover — so a
    keyboard user could not tell where they were from where the mouse had last been.
*/
:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}

/*
   Scrollbars, in the app's own colours rather than the browser's.

   Both spellings, because they are read by different browsers and not by the same one: where the
   standard properties are understood they win outright and the webkit rules below are ignored,
   which is the right way round - the standard pair is the one that will still be here later.

   Stated on the root so every box inherits it, including the ones inside a modal.
*/
:root {
    scrollbar-width: thin;
    scrollbar-color: var(--border-strong) transparent;
}

::-webkit-scrollbar {
    width: 10px;
    height: 10px;
}

::-webkit-scrollbar-track {
    background: transparent;
}

::-webkit-scrollbar-thumb {
    background: var(--border-strong);
    border: 2px solid transparent;
    border-radius: 999px;

    /* So the border reads as a gap rather than as a paler edge to the thumb. */
    background-clip: padding-box;
}

::-webkit-scrollbar-thumb:hover {
    background: var(--text-faint);
    background-clip: padding-box;
}

::-webkit-scrollbar-corner {
    background: transparent;
}

/*
   A field whose label sits beside its control rather than over it, for the short ones where a
   stacked label costs more height than the answer is worth.
*/
.sem-field.inline {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: var(--space-md);
}

.sem-field.inline > label {
    flex: 0 0 auto;

    /* The gap is doing the spacing now, and the label's own would push it off centre. */
    margin-bottom: 0;
}

.sem-field.inline > select,
.sem-field.inline > input {
    flex: 1;
    min-width: 0;
}

input[type="text"], input[type="number"], select, textarea {
    font: inherit;
    color: var(--text);
    background: var(--bg);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    padding: var(--space-sm) var(--space-lg);
    width: 100%;
}

/*
    One line, so the height is stated and the padding only holds the text off the sides. A textarea
    is the exception and keeps growing by its padding, which is the whole point of one.
*/
input[type="text"], select {
    height: var(--control-h);
    padding-top: 0;
    padding-bottom: 0;
}

/*
    A field lights its own border when focused, which is the convention and reads better on a box
    that is already outlined than a second line drawn around the first.

    The ring is dropped only for a pointer. Written as :not(:focus-visible) rather than as a plain
    outline:none, because that suppressed it for the keyboard too — and these selectors outrank the
    global rule, so a text field was the one place tabbing left no mark beyond a one-pixel hue.
*/
input[type="text"]:focus, select:focus, textarea:focus {
    border-color: var(--accent);
}

input[type="text"]:focus:not(:focus-visible),
select:focus:not(:focus-visible),
textarea:focus:not(:focus-visible) {
    outline: none;
}

/*
    The smallest gap on the scale, because a label and the box it names are one thing. Any more and
    the pair stops reading as a unit; this is the last two pixels of a field that costs 50.
*/
label {
    display: block;
    color: var(--text-dim);
    font-size: var(--text-sm);
    line-height: var(--label-line);
    margin-bottom: var(--space-2xs);
}

.sem-field {
    margin-bottom: var(--gap);
}

/* The last field in a row, or one whose spacing its container is already providing. */
.sem-field.flush { margin-bottom: 0; }

/* A field set off from whatever it follows, where it is not one of a stack that already spaces. */
.sem-field.spaced { margin-top: var(--gap); }

/*
   What a tradition tree holds, under the tree's own description.

   Numbered, because they are taken in this order and the order is part of what a tree is: the one
   that opens it, the five picks, the one that finishes it. Indented off the number so a tradition's
   own paragraph and its modifiers read as belonging to it rather than to the list.
*/
.sem-plan-contents {
    display: flex;
    flex-direction: column;
    gap: var(--space-md);

    margin: var(--space-md) 0 0;
    padding-left: var(--space-xl);
}

.sem-plan-contents > li {
    display: flex;
    flex-direction: column;
    gap: var(--space-2xs);
}

.sem-plan-contents .head {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
}

.sem-plan-contents .head img {
    width: var(--icon-sm);
    height: var(--icon-sm);
}

.sem-plan-contents .head .name {
    color: var(--text);
    font-weight: 600;
}

/*
   A value the plan works out rather than one the player sets.

   What the plan names, in the order it means to take them.

   A list rather than a stack of buttons, because the order carries meaning: twenty-five ascension
   perks are gated on how many come before them, so where one sits is part of the plan. The numbers
   are only drawn where that is true - the civics have no order, and numbering them would say
   something about a government that is not so.
*/
.sem-plan-order {
    margin: 0;
    padding: 0;
    list-style: none;
    counter-reset: plan;
}

.sem-plan-slot {
    display: flex;
    align-items: center;
}

.sem-plan-order.numbered > .sem-plan-slot {
    counter-increment: plan;
}

.sem-plan-order.numbered > .sem-plan-slot::before {
    content: counter(plan);

    /* Wide enough for two digits so the names line up, and right-aligned within it so they still
       line up once the count passes nine. */
    flex: 0 0 auto;
    width: 1.5em;
    padding-left: var(--space-sm);
    color: var(--text-faint);
    font-size: var(--text-sm);
    font-variant-numeric: tabular-nums;
    text-align: right;
}

/*
   The handle, on the row rather than in a corner of it.

   Faint rather than hidden until hovered, the way the empire cards' is. Those are large and few and
   a grip appearing on hover is a nice surprise; these are dense rows in a scrolling box, and a
   control that cannot be seen until the pointer is already on it is one a reader has to hunt for.
*/
.sem-plan-grip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;

    /* Both, or it is as tall as one line of text - thirteen pixels - because the row centres it
       rather than stretching it. The empire cards' grip has always set the pair. */
    width: var(--icon-sm);
    height: var(--icon-sm);
    padding: 0;
    background: none;
    border: none;
    color: var(--text-faint);
    font: inherit;
    line-height: 1;
    cursor: grab;
    touch-action: none;
    opacity: 0.5;
}

.sem-plan-slot:hover .sem-plan-grip,
.sem-plan-grip:focus-visible {
    color: var(--text);
    opacity: 1;
}

.sem-plan-grip:active { cursor: grabbing; }

/*
   The rows while one of them is being carried. Every row is placed by a transform the script sets,
   so the held one follows the pointer and the rest slide into the places they would take if it were
   dropped now - the answer is visible before the drop rather than after it. The transition is on
   the others and not on the held one, which has to keep up with the pointer exactly.
*/
.sem-plan-order.sorting > .sem-plan-slot {
    transition: transform 160ms ease;
}

.sem-plan-order.sorting > .sem-plan-slot.lifted {
    transition: none;
    position: relative;
    z-index: 2;
}

.sem-plan-slot.lifted .sem-trait-row {
    background: var(--bg-hover);
    box-shadow: 0 6px 18px rgb(0 0 0 / 45%);
    cursor: grabbing;
}

@media (prefers-reduced-motion: reduce) {
    .sem-plan-order.sorting > .sem-plan-slot { transition: none; }
}

/*
   A civic the empire cannot give up.

   Dimmed like a blocked row, but it is not blocked - it is simply not a choice. The game's own
   `modification = no` says so, and the plan spends a slot on it either way, so hiding it would
   leave the budget unexplained.
*/
.sem-trait-row.locked {
    opacity: 0.55;
    cursor: default;
    font-style: italic;
}

/*
   A plan as the game will show it.

   Drawn like the box it stands in for rather than like a quotation, because that is what it is: the
   biography, written here instead of typed. The line breaks are the plan's own, so they are kept.
*/
.sem-plan-preview {
    padding: var(--space-sm) var(--space-lg);
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    color: var(--text-dim);
    font-size: var(--text-sm);
    white-space: pre-wrap;
}

/* ---------------------------------------------------------------- tabs */


/*
   A header item. Of the four, one is a link, two are buttons and the fourth is the label of a hidden
   file input — a file picker cannot be opened from code, so that control has to be a label.

   Every one of those is a different element with different defaults, so everything is stated rather
   than inherited, and each selector is written to out-weigh the plain "button" rules above. Left as
   they were, Create and Export took the button hover — a raised background and an accent border —
   while Empires and Import took the tab hover, so two of the four lit up differently from the other
   two. Flex with a centred cross axis for the same reason: a label and a button do not sit on the
   same baseline by themselves.
*/
.sem-tab,
button.sem-tab {
    display: inline-flex;
    align-items: center;
    height: var(--tab-h);

    /* A label carries the four pixels every field's caption gets, and centring its margin box
       lifted Import two pixels above the three items beside it. */
    margin: 0;
    padding: 0 var(--space-xl);
    background: none;
    border: none;
    border-bottom: 2px solid transparent;
    border-radius: 0;
    color: var(--text-dim);
    font: inherit;
    white-space: nowrap;
    cursor: pointer;
}

.sem-tab:hover,
button.sem-tab:hover:not(:disabled) {
    background: var(--bg-panel);
    border-color: transparent;
    color: var(--text);
}

.sem-tab:disabled,
button.sem-tab:disabled {
    opacity: 1;
    color: var(--text-faint);
    cursor: default;
}

button.sem-tab:disabled:hover { background: none; }

.sem-tab.active {
    color: var(--accent);
    border-bottom-color: var(--accent);
}

/*
   Save, and the switch that says whether saving is something you ask for.

   One group, read left to right, so the word and the state that changes what it means are not
   separated by the row's own gap. The button gives up its right-hand padding to the chip, which is
   the whole trick: without that the two sit a tab's width apart and read as unrelated.
*/
.sem-save {
    display: inline-flex;
    align-items: center;
}

.sem-save > button.sem-tab { padding-right: var(--space-sm); }

/*
   A chip, because that is what this app calls a state a thing is in - same pill, same raised
   ground, same border - rather than a tab, which is a thing to press. The checkbox stays visible
   instead of being replaced by a tick or an icon: it is the control, and hiding it would leave the
   only way of telling on from off as a colour.

   Sized to the word beside it rather than to itself. A chip is a note on the verb it qualifies, and
   one standing taller than the verb reads as the louder of the two - so the padding is a hairline,
   the box is shrunk to match, and the whole thing comes out at the height of the text in Save.
*/
.sem-save > .sem-auto {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2xs);
    margin-right: var(--space-sm);
    padding: var(--space-2xs) var(--space-xs);
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    color: var(--text-dim);
    font-size: var(--text-sm);
    line-height: 1;
    white-space: nowrap;
    cursor: pointer;
}

.sem-save > .sem-auto:hover {
    background: var(--bg-hover);
    color: var(--text);
}

/* On, it is a mode the file is in rather than a button that was pressed, and it should read as one. */
.sem-save > .sem-auto:has(input:checked) {
    border-color: var(--accent-dim);
    color: var(--accent);
}

/*
   Shrunk with the chip. A checkbox draws at whatever size the platform likes, which here is taller
   than the chip it sits in - so it is given one, and the accent colour so that on and off differ by
   more than a tick nobody looks closely enough to see.
*/
.sem-save > .sem-auto input {
    width: 0.8em;
    height: 0.8em;
    margin: 0;
    accent-color: var(--accent);
    cursor: pointer;
}

/* The icon takes the chip's colour, so it dims and lights with the state rather than beside it. */
.sem-save > .sem-auto svg {
    display: block;
    flex: none;
}


/* ---------------------------------------------------------------- options */

.sem-options {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
    gap: var(--space-md);
}

.sem-option {
    display: flex;
    gap: var(--space-lg);
    align-items: flex-start;
    padding: var(--space-md) var(--space-lg);
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    cursor: pointer;
    text-align: left;
    width: 100%;
}

.sem-option:hover:not(.blocked) {
    background: var(--bg-hover);
    border-color: var(--accent-dim);
}

/*
   One way of saying "this is the one you picked", for every grid of tiles in the app.

   There had been five treatments across three colours, three of which changed only the border from
   #2a3543 to #4a9fd4 — a one-pixel hue shift that is genuinely hard to find in a grid of a hundred
   icons. This is the treatment the busiest grids already used, so unifying on it moves the fewest
   pixels on the screens that matter most.

   Two rules deliberately stay out of this and say why where they are: the colour swatches, and the
   current value in a dropdown.
*/
.sem-option.selected,
.sem-thumb-option.selected,
.sem-portrait.selected,
.sem-empire.selected,
.sem-class-list button.selected,
.sem-gender.selected,
.sem-nomad.selected,
.sem-icon-option.selected {
    border-color: var(--accent);
    background: var(--bg-hover);
    box-shadow: inset 0 0 0 1px var(--accent-dim);
}

.sem-option.blocked {
    opacity: 0.5;
    cursor: not-allowed;
}

.sem-option .body {
    flex: 1;
    min-width: 0;
}

.sem-option .name {
    font-weight: 600;
    display: flex;
    gap: var(--space-sm);
    align-items: baseline;
}

.sem-option .cost {
    margin-left: auto;
    color: var(--gold);
    font-variant-numeric: tabular-nums;
}

.sem-option .cost.negative { color: var(--good); }

.sem-option .why {
    color: var(--bad);
    font-size: var(--text-sm);
    margin-top: 3px;
}

.sem-option .dlc {
    color: var(--warn);
    font-size: var(--text-sm);
    margin-top: 3px;
}

/* ---------------------------------------------------------------- images */

.sem-icon {
    width: var(--icon);
    height: var(--icon);
    object-fit: contain;
    flex-shrink: 0;
    image-rendering: auto;
}

/* Takes its size from .sem-icon beside it, so a missing icon holds exactly the space one would. */
.sem-icon-placeholder {
    display: inline-block;
    border-radius: var(--radius-sm);
    background: var(--bg-hover);
    border: 1px solid var(--border);
    flex-shrink: 0;
}

.sem-thumb {
    display: block;
    width: 100%;
    border-radius: var(--radius);
    border: 1px solid var(--border);
}

/*
    An emblem holds its square before its picture arrives. They are loaded lazily and there are
    hundreds, so without this a tile that has not loaded yet is its padding and nothing else — the
    grid reflowed as you scrolled, and on a phone nineteen of the eighty-seven on screen were four
    pixels tall and unhittable until they drew.
*/
.sem-thumbs.emblems .sem-thumb { aspect-ratio: 1; }

.sem-thumbs {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: var(--space-md);
}


/*
    Emblems are square and read at a glance, so they pack far tighter than the wide pictures the
    default track is sized for — there are hundreds of them and a 200px column would put a dozen on
    a screen where a 52px one puts a hundred.
*/
.sem-thumbs.emblems { grid-template-columns: repeat(auto-fill, minmax(52px, 1fr)); }

/*
    A city tile is the scene at the top of the same panel, small: the empire's own world in bands
    with the city built between them, stacked by the same code for the reason the scene gives at
    length — a city is painted into a world rather than over it.

    Three to one rather than the pictures' own two to one, and no magnification. The tile had both
    once, because it held only the game's flat city preview — the furthest band alone, a distant
    skyline over an otherwise empty field — and needed doubling to be visible at all. With the
    whole city drawn there is nothing left to magnify: at twice the size the panorama is cropped to
    a fragment and the wilderness set becomes an abstract shape. The 3:1 window earns its keep on
    its own by trimming empty sky, and the picture sits at 60% so the ground wins what is left.

    The shape is stated rather than left to the pictures, because one installed set, ai_01, ships a
    four-pixel placeholder where its artwork should be, and the tiles either side of it should not
    change size because of it.
*/
.sem-thumbs.cities .sem-thumb {
    position: relative;
    display: block;
    aspect-ratio: 3;
    overflow: hidden;
    background: var(--bg-void);
}

/* Every band fills the frame, and the frame clips them, exactly as the scene stacks them. */
.sem-thumbs.cities .sem-thumb img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: 50% 60%;
}

/* The tile that chooses no city is the frame and nothing in it, so the grid keeps one shape. */
.sem-thumbs.cities .sem-thumb.none { background: var(--bg); }

.sem-thumb-option {
    padding: var(--space-xs);
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    cursor: pointer;
}

.sem-thumb-option .caption {
    display: block;
    padding: var(--space-xs) var(--space-2xs) 0;
    color: var(--text-dim);
    font-size: var(--text-sm);
    text-align: center;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ---------------------------------------------------------------- portraits */

/*
    Classes on the left, their faces on the right — the game's own arrangement, and the one that
    says the class is what decides the list rather than a filter applied to it afterwards.
*/
.sem-portrait-picker {
    display: grid;
    grid-template-columns: minmax(120px, 160px) 1fr;
    gap: var(--gap);
    align-items: start;
}

.sem-class-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-2xs);
    max-height: 460px;
    overflow-y: auto;
}

.sem-class-list button {
    padding: var(--space-sm) var(--space-lg);
    text-align: left;
    background: none;
    border: 1px solid transparent;
    border-radius: var(--radius);
}

.sem-class-list button:hover:not(:disabled) {
    background: var(--bg-hover);
}

.sem-class-list button.blocked {
    opacity: 0.4;
    cursor: not-allowed;
}

@media (max-width: 720px) {
    .sem-portrait-picker {
        grid-template-columns: minmax(0, 1fr);
    }

    .sem-class-list {
        flex-direction: row;
        flex-wrap: wrap;
        max-height: none;
    }
}

.sem-portraits {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
    gap: var(--space-md);
    max-height: 460px;
    overflow-y: auto;
}

.sem-portrait {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-xs);
    padding: var(--space-sm) var(--space-xs);
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    cursor: pointer;
}

.sem-portrait:hover:not(.blocked) {
    background: var(--bg-hover);
    border-color: var(--accent-dim);
}


.sem-portrait.blocked {
    opacity: 0.4;
    cursor: not-allowed;
}

.sem-portrait img {
    width: 100%;
    aspect-ratio: 3 / 4;
    object-fit: cover;
    border-radius: var(--radius-sm);
    background: var(--bg);
}

/*
   Stands in where no likeness has been rendered. A plain shape reads as "no picture yet" rather
   than as a broken image.
*/
.sem-portrait .silhouette {
    width: 100%;
    aspect-ratio: 3 / 4;
    border-radius: var(--radius-sm);
    background:
        radial-gradient(circle at 50% 34%, var(--border-strong) 0 18%, transparent 19%),
        radial-gradient(ellipse at 50% 92%, var(--border-strong) 0 34%, transparent 35%),
        var(--bg);
}

.sem-portrait .caption {
    font-size: var(--text-xs);
    color: var(--text-dim);
    text-align: center;
    width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ---------------------------------------------------------------- flags */

/* The box is the whole framed flag, border included — which is what the game's own sprite measures,
   so a flag drawn at 128 is 131 across and its colour only fills the middle of that. No background
   of its own: the frame's artwork is the edge, and anything painted behind it shows in the corners
   the mask cuts away. */
.sem-flag {
    position: relative;
    width: 100%;
    aspect-ratio: 1;
}

/* The shape the field is cut to, in the frame's own coordinates. Covers the whole flag because the
   game's mask does; it rounds the corners well inside the field rectangle. */
.sem-flag .cut {
    position: absolute;
    inset: 0;
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
}

/* The three coloured shapes add together and clip at white, which is what the game's shader does.
   Isolating them keeps that addition inside the flag rather than blending it with the page. */
.sem-flag .field {
    position: absolute;
    inset: 0;
    isolation: isolate;
    background: #000;
}

/* Over everything, because a frame is a border drawn on top of what it frames. */
.sem-flag .border {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
}

.sem-flag .layer {
    position: absolute;
    inset: 0;
    -webkit-mask-size: 100% 100%;
    mask-size: 100% 100%;
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    mix-blend-mode: plus-lighter;
}

/* Drawn as it was painted: the artwork carries its own colours and the game never tints it. The
   inset comes from the game's own numbers and is set inline, since it differs by size — seven tenths
   of the field on the largest flag, four fifths on the smallest. */
.sem-flag .emblem {
    position: absolute;
    inset: 0;
    object-fit: contain;
}

.sem-flag.small { width: 44px; height: 44px; }
.sem-flag.medium { width: 72px; height: 72px; }
.sem-flag.large { width: 160px; height: 160px; }

/* ---------------------------------------------------------------- budgets */

.sem-budget {
    margin-bottom: var(--gap);
}

/* Several counters across the panel, each taking an equal share of it. */
.sem-budgets {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: var(--gap);
}

.sem-budgets > .sem-budget { margin-bottom: var(--gap); }

/*
    The reading sits on the bar. The fill is behind it rather than around it, so the words stay put
    as the bar fills and stay legible over either side of it.
*/
.sem-budget .track {
    position: relative;
    height: var(--track-h);
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    overflow: hidden;
}

/*
   Scaled rather than widened. A width animates by laying the box out again on every frame, and this
   is the one thing in the sheet that was still doing that; a transform is the browser's to do
   without asking the page anything. The fill is out of flow inside a clipped track, so the reflow
   could not reach anything - this is the sheet agreeing with itself rather than a measured win.

   The reading comes in as a whole number and the dividing is done here, because a fraction written
   from C# is formatted by the reader's own culture, and scaleX(0,42) is not a number.
*/
.sem-budget .fill {
    position: absolute;
    inset: 0 auto 0 0;
    width: 100%;
    transform: scaleX(calc(var(--pct) / 100));
    transform-origin: left;
    background: var(--accent-dim);
    transition: transform 0.15s;
}

.sem-budget .reading {
    position: relative;
    display: block;
    padding: 0 var(--space-md);
    line-height: calc(var(--track-h) - 2px);
    font-size: var(--text-sm);
    color: var(--text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    font-variant-numeric: tabular-nums;
}

.sem-budget.over .fill { background: var(--bad); }
.sem-budget.full .fill { background: var(--gold); }

/* Gold is bright enough that the words need the dark side of the palette to stay readable. */
.sem-budget.full .reading { color: var(--bg); }

/* ---------------------------------------------------------------- status */

.sem-status {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: var(--text-sm);
}

.sem-status.bad { color: var(--bad); }

/*
    As a paragraph it is a message rather than a label beside something, so it is set off above and
    sits flush below. Left to the browser it took a margin measured from its own font size, which is
    how a status line ended up 14px from what it was reporting on where everything else is 12.
*/
p.sem-status { margin: var(--gap) 0 0; }

/*
   What is wrong with the empire, folded away at the foot of the panel that describes it.

   Built like the biography fields rather than like a heading, for the same reason: a dim label above
   nothing reads as a section that failed to load. The colour is the report's — amber where there are
   only warnings, red where something is actually wrong — so the strip says which before it is opened.
*/
.sem-showcase-problems {
    margin-top: var(--gap);
}

.sem-showcase-problems > summary {
    display: flex;
    align-items: baseline;
    gap: var(--space-md);
    padding: var(--space-sm) var(--space-lg);
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    color: var(--bad);
    cursor: pointer;
    list-style: none;
}

.sem-showcase-problems > summary::-webkit-details-marker { display: none; }

.sem-showcase-problems > summary:hover { background: var(--bg-hover); }

.sem-showcase-problems[open] > summary {
    border-bottom-color: transparent;
    border-radius: var(--radius) var(--radius) 0 0;
}

.sem-showcase-problems > summary .chevron { color: var(--text-faint); }

.sem-showcase-problems[open] > summary .chevron { rotate: 90deg; }

.sem-showcase-problems > summary .title {
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

.sem-showcase-problems > .sem-problem:last-child {
    margin-bottom: 0;
}

.sem-problem {
    padding: var(--space-md) var(--space-lg);
    border-left: 3px solid var(--bad);
    background: var(--bg-raised);
    border-radius: 0 var(--radius) var(--radius) 0;
    margin-bottom: var(--space-sm);
}

.sem-problem.warning { border-left-color: var(--warn); }

/* Fainter than the rest: the area is context for the message, not the message. */
.sem-problem .area {
    color: var(--text-faint);
}

.sem-problem .why {
    color: var(--text-dim);
    font-size: var(--text-sm);
    margin-top: var(--space-2xs);
}

.sem-empty {
    padding: var(--space-3xl) var(--space-xl);
    text-align: center;
    color: var(--text-dim);
}

/*
    A hint carries no spacing of its own. It had none declared, so it inherited the browser's 1em
    top and bottom — a margin measured from the font size rather than from the spacing scale, which
    is why three call sites had inline styles arguing with it and why the seven hints on the
    designer sat at three different distances from their neighbours.

    A hint that follows something is set off from it; one that opens a block is not. That is what
    those three inline styles were each saying separately.
*/
.sem-hint {
    margin: 0;
    color: var(--text-dim);
    font-size: var(--text-sm);
}

* + .sem-hint { margin-top: var(--gap); }

/*
    A heading with a line under it saying how many, or from where. The heading gives up its own
    bottom margin because the line below already brings a top one, and two margins between two lines
    of the same thing is one too many.
*/
h1:has(+ .sem-hint), h2:has(+ .sem-hint), h3:has(+ .sem-hint), h4:has(+ .sem-hint) { margin-bottom: 0; }

/* An empty state standing in for a list, which wants the padding the list would have had. */
.sem-hint.padded { padding: var(--space-sm); }

/* In a row the flex gap already spaces it, and a margin would be added on top of that. */
.sem-row > .sem-hint,
.sem-dlc-bar > .sem-hint { margin-top: 0; }

.sem-desc {
    color: var(--text-dim);
    font-size: var(--text-sm);
    margin-top: var(--space-sm);
}

/* ---------------------------------------------------------------- empire list */

/*
    Both lists on the front page use this: the player's own empires and the game's. The game's entry
    is 200 by 65 with a 60 by 62 face, and these keep those proportions with a flag added.
*/
.sem-empires {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: var(--space-lg);
}

/*
   The game's empires take their columns from the rule above, as the player's own do.

   They used to be pinned to a stated number at each width - three, then two, then one - because
   the shared rule fitted as many 300px columns as it could and that came out at three on a wide
   screen and four on a narrow one, which was the preview beside the list rather than anything
   chosen. The preview has gone, both lists have the same width to fill, and one card is the same
   size in either of them; so a second set of breakpoints was only a way for the two lists to
   disagree about where a column fits.
*/

/* A section rather than a panel: a heading, a line of hint, and the list. */
.sem-prescripted {
    margin-top: var(--space-2xl);
}

.sem-prescripted > .sem-empires {
    margin-top: var(--space-lg);
}

/*
   The card and its share button, which are siblings because a button cannot hold a button. This is
   what the grid lays out, so it carries no look of its own — only the frame the share button is
   positioned against.
*/
.sem-empire-slot {
    position: relative;
    min-width: 0;
}

/*
   The share button, in the card's top right corner and out of sight until it is wanted.

   Hidden at rest rather than always shown: the front page is two lists totalling fifty-five cards
   whose whole purpose is choosing between them, and a permanent second button on every one of them
   competes with that. Revealed on hover, and on focus-within so it is reachable by keyboard — it
   comes after the card in the markup, so tabbing to a card and pressing Tab again lands on it.

   Its own background, because it sits over the card's face and name.
*/
.sem-empire-share {
    position: absolute;
    top: var(--space-xs);
    right: var(--space-xs);
    background: var(--bg-panel);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    opacity: 0;
}

.sem-empire-slot:hover .sem-empire-share,
.sem-empire-slot:focus-within .sem-empire-share {
    opacity: 1;
}

/*
   Somewhere to take hold of the card.

   The card is a button, and a browser will not begin a drag from inside one - so with the whole
   face of the card being that button there was nothing to grab, and dragging did nothing at all.
   This is the part that is not the button.

   touch-action:none is half of what stops a finger scrolling the page instead of moving the card;
   preventDefault on the pointer press is the other half, and neither works without the other.
*/
.sem-empire-grip {
    position: absolute;
    right: var(--space-xs);
    bottom: var(--space-xs);

    /* The same box as the share button above it, and nothing drawn around it: a grip is a texture
       to take hold of rather than a thing to press, and a second bordered square in the corner read
       as a second button. Matched to the share button rather than the other way round, since
       growing either is the card growing. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--icon-btn);
    height: var(--icon-btn);
    padding: 0;

    background: none;
    border: none;
    color: var(--text-faint);
    font: inherit;
    line-height: 1;
    cursor: grab;
    touch-action: none;
    opacity: 0;
}

.sem-empire-grip:hover { color: var(--text); }

.sem-empire-grip:active { cursor: grabbing; }

.sem-empire-slot:hover .sem-empire-grip,
.sem-empire-slot:focus-within .sem-empire-grip {
    opacity: 1;
}

/*
   The cards while one of them is being carried.

   Every card is placed by a transform the script sets, so the one under the pointer follows it and
   the others slide into the places they would take if it were dropped now. The list itself does not
   change until it is: what moves is only where each card is drawn, so nothing reflows under the
   finger and the answer is visible before the drop rather than after it.

   The transition is on the others and not on the held one, which has to keep up with the pointer
   exactly.
*/
.sem-empires.sorting .sem-empire-drag {
    transition: transform 160ms ease;
}

.sem-empires.sorting .sem-empire-drag.lifted {
    transition: none;
    position: relative;
    z-index: 2;
}

.sem-empire-drag.lifted .sem-empire {
    border-color: var(--accent);
    box-shadow: 0 6px 18px rgb(0 0 0 / 45%);
    cursor: grabbing;
}

@media (prefers-reduced-motion: reduce) {
    .sem-empires.sorting .sem-empire-drag { transition: none; }
}

/*
   A finger has no hover, so on a touch screen neither control would ever appear. Shown there, at
   the cost of the tidiness the rules above buy on a pointer — and the card gives up the width for
   them, since nothing is going to move them out of the way again. On a pointer they are absent at
   rest and the card keeps the whole line for the empire's name.
*/
@media (hover: none) {
    .sem-empire-share,
    .sem-empire-grip { opacity: 1; }

    .sem-empire-slot .sem-empire {
        padding-right: calc(var(--icon-btn) + var(--space-lg));
    }
}

.sem-empire {
    display: flex;
    gap: var(--space-lg);
    align-items: center;
    padding: var(--space-md) var(--space-lg);
    background: var(--bg-panel);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    color: inherit;
    font: inherit;
    cursor: pointer;
    text-align: left;
    width: 100%;
}

.sem-empire:hover:not(:disabled) {
    background: var(--bg-hover);
    border-color: var(--accent-dim);
}


/* An empire the game would refuse, said without spending a line of the card on saying it. */
.sem-empire.broken { border-color: var(--bad); }

/* An empire the game keeps but stores nothing readable for cannot be shown, so it is not offered. */
.sem-empire:disabled {
    cursor: default;
    opacity: 0.5;
}

/* The face, cropped to the head as the game crops it. */
.sem-empire .face {
    width: var(--face-w);
    height: var(--face-h);
    object-fit: cover;
    object-position: top center;
    flex-shrink: 0;
    border-radius: var(--radius);
}

/* Three lines stacked, as the game's own entry has them: the name, the government it turned out to
   be, and the species class, at (65,6), (65,24) and (65,42) of a 200 by 65 tile. Set side by side,
   which is what a plain block did, the government read as part of the name. */
.sem-empire .body {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-2xs);
}

.sem-empire .name {
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.sem-empire .sub {
    color: var(--text-dim);
    font-size: var(--text-sm);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ---------------------------------------------------------------- misc */


.sem-loading {
    padding: var(--space-3xl) var(--space-xl);
    text-align: center;
    color: var(--text-dim);
}


/* ---------------------------------------------------------------- icon grids

   The game fits a whole screen of choices into a corner of one by showing icons and putting the
   words in a panel beside them. These do the same: a grid that stays the same size however many
   options it holds, and one detail panel that describes whatever the pointer is over. */

.sem-grid-and-detail {
    display: grid;
    grid-template-columns: minmax(0, 3fr) minmax(240px, 2fr);
    gap: var(--gap);
    align-items: start;
}

@media (max-width: 900px) {
    .sem-grid-and-detail { grid-template-columns: minmax(0, 1fr); }

    /*
        And the description above the options rather than after them, for the reason the trait
        lists put it there: a press on a touch screen is how an option is read, and the answer
        below a scrolling list is an answer off the screen. The panel already has a height of its
        own here, so nothing moves under the finger between the press that reads and the one that
        chooses.
    */
    .sem-grid-and-detail > .sem-detail { order: -1; }
}

/* Icons are a fixed size and the row length follows the space available. Letting them stretch to
   fill the row would make the same trait a different size in two places, and would waste the height
   that the compact layout exists to save. */
.sem-icon-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, var(--sem-icon-size, var(--tile)));
    gap: var(--space-sm);
}

.sem-icon-option {
    position: relative;
    aspect-ratio: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-xs);
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    cursor: pointer;
    transition: border-color 0.12s, background 0.12s;
}

/*
    Not over a tile already taken.

    Hover is a class and two pseudo-classes and selection is two classes, so without the third
    the hover border outranked the accent one and a selected tile went dull under the pointer -
    the same way round as the reading mark did, and just as wrong. Nothing is lost by standing
    aside: a selected tile is already drawn on the hover background.
*/
.sem-icon-option:hover:not(.blocked):not(.selected) {
    background: var(--bg-hover);
    border-color: var(--border-strong);
}

/*
    Still legible, and still hoverable: a blocked option has to be able to explain itself.

    Hoverable took a change in the markup to become true. The button carried the disabled attribute,
    and a disabled control is sent no mouse events at all — no hover, no focus, not even its own
    title — so the panel beside the grid held a list of reasons that the options with reasons were
    the only ones unable to reach. It is marked unavailable to a reader now and refuses the press
    itself, which is how the trait list had always done it.
*/
.sem-icon-option.blocked {
    opacity: 0.35;
    cursor: not-allowed;
}

/*
    Not the same thing as blocked, and not drawn as it.

    Nothing is wrong with an option there is simply no room left for. The distinction would be
    pedantic if it applied to one tile at a time, but it applies to nearly all of them at once:
    taking a second civic puts seventy-nine others out of reach, and at the blocked treatment the
    whole grid dropped to a third of its brightness at the exact moment the player was reading it to
    decide which one to release. Dimmed enough to say it is not available, bright enough to be the
    thing you are choosing between.
*/
.sem-icon-option.at-limit,
.sem-trait-row.at-limit {
    opacity: 0.6;
    cursor: default;
}

.sem-icon-option img {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
}

.sem-icon-option .initials {
    font-size: var(--text-xs);
    color: var(--text-faint);
    letter-spacing: 0.05em;
}

.sem-icon-option .cost {
    position: absolute;
    right: 2px;
    bottom: 1px;
    font-size: var(--text-xs);
    font-variant-numeric: tabular-nums;
    color: var(--text-dim);
    text-shadow: 0 0 3px var(--bg);
}

.sem-icon-option .cost.gain { color: var(--good); }

/* The icons, and beside them whatever belongs with them without being one of them. */
.sem-grid-column {
    display: flex;
    align-items: flex-start;
    gap: var(--space-sm);
    min-width: 0;
}

.sem-grid-column > .sem-icon-grid { flex: 1; min-width: 0; }

.sem-grid-aside { flex-shrink: 0; }

/* ------------------------------------------------------- the same choices as a list

   Eighty-one civics as unlabelled symbols is a memory test. The same options as named rows, in the
   same two-column shape, so that changing view changes the left half and leaves the description
   where it was. */

.sem-picker { min-width: 0; }

/*
    A choice between two ways of being shown one list, and the first control of its kind here — so
    it is built out of the surfaces already in use rather than a look of its own: the strip is the
    page's own depth, and the view in force is a raised tile inside it.

    It sits on the heading's line, in the flex row above. It had a row to itself once, which cost
    the section a whole row of height to say one word.
*/
.sem-view-toggle {
    display: inline-flex;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;
}

.sem-view-toggle button {
    padding: var(--space-2xs) var(--space-lg);
    background: none;
    border: none;
    border-radius: 0;
    color: var(--text-dim);
    font-size: var(--text-sm);
}

.sem-view-toggle button:hover {
    background: var(--bg-hover);
    color: var(--text);
}

.sem-view-toggle button[aria-pressed="true"] {
    background: var(--bg-raised);
    color: var(--text);
}

/*
    As tall as the panel beside it, so the two columns end level and switching view does not change
    the height of the section. The panel says how tall it is by whether it carries a picture, and
    the list takes the same answer from it rather than being told twice.
*/
.sem-grid-and-detail { --option-list: 300px; }

.sem-grid-and-detail:has(.sem-detail-picture) { --option-list: 460px; }

.sem-grid-column > .sem-option-list { flex: 1; min-width: 0; }

.sem-option-list {
    height: var(--option-list);
    overflow-y: auto;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

/*
    What the empire has, held at the top while the rest goes under it — so what you already hold is
    an answer the list gives rather than one you have to scroll for.

    One sticky box around them, not a sticky row apiece. Rows pinned to a common top slide under
    each other, and pinning each at its own offset needs a row height written in the stylesheet,
    which is not a constant: a finger gets ten pixels of padding where a pointer gets three. A box
    is whatever its contents come to, whether that is nothing, one row or two.

    The edge is the raised surface plus a rule, which is how the chosen column of the trait picker
    already tells itself from the available one. No shadow: the three in this sheet are an inset
    ring and two popups, and this is neither.

    This is also the whole of how a held option is marked. The rows carry aria-pressed for a reader
    who is not looking, and for one who is, being in this box is what says it — the same answer the
    trait picker gives by which of its two lists a row is in. A ring around each row as well would
    be a second treatment for one fact, on the two rows that need it least.
*/
.sem-option-list > .stuck {
    position: sticky;
    top: 0;
    z-index: 1;
    background: var(--bg-raised);
    border-bottom: 1px solid var(--border-strong);
}

/*
    On a phone the list is sized for itself rather than for the panel. The two are stacked here
    instead of side by side, so there is no longer a row for them to end level at — and the reason
    they matched was that they were columns of one.

    380 is what it takes to stay usable. A row is 42 pixels under a finger against 25 under a
    pointer, and the held civics take 85 of them off the top: at the panel's 224 that left three
    rows of the eighty-one to scroll through. This gives seven under the two that are pinned, which
    is about what the desktop shows.
*/
@media (max-width: 700px) {
    .sem-grid-and-detail,
    .sem-grid-and-detail:has(.sem-detail-picture) { --option-list: 380px; }
}

/*
    How deep a surface is, and what that means. This panel is the first case of it.

      the page                         --bg
      a card framing a region          --bg-panel with a border
      a readout set apart inside one   --bg again, recessed
      a tile you can click             --bg-raised with a border, which carries the selected state

    The readout panels had been --bg-panel like the card containing them, so two boxes sat one
    inside the other in the same colour, told apart by a single hairline. Recessing them means the
    border is doing something.

    Sized to what it is describing, between a floor and a ceiling.

    This had been one fixed height per caller — 178, 320 or 460, each picked for the longest thing
    that panel might have to show — so a one-line civic description sat in a box three times its own
    size. The reason given was that a self-sizing panel moved the icons out from under the pointer.
    Measured, that is no longer true: with align-items:start on the row, sweeping the pointer across
    eighty-one civics leaves every icon exactly where it was.

    What a self-sizing panel does still move is everything below the grid, since the section grows
    with it. Hence the ceiling, and hence 300 rather than the 460 the tallest description would ask
    for. Measuring every option in every grid, three quarters of them fit inside 300 — the medians
    are 234 for the ethics, 238 for the civics, 180 for the traditions — so the common case is a
    panel that fits its contents exactly and a page that does not move at all beneath it. The long
    tail scrolls, which is what the ceiling buys.

    A column rather than one scrolling box, so that a picture can sit above the words and stay put
    while they scroll: an origin's scene is what it looks like, and scrolling past it to read about
    it loses the thing being read about.
*/
.sem-detail {
    display: flex;
    flex-direction: column;

    /*
       One height, not a range.

       This was a floor and a ceiling, sized to what each option had to say, which fitted the panel
       to its contents and made the page under it move as the pointer crossed the grid — up to 131
       pixels of the sections below sliding about while reading. Fitting the box to the text was the
       wrong trade: a reader looks at one description at a time, and a page that holds still while
       they do is worth more than a box with no slack in it.

       300 because three quarters of every option fits inside it, measured across all of them: the
       medians are 234 for the ethics, 238 for the civics and 180 for the traditions. The rest
       scroll, which is what the scrolling is for.
    */
    height: 300px;

    overflow: hidden;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

/*
    Origins are the exception, and say so themselves rather than being told by a parameter: they are
    the only choices whose detail carries a picture. That picture costs 160 of the 300 before a word
    is read, and their descriptions are the longest in the game — half of the fifty-four run past
    418 pixels where three quarters of every other kind fit in 300. Keyed on the picture so the
    reason travels with the rule; the numbers used to live in three call sites that could not say why.
*/
.sem-detail:has(> .sem-detail-picture) {
    height: 460px;
}

/*
    The padding belongs to the scrolling part, not to the panel around it.

    On the panel it inset the scroller, so the bar appeared a dozen pixels inside the border with the
    text running right up against it. Here the bar sits on the panel's own edge and the padding keeps
    the words off it.

    scrollbar-gutter so the text does not shift sideways when a description is long enough to need a
    bar and the next one is not.
*/
.sem-detail > .body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    scrollbar-gutter: stable;
    padding: var(--space-lg);
}

/* The picture is outside the scroller and needs the inset the scroller gives itself. */
.sem-detail > .sem-detail-picture {
    margin: var(--space-lg) var(--space-lg) 0;
    width: auto;
}

/*
    On a phone the grid and the panel are stacked rather than side by side, so the panel's height is
    taken out of the same scroll the grid is in. Both selectors, because :has() outranks the bare
    class and the origin panel would otherwise keep its desktop 460 here.
*/
@media (max-width: 700px) {
    .sem-detail { height: 224px; }

    /*
        A picture at the game's ratio takes about 183 of a phone's width, so the 240 this used to be
        left under sixty pixels to read the description in. The panel is taller here than the plain
        one rather than shorter.
    */
    .sem-detail:has(> .sem-detail-picture) { height: 360px; }
}

.sem-detail h4 {
    margin: 0 0 var(--space-sm);
    font-size: var(--text-lg);
}

/*
    The content packs, across the top of every page. A pack that is off is dimmed rather than hidden,
    because knowing what is being left out is the point of having them visible at all.
*/
.sem-dlc-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-xs);
    padding: var(--space-sm) var(--space-xl);
    background: var(--bg-raised);
    border-bottom: 1px solid var(--border);
}

/*
   The bar folds away on a phone and is a bar everywhere else, so the line that stands in for it is
   absent above the breakpoint. The switch is hidden by size rather than by display, up there and
   down here alike, so it keeps its place in the tab order — see the rule beside the burger.
*/
.sem-dlc-summary { display: none; }

@media (max-width: 720px) {
    .sem-dlc-summary {
        display: flex;
        align-items: center;
        gap: var(--space-md);
        padding: var(--space-md) var(--space-lg);
        background: var(--bg-raised);
        border-bottom: 1px solid var(--border);
        color: var(--text-dim);
        font-size: var(--text-sm);
        cursor: pointer;
    }

    .sem-dlc-summary:hover { color: var(--text); }

    .sem-dlc-summary .chevron { color: var(--text-faint); }

    .sem-dlc-toggle:checked + .sem-dlc-summary .chevron { rotate: 90deg; }

    /* Thirty icons wrapping to four rows, above every page, is most of a phone screen. */
    .sem-dlc-toggle:not(:checked) ~ .sem-dlc-bar { display: none; }

    .sem-dlc-bar { padding: var(--space-sm) var(--space-lg); }
}

.sem-dlc-bar .spacer { flex: 1; }

.sem-dlc {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--dlc);
    height: var(--dlc);
    padding: 0;
    background: none;
    border: 1px solid transparent;
    border-radius: var(--radius);
}

/*
   A borrowed face rather than a badge: the three portrait packs wear the portrait they add, which
   is a wide render with the head upper centre. Filled and pulled to the top, so what survives the
   crop is the face; contain would letterbox a 332x220 picture into a 24px square and show mostly
   background.
*/
.sem-dlc img.face {
    object-fit: cover;
    object-position: 50% 12%;
    border-radius: 2px;
}

.sem-dlc img {
    width: var(--icon);
    height: var(--icon);
    object-fit: contain;
}

.sem-dlc .initials {
    font-size: var(--text-xs);
    font-weight: 600;
}

.sem-dlc.on { border-color: var(--border-strong); }

.sem-dlc.off {
    opacity: 0.3;
    filter: grayscale(1);
}

.sem-dlc:hover { background: var(--bg-hover); }

/*
    The ethics, as arguments rather than as a list. One column per opposition, read top to bottom
    from the fanatic form of one side to the fanatic form of the other, so what a conviction rules
    out is directly beneath it.
*/
/*
    One column per opposition, each a fixed width. Sized in fractions of the panel they stretched to
    fill it, and since an option is square that made every conviction a hundred-pixel tile. A column
    per track rather than a wrapping row, so the two halves of an argument never come apart.
*/
.sem-ethics-grid {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: var(--sem-icon-size, var(--tile));
    justify-content: start;
    gap: var(--space-sm);
    align-items: start;
}

.sem-ethics-grid .column {
    display: grid;
    gap: var(--space-sm);
}

/*
    The grid is now as wide as its columns make it rather than as wide as the panel, so the detail
    beside it takes the rest. Written as a minimum width rather than beside the rule above, because
    a selector carrying :has() outranks the breakpoint that stacks these on a narrow screen, and
    unqualified it forced two columns into a space with room for one.
*/
@media (min-width: 901px) {
    .sem-grid-and-detail:has(> .sem-ethics-grid) {
        grid-template-columns: auto minmax(240px, 1fr);
    }
}

/*
    The nomad toggle. Square like the authorities it sits beside, and kept out of their grid because
    it is not one of them — which is what the game does too, giving it a container of its own.

    It had also been marked in gold rather than the selection blue, on the same reasoning. That was
    a colour saying two things: gold means cost everywhere else in this sheet, and a toggle that
    costs nothing wearing it made the palette contradict itself. Position already carries the
    distinction, so the colour no longer has to.
*/
.sem-nomad {
    width: var(--sem-icon-size, var(--tile));
}

.sem-nomad .initials {
    font-size: var(--text-xs);
    text-align: center;
    line-height: 1.1;
}


/*
    The empire as the game shows it: a leader in a room with their own world behind them. Everything
    inside is placed as a fraction of the whole, so the scene keeps its composition at any width the
    page gives it. The aspect is the game's own room, 952 by 340.
*/
.sem-room {
    position: relative;
    width: 100%;
    aspect-ratio: 952 / 340;
    overflow: hidden;
    border-radius: var(--radius);
    background: var(--bg-void);
}

.sem-room > .room {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* The world through the window, painted before the room and so showing through its cut-out. */
.sem-room > .world {
    position: absolute;
    overflow: hidden;
}

.sem-room > .world img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/*
    Where the figure stands is set on the element, not here: its width, its height and the depth its
    feet are buried at are three parts of one arrangement and belong beside each other, in the
    component that holds the game's numbers.
*/
.sem-room > .figure {
    position: absolute;
    object-fit: contain;
}

/*
    A ruler drawn from their own layers rather than taken as one picture.

    The layers are placed against a 332 by 220 frame and cannot be reflowed, only scaled, so the box
    becomes a container and the figure is scaled to fill its width exactly. The arithmetic closes:
    the box is as wide as 332/220 of its height, and a figure scaled by width/332 is 220 * width/332
    tall, which is that same height.

    Unclamped, unlike the appearance panel's version of this, which caps the scale at 1 to avoid
    enlarging the artwork. A room is routinely drawn wider than 332, and the flat portrait it
    replaces was being enlarged to exactly the same size from exactly the same 332 by 220 render -
    so capping it here would shrink the ruler rather than sharpen them.
*/
.sem-room > .figure.dressed {
    container-type: inline-size;
}

.sem-room > .figure.dressed .sem-figure {
    transform-origin: top left;

    /*
        332px and not 332. A scale is a ratio, so both sides of the division have to be lengths for
        one to come out - written against a bare number the whole declaration is invalid, and the
        figure silently draws at its own 332 by 220 in the corner of a box that is neither.
    */
    scale: calc(100cqw / 332px);
}

/* The stand-in, which is one picture and so simply fills the box it is given. */
.sem-room > .figure.dressed > .flat {
    width: 100%;
    height: 100%;
    object-fit: contain;
}

/*
    The flag on the wall. Placed and sized in the markup as fractions of the room, so it keeps the
    game's own geometry at whatever width the page draws the scene at.
*/
.sem-room > .banner {
    position: absolute;
    filter: drop-shadow(0 2px 6px rgb(0 0 0 / 0.55));
}


/*
    A second list within a panel, set apart from the first — the rooms the game hands out rather
    than offers. The rule above it is the point: these are not more of the same.
*/
.sem-subhead {
    margin: var(--space-xl) 0 var(--space-2xs);
    padding-top: var(--space-lg);
    border-top: 1px solid var(--border);
    font-size: var(--text-sm);
    color: var(--text-dim);
}

/*
   And a heavier one for a divider that separates two whole groups rather than two lists. The emblem
   picker already gives every category a subhead, so the ordinary rule reads as one more of them —
   which is precisely the wrong thing to say about the line where the game's own offering stops.
*/
.sem-subhead.apart {
    border-top-width: 2px;
    border-top-color: var(--text-faint);
    color: var(--text);
}

/* The scene an origin opens on, above whatever is written about it and fixed while that scrolls. */
/*
    Cropped the way the game crops it. Its own entry frames a picture at 216 by 115 with clipping on
    — empire_origin_entry in customize_species_editors.gui — and the pictures are 220 by 115 apiece
    but for one, which is 450 by 150. Against a max-height that one came out shorter than all the
    others at the same width; against the game's own ratio every one of the sixty-six is identical.
*/
.sem-detail-picture {
    display: block;
    width: 100%;
    aspect-ratio: 216 / 115;
    object-position: left top;
    flex-shrink: 0;
    object-fit: cover;
    border-radius: var(--radius);
    margin-bottom: var(--space-md);
}

/*
    Capped at a readable measure rather than at the width of its box. Most of these panels are about
    400px and were never near it, but the ethics row gives its detail whatever the narrow grid of
    convictions does not use — 779px on a wide screen, which is something like a hundred and ten
    characters a line, far past the point where the eye starts losing its place on the way back.
*/
.sem-detail-lore {
    max-width: 68ch;
    margin-bottom: var(--space-md);
    color: var(--text-dim);
    font-size: var(--text-sm);
}

.sem-blocked {
    margin: var(--space-md) 0 0;
    padding-left: var(--space-xl);
    color: var(--bad);
    font-size: var(--text-sm);
}

/* ---------------------------------------------------------------- effects */

.sem-effects { font-size: var(--text-sm); }

.sem-effects-none {
    margin: 0;
    color: var(--text-faint);
    font-size: var(--text-sm);
}

.sem-effects-text {
    margin-bottom: var(--space-sm);
    color: var(--text-dim);
}

.sem-modifiers {
    margin: 0 0 var(--space-sm);
    padding: 0;
    list-style: none;
}

.sem-modifiers li {
    display: flex;
    align-items: baseline;
    gap: var(--space-md);
    padding: 1px 0;
}

.sem-modifiers .label {
    flex: 1;
    min-width: 0;
    color: var(--text-dim);
}

/*
   Last in the row and right-aligned, which is what makes a column of numbers readable. The count
   badge used to follow it and pushed every total that had one left by its own width, so no two
   values in the panel shared an edge; the badge now comes first and the numbers end where the panel
   does. The minimum width keeps a lone "+2" from crowding the badge beside it.
*/
.sem-modifiers .value {
    min-width: 3.5rem;
    font-variant-numeric: tabular-nums;
    text-align: right;
    white-space: nowrap;
}

.sem-modifiers .value.good { color: var(--good); }
.sem-modifiers .value.bad { color: var(--bad); }

.sem-modifiers .sources {
    min-width: 16px;
    padding: 0 var(--space-xs);
    border-radius: var(--radius-pill);
    background: var(--bg-raised);
    color: var(--text-faint);
    font-size: var(--text-xs);
    text-align: center;
}

/* A bonus that only applies in some empires is set apart rather than mixed in with the rest, so it
   cannot be mistaken for something the empire starts with. */
.sem-effects-when {
    margin: var(--space-sm) 0;
    padding-left: var(--space-md);
    border-left: 2px solid var(--border);
}

.sem-effects-when .when {
    color: var(--text-faint);
    font-size: var(--text-xs);
    font-style: italic;
}

/* Warned rather than dimmed: this heading introduces what an option costs you. */
.sem-effects-penalty .heading {
    margin-top: var(--space-sm);
    color: var(--warn);
}

/* An icon standing in for a word, so it sits on the text baseline at the height of a capital. */
.sem-text-icon {
    height: 1em;
    width: auto;
    vertical-align: -0.12em;
    margin: 0 1px;
}

.sem-combined h4 {
    margin: 0 0 var(--space-md);
}

/* ---------------------------------------------------------------- designer

   Two panes: the choices on the left, what the empire amounts to on the right. The right pane
   follows the page so the running total and the problem list stay in view while choices are made
   further down. */

.sem-designer {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(300px, 360px);
    gap: var(--gap);
    align-items: start;
}

/*
    The preview takes more of the width as there is more of it to take.

    The choices on the left stopped needing the room once everything on them came in a step: an
    option grid wraps to whatever it is given and reads the same at 900 pixels as at 1100. The panel
    on the right does not — it holds the scene, the chips and the whole modifier list, all of which
    are better bigger, and it was the pane doing the scrolling. So the extra width goes there rather
    than into a column of half-empty option rows.

    Stated as a fixed width above 1500 rather than a range, because past that point the left pane has
    long since stopped improving and the only question is how much the preview gets.
*/
@media (min-width: 1500px) {
    .sem-designer { grid-template-columns: minmax(0, 1fr) 440px; }
}

@media (min-width: 1800px) {
    .sem-designer { grid-template-columns: minmax(0, 1fr) 520px; }
}

/* One column, and the preview leads. Grid children follow source order, so without this the panel
   would sit under every section — past the point where it answers anything. */
@media (max-width: 1100px) {
    .sem-designer { grid-template-columns: minmax(0, 1fr); }

    .sem-summary-pane {
        position: static !important;
        order: -1;
        max-height: none;
    }
}

.sem-sections {
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
    min-width: 0;
}

.sem-summary-pane {
    position: sticky;
    top: var(--sticky-top);
    display: flex;
    flex-direction: column;
    gap: var(--gap);
    /* What is left of the window once the header and the gap either side are taken. */
    max-height: calc(100vh - var(--sticky-top) - var(--gap));
    overflow-y: auto;
}

/*
    No overflow:hidden here, deliberately. It was clipping the combo boxes: a dropdown opened near
    the foot of a section had most of its list cut off at the section's edge, which is the one place
    a dropdown must be allowed to escape. The rounding it was there for is done by the head and the
    body instead, each taking the corners it actually touches.

    Do not give this a position or a z-index either — either would make it a stacking context and
    trap the list again just as effectively.
*/
.sem-section {
    background: var(--bg-panel);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

.sem-section > .head {
    border-radius: calc(var(--radius) - 1px) calc(var(--radius) - 1px) 0 0;
}

/* A closed section is only its head, so that head rounds all four corners. */
.sem-section:not(:has(> .body)) > .head {
    border-radius: calc(var(--radius) - 1px);
}

.sem-section > .body {
    border-radius: 0 0 calc(var(--radius) - 1px) calc(var(--radius) - 1px);
}

.sem-section > .head {
    display: flex;
    align-items: baseline;
    gap: var(--space-md);
    width: 100%;
    padding: var(--space-md) var(--space-lg);
    background: none;
    border: none;
    color: var(--text);
    font: inherit;
    font-weight: 600;
    text-align: left;
    cursor: pointer;
}

.sem-section > .head:hover { background: var(--bg-hover); }

.sem-section > .head .chevron {
    width: 12px;
    color: var(--text-faint);
}

/* What is chosen, so a collapsed section still answers the question it asks. */
.sem-section > .head .summary {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    color: var(--text-dim);
    font-weight: 400;
    font-size: var(--text-sm);
    white-space: nowrap;
    text-overflow: ellipsis;
}

.sem-section > .head .count {
    padding: 0 var(--space-sm);
    border-radius: var(--radius-pill);
    background: var(--bad);
    color: var(--bg);
    font-size: var(--text-xs);
}

.sem-section > .body {
    padding: 0 var(--space-lg) var(--space-lg);
    border-top: 1px solid var(--border);
    padding-top: var(--space-lg);
}

/* The sections supply their own frame, so the panels inside them must not draw a second one. */

/* The page is already two columns. A section dividing itself again leaves the icon grids a few
   icons wide, which is how a compact grid turns back into a long list. */

/* ---------------------------------------------------------------- swatches */

/* The three slots side by side, each a square block of the game's palette under its own heading. */
/* Eight across and 28 to a side, which is the shape the game's own colour picker uses. */
.sem-swatches {
    display: grid;
    grid-template-columns: repeat(auto-fill, var(--swatch));
    gap: 3px;
    margin-top: var(--space-xs);
}

.sem-swatch {
    width: var(--swatch);
    height: var(--swatch);
    padding: 0;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    cursor: pointer;
}

.sem-swatch:hover { border-color: var(--border-strong); }

/*
   The three palettes, and the line that folds them away.

   Three grids of seventy-two is most of a phone screen spent above the flag they change, and the
   flag is the answer to all of it. Folded, the line still carries the three colours as dots, so
   what is set can be read without opening anything.
*/
/* Taking a tag off is a thing to do, so it is a tile like the colours rather than a link beside them. */
.sem-swatch.none {
    background: repeating-linear-gradient(
        45deg, var(--bg) 0 3px, var(--bg-raised) 3px 6px);
}

/*
   The one grid that cannot use the shared treatment. A swatch's fill is an arbitrary colour from
   the game's palette, so an accent-blue marker disappears on a blue swatch, and a ring drawn inside
   would be read as part of the colour rather than as a mark on it. Hence the brightest colour in
   the palette, drawn outside the tile.
*/
.sem-swatch.selected {
    border-color: var(--text);
    box-shadow: 0 0 0 1px var(--text);
}

/* ---------------------------------------------------------------- preview

   What the empire currently is. Beside the choices there is room to show all of it at once; above
   them, on a narrow screen, the header alone answers the question and the rest waits for a tap. */

/*
    The showcase folded behind its own header, which is what the designer's pane does on a narrow
    screen. One component draws the empire in both places now; this is the only thing the pane wants
    that the select screen does not.
*/
.sem-showcase.collapsible { padding: 0; }

.sem-showcase.collapsible > .head {
    display: flex;
    align-items: center;
    gap: var(--space-lg);
    width: 100%;
    padding: var(--space-lg);
    background: none;
    border: none;
    border-radius: 0;
    color: var(--text);
    font: inherit;
    text-align: left;

    /* Wide enough for the panel, and the toggle is inert there, so it should not invite a click. */
    cursor: default;
}

.sem-showcase.collapsible > .head .titles {
    flex: 1;
    min-width: 0;
}

.sem-showcase.collapsible > .head .name {
    display: block;
    font-weight: 600;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.sem-showcase.collapsible > .head .government {
    display: block;
    color: var(--text-dim);
    font-size: var(--text-sm);
}

/* Only meaningful where the panel collapses, so it is not shown where it is not. */
.sem-showcase.collapsible > .head .chevron { display: none; }

/*
   Save, share and delete, under the empire's name.

   Outside the folding body, so they are still there when the panel is folded on a phone — which is
   exactly when somebody is most likely to want Save and least likely to want the summary above it.
*/
.sem-showcase.collapsible > .actions {
    display: flex;
    gap: var(--space-md);
    padding: 0 var(--space-lg) var(--space-lg);
}

/*
   A button that is only a glyph. Square, so a row of them is even, and titled rather than labelled —
   which is why every one of them carries an aria-label as well.
*/
button.icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--icon-btn);
    height: var(--icon-btn);
    padding: 0;
}

button.icon .sem-action-icon { display: block; }

/*
    The stack of everything the panel shows, which is the panel itself where there is no header.

    Note that everything below is matched as a descendant rather than a child. The body is a wrapper
    the panel did not used to have, and the child combinators that were written before it silently
    stopped matching when it arrived — which drew the species portrait at the room scene's 332 by 220
    instead of its own 84 by 100.
*/
.sem-showcase > .body {
    display: flex;
    flex-direction: column;
    gap: var(--space-lg);
    min-width: 0;
}

/* With a header above it the padding moves here, so the header can span the full width. */
.sem-showcase.collapsible > .body {
    padding: 0 var(--space-lg) var(--space-lg);
}

.sem-summary {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: var(--space-2xs) var(--space-lg);
    margin: 0 0 var(--space-lg);
    font-size: var(--text-sm);
}

.sem-summary dt {
    color: var(--text-faint);
    white-space: nowrap;
}

.sem-summary dd {
    margin: 0;
    color: var(--text-dim);
}

@media (max-width: 1100px) {
    .sem-showcase.collapsible > .head { cursor: pointer; }
    .sem-showcase.collapsible > .head:hover { background: var(--bg-hover); }
    .sem-showcase.collapsible > .head .chevron { display: inline; color: var(--text-faint); }
    .sem-showcase.collapsible.collapsed > .body { display: none; }
}

/* ---------------------------------------------------------------- subsections

   Parts of one section, separated by a rule rather than another box: nesting a panel inside a panel
   inside the page reads as three frames around one list. */

/*
    Stacked by default. Five tabs use this and most of their sections are rows of named fields that
    want the whole width; measured, making them all multi-column squeezed the species names into
    328px and broke the homeworld row onto three lines.
*/
.sem-subsections {
    display: flex;
    flex-direction: column;
    gap: var(--space-xl);
}

/*
    Except where two of the sections are grids of choices, which is the government: its ethics and its
    authority each want an icon grid with a panel beside it, and stacking them left the right half of
    a wide screen empty. Paired at 460 apiece — the width an icon column plus its 240px detail panel
    actually needs, below which the panel starts scrolling text it has room for.

    Only the first two pair. Anything after them spans the width, because the civics are eighty-one
    options and a half-width grid of them is a column of scrolling.

    auto-fit rather than a breakpoint, and this matters: the first version asked for two 460px
    columns above a viewport width of 1000, but what has to fit them is the sections column, not the
    window. Between 1100 and about 1400 the preview keeps its 340px beside them, so the sections had
    only ~726px to hold 920px of columns — and the detail panels ran out of their column, out of the
    panel, and under the preview. auto-fit measures the space it actually has and drops to one
    column when two will not fit, at every width, with nothing to keep in step.

    min(460px, 100%) rather than 460px, because auto-fit can drop the column count to one but cannot
    take a column below the minimum it was given: on a 343px phone the lone column stayed 460 wide
    and eighty-nine things hung off the side of the page. The minimum is the smaller of the two.
*/
.sem-subsections.paired {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(460px, 100%), 1fr));
    align-items: start;
}

/* Anything after the first two spans whatever columns there turned out to be. */
.sem-subsections.paired > section:nth-child(n + 3) { grid-column: 1 / -1; }

/* A rule between stacked sections, which is what they are unless the container says otherwise. */
.sem-subsections > section + section {
    padding-top: var(--space-xl);
    border-top: 1px solid var(--border);
}

/*
    A grid lays its own sections out, so none of them wants the stacked divider: where two sit side
    by side the border would run across the top of a column rather than between two things, and
    where they stack the grid's gap already separates them.
*/
.sem-subsections.paired > section + section {
    padding-top: 0;
    border-top: 0;
}

.sem-subsections > section > h4 {
    margin: 0 0 var(--space-lg);
}

/*
    A heading with a control beside it, as the empire modifiers panel has it. The spacing moves to
    the row, because the heading is no longer the thing the section spaces away from what follows -
    two rules give a subsection heading its look and both are child selectors, so a wrapper has to
    be named in each rather than the selectors loosened for four other places that share them.

    .sem-heading-row is the same row without the ancestry. Civics is shown both inside a subsection
    and alone in a dialog, and a child selector cannot follow it into the second - shown on its own
    the row lost its layout and the view toggle dropped onto a line of its own under the heading.
*/
.sem-heading-row,
.sem-subsections > section > .heading {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-md);
    margin: 0 0 var(--space-lg);
}

.sem-heading-row > h4,
.sem-subsections > section > .heading > h4 {
    margin: 0;
}

/* The names a list holds, shown rather than described. Read-only, and sized to hint at how many
   there are without taking the page over. */
.sem-name-set label {
    display: flex;
    align-items: baseline;
    gap: var(--space-sm);
}

.sem-name-set label .count {
    color: var(--text-faint);
    font-variant-numeric: tabular-nums;
}

.sem-name-set textarea {
    resize: vertical;
    color: var(--text-dim);
    font-size: var(--text-sm);
    line-height: 1.45;
}

/* ---------------------------------------------------------------- traits

   The game's own arrangement: what may be taken, what has been taken, and what the pointer is over
   described underneath.

   `.sem-trait-row` is a shape rather than a place: the civic and origin lists use it too, so a row
   of choices is the same row of choices wherever it is. Changing it changes all of them, which is
   the point — including the target size it is given under a coarse pointer. */

/*
    Both columns are exactly as tall as each other and never change height. The box on the right
    describes whatever the pointer is over, and its contents run from a line to a paragraph and a
    list of conditions — left to size itself it grew and shrank as the pointer crossed the list,
    which moved every trait under the pointer and the rest of the page with them. So the height is
    fixed and the overflow scrolls.
*/
.sem-trait-lists {
    --trait-column: 380px;
    --trait-detail: 180px;

    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--gap);
    margin-top: var(--space-xs);
}

/* What is chosen, and beneath it what the pointer is over: reading about a trait and seeing what
   it would join belong together. */
.sem-trait-lists .chosen-column {
    display: flex;
    flex-direction: column;
    gap: var(--gap);
    min-width: 0;
    height: var(--trait-column);
}

/* The chosen list takes whatever the detail box leaves, so the two columns end level. */
.sem-trait-lists .chosen-column .sem-trait-list {
    flex: 1;
    height: auto;
    min-height: 0;
}

.sem-trait-lists .chosen-column .sem-trait-detail {
    flex: 0 0 var(--trait-detail);
    margin-top: 0;
    overflow-y: auto;
}

/*
    Wide enough for three, so the description stands beside what it describes rather than under it.
    The chosen column stops being a column — display: contents hands its two children to the grid
    above — which is the whole of the change: no second arrangement of the same markup, and the two
    narrower ones below are untouched.

    At the step where the preview panel widens, rather than a step of its own. A trait row needs 199
    pixels to show its longest name uncut, and the three columns come to 369, 369 and 332 at 1600.
*/
@media (min-width: 1500px) {
    .sem-trait-lists {
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 0.9fr);
    }

    .sem-trait-lists .chosen-column {
        display: contents;
    }

    .sem-trait-lists .chosen-column .sem-trait-list,
    .sem-trait-lists .chosen-column .sem-trait-detail {
        flex: none;
        height: var(--trait-column);
        margin-top: 0;
    }
}

/*
    Narrow, the two columns become one above the other - and the boxes below the first stop being
    boxes.

    Stacked, the arrangement was three lists that scroll inside a page that also scrolls, and the
    one holding what had been chosen was three rows tall. Nesting scrolling regions is unpleasant
    to read on a touch screen and close to unusable to drag in: a row lifted in a hundred and forty
    pixels is a row dragged against the edge of its own box.

    So only the offered list keeps its own scroll, because it is the one that is genuinely long -
    thirty perks, three hundred civics. What is chosen is short by nature, at most eight, and the
    description is a paragraph: both grow to what they hold and the page scrolls once.
*/
@media (max-width: 700px) {
    .sem-trait-lists {
        --trait-column: 50vh;

        grid-template-columns: minmax(0, 1fr);
    }

    .sem-trait-lists .chosen-column {
        height: auto;
    }

    .sem-trait-lists .chosen-column .sem-trait-list,
    .sem-trait-lists .chosen-column .sem-trait-detail {
        flex: none;
        height: auto;
        overflow-y: visible;
    }

    /*
        The description goes above the lists on a phone, not below them.

        Stacked, it had been under a list half the screen tall and a chosen list that grows, so the
        one thing a press was asking for was the one thing off the bottom of the screen. Reading is
        the reason to press at all here - a finger has no hover - so it goes where the answer can be
        seen without going to look for it.

        `display: contents` to get it out of the column it shares with the chosen list, the same way
        the three-column layout does at the other end of the range.
    */
    .sem-trait-lists .chosen-column {
        display: contents;
    }

    .sem-trait-lists .chosen-column .sem-trait-detail {
        order: -1;

        /*
            A height rather than none. Above a list, a panel that grows with what it says moves the
            row under the finger between one press and the next - and the second press is the one
            that chooses.
        */
        height: 160px;
        overflow-y: auto;
        overscroll-behavior: contain;
    }
}

.sem-trait-list {
    height: var(--trait-column);
    overflow-y: auto;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

.sem-trait-list.chosen {
    background: var(--bg-raised);
}

.sem-trait-row {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    width: 100%;
    padding: 3px var(--space-md);
    background: none;
    border: none;
    border-radius: 0;
    color: var(--text);
    font: inherit;
    text-align: left;
}

.sem-trait-row:hover:not(.blocked):not(.locked) {
    background: var(--bg-hover);
    border-color: transparent;
}

/*
    The row a finger is reading, which is the row a second press would take.

    Only ever set under a finger - a mouse reads by hovering and already has a mark for it. The
    first press on a touch screen shows the description instead of choosing, and without something
    on the row to say so that press looks like one the app ignored. A bar down the leading edge
    rather than a fill: the fill is what hover uses and what selection deepens, and a third shade of
    the same thing would say less the more of them there were.
*/
.sem-trait-row.reading,
.sem-icon-option.reading {
    box-shadow: inset 2px 0 0 0 var(--accent);
}

.sem-icon-option.reading:not(.selected) {
    border-color: var(--accent-dim);
}

/*
    Drawn over the selection rather than instead of it.

    Both marks are the same two properties, and at equal weight the later rule simply won: a
    selected tile being read lost its ring and took the dimmer border, so the tile you had just
    chosen looked less chosen than the ones around it. Two shadows in one list say both things,
    which is what a tile that is selected and being read is.
*/
.sem-icon-option.selected.reading {
    box-shadow: inset 0 0 0 1px var(--accent-dim), inset 2px 0 0 0 var(--accent);
}

.sem-trait-row.blocked {
    opacity: 0.4;
    cursor: not-allowed;
}

/*
    A trait there is no room for yet is still a trait the player may take: it is marked rather than
    put out of reach, so building towards a total works from either direction.
*/
.sem-trait-row.overspends {
    color: var(--warn);
}

.sem-trait-row.overspends .cost,
.sem-trait-row.overspends .cost.gain {
    color: var(--warn);
    font-weight: 600;
}

.sem-trait-row img,
.sem-trait-row .no-icon {
    width: var(--icon-sm);
    height: var(--icon-sm);
    flex-shrink: 0;
    object-fit: contain;
}

.sem-trait-row .name {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.sem-trait-row .cost {
    color: var(--gold);
    font-variant-numeric: tabular-nums;
}

.sem-trait-row .cost.gain { color: var(--good); }

/* Recessed inside the card holding it, by the rule written above .sem-detail. */
.sem-trait-detail {
    margin-top: var(--gap);
    padding: var(--space-lg) var(--space-lg);
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

.sem-trait-detail h4 {
    margin: 0 0 var(--space-sm);
    font-size: var(--text-lg);
}

/* ---------------------------------------------------------------- combobox

   Type anything, or open the list. The browser's own datalist filters itself by what has been
   typed, so it empties as soon as a name is entered; this does not. */

.sem-combo {
    position: relative;
    display: flex;
}

.sem-combo input[type="text"] {
    padding-right: calc(var(--combo-toggle) + var(--space-xs));
}

.sem-combo .toggle {
    position: absolute;
    top: 1px;
    right: 1px;
    bottom: 1px;
    width: var(--combo-toggle);
    padding: 0;
    background: none;
    border: none;
    border-radius: 0 var(--radius) var(--radius) 0;
    color: var(--text-dim);
    line-height: 1;
}

.sem-combo .toggle:hover { background: var(--bg-hover); color: var(--text); }

.sem-combo .options {
    position: absolute;
    top: calc(100% + 2px);
    left: 0;
    right: 0;
    z-index: 20;
    max-height: 260px;
    overflow-y: auto;
    margin: 0;
    padding: var(--space-2xs);
    list-style: none;
    background: var(--bg-panel);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    box-shadow: var(--shadow-popup);
}

.sem-combo .options button {
    display: block;
    width: 100%;
    padding: var(--space-xs) var(--space-md);
    background: none;
    border: none;
    border-radius: var(--radius-sm);
    color: var(--text);
    font: inherit;
    text-align: left;
}

.sem-combo .options button:hover { background: var(--bg-hover); }
/* A menu's current value, not a chosen tile: it wants marking, not framing. */
.sem-combo .options button.selected { color: var(--accent); }

/* ---------------------------------------------------------------- name fields

   Three names and the button that fills them, side by side, because they are one answer given three
   ways rather than three separate questions. */

/*
    Wrapping rather than a grid of a stated width.

    It had been a grid of three columns, or four where a row held four fields, dropping to one at
    700px — so the empire's four names went from a row of four straight to a column of four, with
    nothing in between on the widths most monitors actually are. Every count was also written into a
    selector twice: once as repeat(3), once as a :has(:nth-child(5)) that meant "and four when there
    are four".

    A flex row with a sensible basis says the same thing once and answers every width: four across
    becomes two, then one, as the room runs out, and a row holding two fields is simply a row holding
    two fields rather than a case anyone had to write down.

    200 rather than something rounder because of what the column actually is. With the preview beside
    them the sections get about 900px, and four fields at 220 want 916 — so the empire's row broke to
    two lines on a 1300px monitor, which is the width it most wants to be one. At 200 they fit, and
    they grow to fill whatever is really there.
*/
.sem-name-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--gap);
    align-items: end;
}

.sem-name-row > .sem-field {
    flex: 1 1 200px;
    min-width: 0;
    margin-bottom: 0;
}

/*
    The die is not a field and does not share their width: it is one button, and it belongs at the
    end of whatever row it lands on.
*/
.sem-name-row > .sem-field:has(> .sem-randomise) {
    flex: 0 0 auto;
}

/*
    The ruler's likeness beside the controls that change it. The figure keeps its own width so the
    controls take whatever is left; below the breakpoint they stack instead, since a 332px portrait
    and a column of steppers do not both fit on a phone.
*/
.sem-appearance {
    display: flex;
    gap: var(--space-2xl);
    align-items: flex-start;
}

.sem-appearance > .figure {
    flex: none;
    width: 332px;
    min-height: 220px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;
}

/*
    The five steppers as a wrapping row rather than one per line. Each is a fixed-width control, so
    at full width they form a grid beside the portrait and stack only when they no longer fit —
    which is what the game does with the same six spinners.
*/
.sem-appearance > .controls {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-lg) var(--space-2xl);
    align-content: start;
}

.sem-appearance > .controls > .sem-stepper {
    flex: 0 1 220px;
    margin-bottom: 0;
}

@media (max-width: 800px) {
    .sem-appearance { flex-direction: column; }

    /*
        The figure cannot be reflowed, only scaled: every layer of it is placed at an offset
        measured against a 332 by 220 picture, so narrowing the box just cut the ruler's shoulder
        off — on a 375-wide phone, 23 pixels of them, quietly, behind the box's own clipping.

        So the box becomes as wide as there is room for and the figure is scaled into it. The
        aspect ratio keeps the box the height the scaled figure actually needs rather than the
        height the unscaled one would have wanted; the max-width stops it growing past the size the
        artwork was drawn at, which would only blur it. A browser that cannot divide one length by
        another leaves the scale unset and clips exactly as before, which is a worse picture rather
        than a broken one.
    */
    .sem-appearance > .figure {
        width: 100%;
        max-width: 332px;
        min-height: 0;
        aspect-ratio: 332 / 220;
        container-type: inline-size;
    }

    .sem-appearance > .figure .sem-figure {
        transform-origin: top left;
        scale: min(1, calc(100cqw / 332px));
    }
}

/*
    A portrait rebuilt from its pieces. Every layer was trimmed to its own content and remembers
    where it came from, so each is placed at its own offset inside a box the size of the original
    frame rather than stretched to fill anything.
*/
.sem-figure {
    position: relative;
    flex: none;
}

.sem-figure img {
    position: absolute;
    image-rendering: auto;
}

/* One of the game's spinners: arrows either side of where you are in a list. */
/*
    The arrows belong either side of the value, not either side of the panel. The value was flex:1,
    so on the ruler's controls column it grew to the full width and pushed the right arrow an inch
    and a half away from the left one. The game's own spinner is a fixed width with its arrows
    adjacent; this is the same idea, capped so a long caption ellipses rather than spreading.
*/
.sem-stepper > .control {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    max-width: 220px;
}

.sem-stepper button {
    flex: none;
    width: var(--stepper);
    padding: var(--space-xs) 0;

    /* The glyphs are a single chevron each and want to be bigger than the label beside them. */
    font-size: var(--text-lg);
    line-height: 1;
}

.sem-stepper .value {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-md);
    padding: 0 var(--space-xs);
}

.sem-stepper .caption {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: var(--text-sm);
}

.sem-stepper .count {
    flex: none;
    font-variant-numeric: tabular-nums;
    font-size: var(--text-sm);
    color: var(--text-dim);
}

/* The paragraph the game writes about an empire, and the one a player writes about their species. */
.sem-lore {
    margin: var(--gap) 0 0;
    padding: 0 var(--space-2xs);
    font-size: var(--text-sm);
    line-height: 1.5;
    color: var(--text-dim);
    white-space: pre-wrap;
}

/*
    Something folded away, with a strip naming it and saying what is inside. A biography most players
    never write; the room and city pickers, which are two long lists of pictures in one section.

    The strip is styled as a control rather than as a caption, so a shut one reads as a thing that
    opens rather than as a heading for something that has gone missing.
*/
.sem-fold {
    margin-top: var(--gap);
}

/*
   Given the shape of a control rather than of a caption.

   It had been an uppercase micro-label in --text-dim with a --text-faint caret, which is the styling
   this sheet uses for the heading above a field — so it read as a label for a box that was not
   there, and two of the designer's fields looked as though they had gone missing. A bordered strip
   in the raised colour reads as a thing that opens, which is what it is.
*/
.sem-fold > summary {
    display: flex;
    align-items: baseline;
    gap: var(--space-md);
    padding: var(--space-sm) var(--space-lg);
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    color: var(--text);
    cursor: pointer;
    list-style: none;
}

.sem-fold > summary::-webkit-details-marker { display: none; }

.sem-fold > summary:hover { background: var(--bg-hover); }

/* Squared off where the box below meets it, so open they read as one piece. */
.sem-fold[open] > summary {
    border-bottom-color: transparent;
    border-radius: var(--radius) var(--radius) 0 0;
}

.sem-fold > summary .chevron {
    width: 12px;
    color: var(--text-faint);
}

.sem-fold[open] > summary .chevron { rotate: 90deg; }

.sem-fold > summary .title {
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

/* The opening words of what is written, or the name of what is chosen, cut to one line. */
.sem-fold > summary .state {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    color: var(--text-dim);
    font-size: var(--text-sm);
    white-space: nowrap;
    text-overflow: ellipsis;
}

.sem-fold > textarea {
    width: 100%;
    border-radius: 0 0 var(--radius) var(--radius);
    resize: vertical;
}

/*
    A list inside a fold continues the frame the strip above it opens, the way the biography's box
    does. Its own top corners are squared and its top border dropped, so the two read as one box
    with a lid rather than as a strip with a separate box under it.
*/
.sem-fold[open] > .sem-scroll-list {
    border-top: none;
    border-radius: 0 0 var(--radius) var(--radius);
}

/* ------------------------------------------------- pickers that pair up

   The flag's patterns and symbols, and the room's rooms and cities. Two long lists of pictures
   that belong to one decision: side by side where there is room for both, and one at a time behind
   a pair of tabs where there is not.

   The narrow arrangement used to stack them instead, and that was the one not worth its cost - two
   boxes of pictures under a picture is a section taller than the window it opens in, which in a
   dialog is the only arrangement there was ever room for.

   What makes it safe to show one where two will not fit is that the answer to both is drawn above
   them and does not move: the flag wears its pattern and its emblem, the scene has its room and its
   city in it. The list you are not looking at has nothing to tell you that the picture is not
   already telling you - so its tab carries only the name of what is chosen in it, which is what the
   strip over that list carries when both are on show. */

.sem-pickers {
    /*
        Measured against the pickers rather than the window. They are two containers deep inside the
        designer's left column and deeper still inside a dialog - which at a 1600px window is 832px
        across - so a viewport width is a guess about them, and the arrangement below and the label
        that goes with it have to agree about which one is in force. A guess cannot promise that.
    */
    container-type: inline-size;

    /*
        A row that wraps, and every part of the arrangement written on the things inside it rather
        than here: an element is never its own container, so a rule in the query below can reach the
        strip and the two lists but not the box holding them. Which turns out to be the better place
        for it anyway - one threshold decides everything, with no second number to keep in step.

        Static, so a strip sits flush on the list under it: the space between the columns is the
        second column's own margin, and there is no row gap to prise the two apart when they stack.
    */
    display: flex;
    flex-wrap: wrap;
    align-items: start;
    min-width: 0;
    margin-top: var(--gap);
}

.sem-picker {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

/* A whole row each, until there is room for two. */
.sem-pickers > .sem-picker,
.sem-picker-tabs { flex: 1 0 100%; }

/*
    The list that is not on show. A class rather than the hidden attribute, because .sem-picker is a
    flex container and a bare [hidden] would lose to it.
*/
.sem-pickers > .sem-picker.away { display: none; }

/*
    The label over a list: what it is, then what is chosen in it, on one line.

    Worn twice, because the two arrangements need it in two places - as a tab where one list is on
    show, and as a strip over each column where both are. Only ever one of them is in the page.

    One line, and always. Stacking the name under the title cost the strip a second row of height to
    say what one row had room for; the name gives up its tail to an ellipsis instead, which is the
    right thing for it to give up.
*/
.sem-picker-tabs button,
.sem-picker > .head {
    align-items: baseline;
    gap: var(--space-md);
    padding: var(--space-sm) var(--space-lg);
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: var(--radius) var(--radius) 0 0;
    font: inherit;
    text-align: left;
}

/* Never squeezed: the title is what the list is, and the name beside it is what it happens to hold. */
.sem-picker-tabs button .title,
.sem-picker > .head .title {
    flex: none;
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

.sem-picker-tabs button .state,
.sem-picker > .head .state {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    font-size: var(--text-sm);
    white-space: nowrap;
    text-overflow: ellipsis;
}

/*
    The tabs, for one list at a time. Full width between them and sharing their border, so the
    strip's whole lower edge is tab and the box below can open into the pressed one without a seam
    to explain. Halves rather than tight to their text because each carries a name that can run
    long, and a strip whose tabs resize as the empire changes emblem is a strip that moves under
    the pointer.
*/
.sem-picker-tabs { display: flex; }

.sem-picker-tabs button {
    flex: 1;
    min-width: 0;
    display: flex;
    color: var(--text-dim);
    cursor: pointer;
}

/* One border between neighbours rather than two. */
.sem-picker-tabs button + button { margin-left: -1px; }

.sem-picker-tabs button:hover { background: var(--bg-hover); }

/*
    The pressed one is the box below, carried up: the same surface, and no edge between them. The
    others keep their lower border and stay a step above it, which is the difference between a lid
    that is off and a lid that is on.
*/
.sem-picker-tabs button[aria-pressed="true"] {
    background: var(--bg);
    border-bottom-color: transparent;
    color: var(--text);
    cursor: default;
}

/*
    The strip over a column, for the arrangement that shows both. Drawn only there: with two lists
    open there is nothing to press and nothing to choose between, so it is a heading and says so by
    having no pointer, no pressed state and no way to be operated.
*/
.sem-picker > .head {
    display: none;
    border-bottom-color: transparent;
    color: var(--text);
}

/* The list continues the frame the strip above it opens, so the two read as one box. */
.sem-picker > .sem-scroll-list {
    border-top: none;
    border-radius: 0 0 var(--radius) var(--radius);

    /*
        A height rather than a ceiling, and now for two reasons. Side by side, as ceilings the taller
        list reached it and the shorter stopped where its contents did, and the pair sat forty pixels
        out of step. Tabbed, sixty-six rooms and eleven cities are two boxes hundreds of pixels
        apart, and the difference would come out of the page the moment the other tab was pressed.
    */
    height: clamp(240px, 34vh, 560px);
    max-height: none;
}

/*
    Room for both. 460 pixels is where a list of pictures stops being a column of one and a half;
    twice that and the gap between them is 930. Above it there is nothing to switch between, so the
    tabs go and each list is labelled over its own column.

    Whichever tab was last pressed on a narrower screen is forgotten here rather than obeyed - the
    reader who widens the window gets both lists, not the one they left showing. That is the whole
    reason this is a class the stylesheet may override and not a <details> the browser owns: shut
    one on a laptop, plug in a monitor, and an attribute would have left the second column empty
    with nothing able to reopen it.
*/
@container (min-width: 930px) {
    .sem-picker-tabs { display: none; }

    /* Half each, less half the space between them, which the second one carries. */
    .sem-pickers > .sem-picker { flex-basis: calc(50% - var(--gap) / 2); }

    .sem-pickers > .sem-picker + .sem-picker { margin-left: var(--gap); }

    .sem-pickers > .sem-picker.away { display: flex; }

    .sem-picker > .head { display: flex; }
}

/* A colour beside the name of it, in the line a tab or a strip gives to what is chosen. */
.sem-picker-tabs button .state > .sem-dot,
.sem-picker > .head .state > .sem-dot {
    margin-right: var(--space-2xs);
}

/*
    The flag's own colours, wearing the picker arrangement rather than a variant of it: two palettes
    side by side where there is room, one at a time behind tabs where there is not.

    Its own threshold, because what has to fit is different. Two lists of pictures want 930px
    between them; two palettes want eight swatches to a row, which is the shape the game's own
    colour picker uses and comes to about 220 each.

    A basis rather than a share of the row, so the row itself wraps: beside the flag while there is
    260px left over for it, under the flag when there is not. That is the phone, and the reason for
    it - the palettes had been squeezed into whatever a 375px screen leaves beside a 160px picture,
    which is not enough for one swatch and pushed the dialog sideways.
*/
.sem-pickers.colors {
    flex: 1 1 260px;
    margin-top: 0;
}

@container (min-width: 460px) {
    .sem-pickers.colors > .sem-picker-tabs { display: none; }

    .sem-pickers.colors > .sem-picker { flex-basis: calc(50% - var(--gap) / 2); }

    .sem-pickers.colors > .sem-picker + .sem-picker { margin-left: var(--gap); }

    .sem-pickers.colors > .sem-picker.away { display: flex; }

    .sem-pickers.colors > .sem-picker > .head { display: flex; }
}

/*
   Three tabs have to fit a phone, and two rules above conspire to stop them: a tab's title is
   deliberately flex:none - "never squeezed", because the title is what the list is - and the colour
   dot beside it cannot shrink either. So a tab's narrowest is its title plus its dot plus its
   padding, and three of those at the usual --space-lg came to 338px inside a 329px dialog, which is
   a horizontal scrollbar on a 375px screen.

   The padding is the part worth giving up. It buys nothing at three tabs that it did at two, and
   taking it back to --space-sm leaves the title and the dot untouched - which is the order these
   things should be sacrificed in.
*/
.sem-pickers.colors.three > .sem-picker-tabs button {
    gap: var(--space-sm);
    padding-right: var(--space-sm);
    padding-left: var(--space-sm);
}

/*
   And the padding was not enough either. It bought about forty-two pixels against a rigid minimum
   near two hundred and ninety-six, which fits a 375px phone with eleven pixels to spare per tab -
   so a 360px device, a wider platform font, or a base size above the browser default puts the
   scrollbar back. Below 460px of container width the tab strip is what is shown whether the
   palettes sit beside the flag or wrapped beneath it, which is why it was seen both ways.

   So here, and only here, the title gives up "never squeezed". Three of them is the one place that
   promise cannot be kept, and an ellipsised PRIMARY still says which slot it is - where a dialog
   that scrolls sideways says nothing and hides the palette as well.
*/
.sem-pickers.colors.three > .sem-picker-tabs button .title {
    /* flex-shrink, not only min-width: the rule above is flex:none, which pins it either way. */
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/*
   And the strip itself, which is the part that was actually overflowing. It is flex:1 0 100% - a
   basis of the full width and no shrink - which reads as "exactly as wide as its parent" and is
   not: min-width on a flex item defaults to auto, which is a floor of its own content, so the
   strip was clamped *up* from the 274px it was given to the 296px its three buttons wanted. The
   buttons could already shrink; nothing was asking them to.
*/
.sem-pickers.colors.three > .sem-picker-tabs {
    min-width: 0;
}

/*
   Three of them, which is the flag: primary, secondary and tertiary, one per channel of the
   background.

   695px, and the number is arithmetic rather than taste. A palette wants eight swatches to a row,
   which is the shape the game's own colour grid uses: eight at 24px, seven 3px gaps between them
   and 6px of padding either side is 225. Three of those with a 10px gap between the columns is
   3 x 225 + 2 x 10 = 695. Below it the three go back to tabs rather than becoming three columns of
   seven, because a palette that wraps at seven puts the last row out of step with the game's and
   there is no reading it against a screenshot any more.

   The two-column rules above would otherwise apply here and wrap the third underneath, alone at
   half width - so the band between 460 and 695 has to put the tabs back explicitly.

   A class rather than :has(.sem-picker:nth-child(3)), because the count is known where the markup
   is written and reading it back out of the DOM is a rule that silently stops applying the day
   something else is added to the row.
*/
@container (min-width: 460px) and (max-width: 694px) {
    .sem-pickers.colors.three > .sem-picker-tabs { display: flex; }

    /*
       A whole row each, which is what the base rule says and what this has to say again to beat the
       two-column rule above it. Written as flex-basis:auto once, which was wrong in a way worth
       recording: auto means "as wide as the content", and a palette's content is seventy-two
       swatches in a row, so each picker claimed a max-content width of well over a thousand pixels.
       That inflated the block's own intrinsic width, which stopped it wrapping under the flag - it
       sat beside it instead - and put a horizontal scrollbar on the dialog at every narrow size.
    */
    .sem-pickers.colors.three > .sem-picker { flex: 1 0 100%; }

    .sem-pickers.colors.three > .sem-picker + .sem-picker { margin-left: 0; }

    .sem-pickers.colors.three > .sem-picker.away { display: none; }

    .sem-pickers.colors.three > .sem-picker > .head { display: none; }
}

@container (min-width: 695px) {
    .sem-pickers.colors.three > .sem-picker {
        flex-basis: calc(33.333% - var(--gap) * 2 / 3);
    }
}

/*
    The palette continues the frame the strip above it opens, as a picker's list does - and scrolls
    inside it for the same reason theirs do. Seventy-two swatches is nine rows, and a palette that
    took its own height pushed the flag off a phone screen: the flag is the answer to which colour
    to press, and scrolling away from the question to reach the answers is the arrangement this was
    changed to avoid.
*/
.sem-pickers.colors .sem-swatches {
    margin-top: 0;
    padding: var(--space-md);
    justify-content: start;
    max-height: clamp(200px, 42vh, 440px);
    overflow-y: auto;
    background: var(--bg);
    border: 1px solid var(--border);
    border-top: none;
    border-radius: 0 0 var(--radius) var(--radius);
}

/*
    One scroller per picker. The patterns grid carries a ceiling of its own, which is right where it
    is the only thing on the row - but inside a picker the box around it already scrolls, and a
    scroller inside a scroller left the patterns short of its neighbour with a band of nothing under
    it.
*/
.sem-picker .sem-thumbs.sem-patterns {
    max-height: none;
    overflow: visible;
}

/*
    The spawn setting: the game's own cycling button, with the state it is showing named beside it.
    The picture alone is a puzzle — three lit variants of one shape — so the words carry the meaning
    and the button carries the recognition.
*/
/*
    The button is the picture and nothing else. It had been 74 by 58 around roughly forty pixels of
    artwork, with a full gap beside it and another above the row — three separate paddings for one
    small toggle. The field above already provides the space this row was adding on top of.
*/
.sem-spawn-row {
    display: flex;
    align-items: center;
    gap: var(--space-md);
}

.sem-spawn {
    flex: none;
    width: 38px;
    height: 34px;
    padding: 0;
    background: none;
    border: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
}

.sem-spawn img {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
}

.sem-spawn:hover img { filter: brightness(1.15); }

/* The ring itself is global now; this only rounds it to the button it is drawn around. */
.sem-spawn:focus-visible { border-radius: var(--radius); }

/*
    The state's own word, and the thing the button is actually saying. Weight rather than size:
    it was set larger than the body text, which made a one-word label shout over the sentence
    explaining it.
*/
.sem-spawn-row .name {
    font-weight: 600;
    color: var(--text);
}

.sem-spawn-row .sem-hint { margin: var(--space-2xs) 0 0; }


/*
    The game's randomise button: a die, sized to sit level with the field beside it. Square, because
    the sprite is, and because four of these in a row read as a set only if they match.
*/
.sem-randomise {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: var(--round-btn);
    height: var(--round-btn);
    padding: 0;
}

.sem-randomise img {
    width: var(--icon-sm);
    height: var(--icon-sm);
    object-fit: contain;
}

/* Four sets of names, side by side where there is room for them. */
.sem-name-sets {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: var(--gap);
    margin-top: var(--gap);
}

@media (max-width: 1100px) {
    .sem-name-sets { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 700px) {
    .sem-name-sets { grid-template-columns: minmax(0, 1fr); }
}

/* ---------------------------------------------------------------- gender */

.sem-genders {
    display: flex;
    gap: var(--space-xs);
}

.sem-gender {
    display: flex;
    align-items: center;
    justify-content: center;
    width: var(--round-btn);
    height: var(--round-btn);
    padding: var(--space-xs);
    background: var(--bg-raised);
    border: 1px solid var(--border);
    font-size: var(--text-xs);
}

.sem-gender img {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
}

/* ---------------------------------------------------------------- preview species */

/*
    The species block that used to live in a second preview component. That component is gone — one
    showcase draws the empire in both places now — and its portrait is the reason worth recording:
    it was 56px wide with no height, inside a flex row that stretched it to whatever the traits
    beside it came to. Every trait added made the box taller and the picture, drawn to fit, smaller.
    The showcase's own face is a fixed 84 by 100 and does not move.
*/

/* ---------------------------------------------------------------- the list view

   Cards or a table, and the controls that choose between them.
*/

/*
   The row above both views, holding the one control that belongs to neither of them.

   The column picker used to sit at the other end of it. It went to the table, which is the only
   thing it has anything to say about.
*/
.sem-list-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-md);
    margin-bottom: var(--gap);
}

.sem-columns {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    min-width: 14rem;

    /*
       And no wider than what holds it. The floor above is a floor, not a width - without this the
       row took its size from the combo inside it and carried the whole page sideways.
    */
    max-width: 100%;
}

/*
   The picker at the end of the table's heading row: pushed there by the margin, and centred on the
   row rather than sitting on the heading's baseline, which is where a bordered control looks like
   it has slipped. Wide enough to read a few column names in and no wider - left to grow it would
   stretch to the table's full width, which is a long way to push one control.
*/
.sem-table-block > .head > .controls {
    margin-left: auto;
    align-self: center;
    max-width: min(26rem, 100%);
    min-width: 0;
}

/*
   What can be done to a handful of empires at once.

   Only on screen while there is a handful, so it is drawn as something that has arrived rather than
   as part of the furniture: its own ground, its own edge, and the count first because the count is
   the thing that makes the rest of it mean anything.
*/
/*
    What can be done to the ticked empires, on the table's own top edge.

    One line, and small. It appears and disappears as boxes are ticked, so anything taller moves the
    table under the pointer that is ticking them - which is why it is a row of icons at the size the
    editors use rather than the row of words it was.

    Allowed to wrap all the same, because on a phone six controls will not fit 375px and a row that
    scrolls sideways is one whose end nobody finds.
*/
.sem-bulk {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-xs);
    padding: var(--space-2xs) var(--space-md);
    background: var(--bg-raised);
    border-bottom: 1px solid var(--accent-dim);
}

/* Italic, so the one thing here that is a statement rather than a control reads as one. */
.sem-bulk > .count {
    margin-right: var(--space-2xs);
    font-style: italic;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/*
    A choice and the tick that carries it out: one action in two presses, and the space between two
    things in this row is what says they are not.
*/
.sem-bulk .set {
    display: flex;
    align-items: center;
    gap: var(--space-2xs);
    margin-left: var(--space-sm);
}

/*
    The game's own spawn control, at the height of the row and nothing else: no box, no padding, no
    border. It is a picture the player already knows from the game's designer, and a button drawn
    around it makes it look like one of this app's rather than one of the game's.
*/
.sem-bulk .sem-spawn.bare {
    width: auto;
    height: var(--icon-btn);
    padding: 0;
    background: none;
    border: 0;
}

.sem-bulk .sem-spawn.bare img {
    width: auto;
    height: 100%;
    max-width: none;
    max-height: none;
}

/* Pushed to the far end, away from the four that change something. */
.sem-bulk > .clear {
    margin-left: auto;
}

/*
   The search box in the heading's place, at the width the heading had rather than the full width a
   text box takes by default. It shrinks before the picker does, since a word is legible in a narrow
   box and a row of column chips is not.
*/
.sem-table-block > .head > .title {
    flex: 0 1 26rem;
    align-self: center;
    min-width: 0;

    /* The box and the toggle beside it, which is one question about the list and then another. */
    display: flex;
    align-items: center;
    gap: var(--space-md);
}

.sem-search {
    width: 100%;
}

/*
   Whose empires are being listed: one button that cycles rather than three to choose between, the
   way the game draws the spawn control and this app already draws it back.

   Written on rather than pictured. There is no artwork in the game for "mine" and "the shelf", and
   two invented glyphs beside a search box would be a puzzle where a word is an answer. It holds its
   own width across the three so the row beside it does not shift as it is pressed.
*/
.sem-preset-toggle {
    flex: 0 0 auto;
    min-width: 5.5rem;
    height: var(--control-h);
    padding: 0 var(--space-lg);
    background: var(--bg);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    color: var(--text);
    font: inherit;
}

.sem-preset-toggle:hover {
    border-color: var(--accent);
    color: var(--accent);
}

/* Showing everything is the resting answer, so it is drawn as one rather than as a choice made. */
.sem-preset-toggle.all {
    color: var(--text-dim);
}

.sem-columns > label {
    color: var(--text-dim);
    white-space: nowrap;
}

/*
   The combo fills the row and may be squeezed to fit it.

   A flex item will not shrink below its own contents unless it is told to, and with the chips on
   one line those contents are every column name laid end to end. Left at auto the picker stood at
   its full width, pushed past the right-hand edge of the table, and took the page sideways with
   it - which is the same shrink the control inside it already asks for, one level further out.
*/
.sem-columns > .sem-combo {
    flex: 1;
    min-width: 0;
}

/*
   A tick list that opens under a control, wearing the combo box's shell for the one listener that
   closes it on a press outside. Everything below undoes the parts of that shell which assume a text
   box: the combo is an input with a button beside it, and this is a row of pressable things.

   A row rather than one big button, because what is in it is pressable: a chip naming something
   chosen is the obvious place to press to stop asking for it, and a button cannot hold buttons.
*/
.sem-combo.sem-multi > .control {
    display: flex;
    align-items: center;
    gap: var(--space-2xs);

    /*
       A flex item of the combo around it, and a flex item will not shrink below its own contents
       unless told to. Left at auto the control took the width of every chip in it - eight civics
       came to 893 pixels inside a 218-pixel column - and carried the whole page sideways with it.
    */
    flex: 1;
    min-width: 0;

    padding: 3px var(--space-2xs);
    min-height: var(--icon-btn);

    background: var(--bg);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    cursor: pointer;
}

.sem-combo.sem-multi > .control:hover {
    border-color: var(--accent-dim);
}

/*
   The column picker is the exception, and stays on one line whatever is ticked.

   Every other one of these owns a row of the filter card and may grow down it. This one shares the
   table's heading, so a second line moves the grid itself down the page - which is a strange thing
   for ticking a column to do, and it happens at seven columns, well short of the eighteen on offer.
   What does not fit is cut off rather than scrolled: the panel behind the arrow is the place these
   are read, and it lists every one of them with a tick beside those in use.
*/
.sem-columns .sem-combo.sem-multi .face {
    flex-wrap: nowrap;
    overflow: hidden;
}

/*
   What was chosen, drawn as the chips it is rather than counted, and wrapping onto as many lines as
   it takes.

   It scrolled on one line for a while, which kept every filter the same height - and hid whichever
   choices did not fit behind a gesture nobody makes. A card whose whole job is saying what is being
   asked for cannot keep half the answer off the end of a line. The control grows instead.
*/
.sem-combo.sem-multi .face {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2xs);

    /* Centred, so anything shorter than the line sits on it rather than above it. */
    align-items: center;

    flex: 1;
    min-width: 0;
}

/* The label standing in for a choice is one line of text, and elides rather than scrolling. */
.sem-combo.sem-multi .face.empty {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    padding-left: var(--space-2xs);
    color: var(--text-dim);
}

/*
   One thing that was chosen, and the way to stop asking for it. A press lands here rather than on
   the control behind, which is what makes the obvious gesture - press the thing you no longer want
   - do the obvious thing.
*/
.sem-pick {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2xs);
    flex: none;

    padding: 1px var(--space-2xs) 1px 2px;
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    color: var(--text);
    font: inherit;
    font-size: var(--text-sm);
    white-space: nowrap;
}

.sem-pick:hover {
    background: var(--bad);
    border-color: var(--bad);
    color: var(--bg);
}

/* Nothing in front of a chip that has no icon, or the name sits off its own left edge. */
.sem-pick:not(:has(.sem-icon)) {
    padding-left: var(--space-sm);
}

.sem-pick .sem-icon {
    width: var(--icon-sm);
    height: var(--icon-sm);
}

.sem-pick > .drop {
    color: var(--text-faint);
}

.sem-pick:hover > .drop {
    color: var(--bg);
}

/*
   The switch between any and all, inside the control it acts on. Flat and small: it is a setting
   for the list beside it rather than a button in its own right, and drawn as one it read as a
   second control with a job of its own.
*/
.sem-combo.sem-multi .mode {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;

    width: 1.7rem;
    height: 1.5rem;
    padding: 0;

    background: none;
    border: none;
    border-right: 1px solid var(--border);
    border-radius: 0;
    color: var(--text-dim);
}

.sem-combo.sem-multi .mode:hover {
    background: none;
    color: var(--accent);
}

.sem-combo.sem-multi .mode[aria-pressed="true"] {
    color: var(--accent);
}

/* The arrow, which is the one part of the control that only ever opens the list. */
.sem-combo.sem-multi > .control > .toggle {
    position: static;
    flex: none;

    width: 1.4rem;
    height: 1.5rem;
    padding: 0;

    background: none;
    border: none;
    border-radius: 0;
    color: var(--text-dim);
    font: inherit;
}

.sem-combo.sem-multi > .control > .toggle:hover {
    background: none;
    color: var(--text);
}

.sem-combo.sem-multi .options > li > button {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    width: 100%;
    text-align: left;
}

/* A fixed box for the tick, so the names line up whether or not one is next to them. */
.sem-combo.sem-multi .options .tick {
    flex: none;
    width: 1em;
    color: var(--accent);
}

/*
   The game's own icon beside the name. Half of these lists are things a player knows by their
   picture first - an ethic, a trait, an origin - and a list of words is a list they have to read
   rather than recognise.
*/
.sem-combo.sem-multi .options .sem-icon {
    width: var(--icon-sm);
    height: var(--icon-sm);
}

.sem-combo.sem-multi .options .name {
    min-width: 0;
}

/*
   What hangs under the control: the box that narrows a long list, and the list itself.

   The positioning is here rather than on the list, which is where it used to be. With only the
   list taken out of the flow the box stayed in it, and the control is a flex row - so the box was
   laid beside the chips instead of above the options, taking their width and, being the taller of
   the two, growing the row and the card around it every time a list opened.
*/
.sem-combo.sem-multi .panel {
    position: absolute;
    top: calc(100% + 2px);
    left: 0;
    right: 0;
    z-index: 20;

    background: var(--bg-panel);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    box-shadow: var(--shadow-popup);

    /* So the list's corners are the panel's corners, whether or not there is a box above it. */
    overflow: hidden;
}

/*
   Inside the panel the list is an ordinary block again, and the only part of it that scrolls.
   Everything undone here is something the shared rule gives every combo's list on the way to
   making it the panel - which this one is not any more.
*/
.sem-combo.sem-multi .panel > .options {
    position: static;
    background: none;
    border: none;
    border-radius: 0;
    box-shadow: none;
}

/* The box that narrows a long list, held above it rather than scrolling away with it. */
.sem-combo.sem-multi .search {
    padding: var(--space-2xs);
    border-bottom: 1px solid var(--border);
}

.sem-combo.sem-multi .search input {
    padding: var(--space-2xs) var(--space-sm);
}

.sem-combo.sem-multi .options .none {
    padding: var(--space-sm) var(--space-md);
}

/*
   The filter card, above the tables and outside them.

   Its own card because these are questions about the empires rather than about the table: they
   survive a column being hidden, they narrow both lists at once, and a row of controls wedged into
   a table header would have to fight the columns for width.
*/
.sem-filters {
    background: var(--bg-panel);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: var(--space-md);
    margin-bottom: var(--gap);
}

/*
   A line, not a banner. It is the lid on a box of controls rather than a section of the page, and
   at a heading's size with a heading's room around it the card announced itself more loudly than
   the empires underneath.
*/
.sem-filters > .head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-md);
    margin-bottom: var(--space-sm);
}

.sem-filters > .head h2 {
    margin: 0;
    font-size: var(--text-md);
}

/* Nothing under the line when there is nothing under the line. */
.sem-filters:not(.open) > .head {
    margin-bottom: 0;
}

/*
   Which of the four sets of headings is showing.

   A row of buttons that each say whether they are pressed, as the view toggle and the picker tabs
   both are - a real tablist owes the reader arrow-key movement between its tabs, and this owes
   nothing it does not give.

   The funnel marks a tab with something set under it. Twenty-one of the twenty-eight headings are
   out of sight at any moment, and a filter left on behind one of them is a short list with no
   visible reason for being short.
*/
.sem-filter-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2xs);
    margin: var(--space-md) 0;
}

.sem-filter-tabs button {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2xs);
    padding: var(--space-2xs) var(--space-md);
}

.sem-filter-tabs button[aria-pressed="true"] {
    background: var(--bg-raised);
    border-color: var(--accent-dim);
    color: var(--text);
}

.sem-filter-tabs button .sem-action-icon {
    width: 0.85em;
    height: 0.85em;
    color: var(--accent);
}

/* The one control above the tabs, and the widest thing in the card. */
.sem-filters > .body > .search {
    max-width: 30rem;
}

/*
   Wide tracks, because what is in them is a row of names rather than a word: at eleven rems a
   filter with two civics in it was scrolling before it had been read. Sixteen fits three of most
   things, and the grid still gives a phone one column.
*/
.sem-filters .controls {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
    gap: var(--space-md);
}


@media (max-width: 560px) {
    .sem-filters .controls { grid-template-columns: minmax(0, 1fr); }
}

.sem-filters .sem-hint {
    margin: var(--space-md) 0 0;
}

.sem-clear {
    padding: var(--space-2xs) var(--space-md);
}

/*
   The heading, which is also the way to fold the card away.

   A button drawn as a heading rather than a heading with a button beside it: the whole line is the
   target, which on a phone is the difference between a fold and a game of hitting a chevron.
*/
.sem-filters .fold {
    display: flex;
    align-items: baseline;
    gap: var(--space-sm);

    padding: 0;
    background: none;
    border: none;
    color: inherit;
    font: inherit;
    text-align: left;
}

.sem-filters .fold:hover {
    background: none;
    color: var(--accent);
}

.sem-filters .fold > .chevron {
    color: var(--text-dim);
    font-size: var(--text-sm);
}

.sem-filters:not(.open) > .body {
    display: none;
}

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

.sem-table-block {
    margin-bottom: var(--space-2xl);
}

.sem-table-block > .head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-md);
    margin-bottom: var(--space-md);
}

.sem-table-block > .head h2 {
    margin: 0;
}

/*
   The one box that scrolls sideways.

   Eighteen columns do not fit a phone and are not meant to: the wrapper takes the overflow so the
   page never does, which is the rule everywhere else in this sheet. The column picker is how a
   narrow screen asks for the four columns it came for.
*/
.sem-table-wrap {
    overflow-x: auto;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--bg-panel);
}

.sem-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--text-sm);
}

/*
   The head, which does not stick, and the reason is worth writing down so nobody adds it back.

   The box around the table scrolls sideways, and a box that scrolls in one direction is a scroll
   container in both - so a sticky head inside it measures itself against that box rather than
   against the page. Given the header's own offset it was pushed that far down from the top of the
   table and drawn over the first row: the column names sat under the first empire's name.

   Sticking it to the box instead would mean giving the box a height of its own and a second
   scrollbar inside the page, which on a phone is two things to drag past each other. A page of
   twenty-five is short enough to read without it.
*/
.sem-table thead th {
    background: var(--bg-raised);
    border-bottom: 1px solid var(--border-strong);
    padding: 0;
    text-align: left;
    white-space: nowrap;
}

.sem-sort {
    display: flex;
    align-items: center;
    gap: var(--space-2xs);

    width: 100%;
    padding: var(--space-sm) var(--space-md);

    background: none;
    border: none;
    border-radius: 0;
    color: var(--text-dim);
    font: inherit;
    font-weight: 600;
    text-align: left;
}

.sem-sort:hover {
    background: var(--bg-hover);
    color: var(--text);
}

.sem-sort.sorted {
    color: var(--accent);
}

/* Kept at a fixed width, or every heading shifts sideways as the sort moves between them. */
.sem-sort > .arrow {
    display: inline-block;
    width: 0.8em;
    font-size: var(--text-sm);
}

.sem-table tbody tr {
    border-top: 1px solid var(--border);
    cursor: pointer;
}

.sem-table tbody tr:hover {
    background: var(--bg-hover);
}

/*
   One line per row, whatever is in it.

   Wrapped, a cell with five civics in it was five lines tall and every row a different height, so
   comparing one empire with the one under it meant reading down a ragged column. The table grows
   sideways instead and the box around it scrolls, which is what that box is for.
*/
.sem-table td,
.sem-table tbody th {
    padding: var(--space-sm) var(--space-md);
    vertical-align: middle;
    text-align: left;
    font-weight: normal;
    white-space: nowrap;
}

/*
   Where the empire sits in the file. Narrow, and the digits line up: a column of numbers that do
   not share a width is a column you cannot read down.
*/
.sem-table .index {
    width: 1%;
    padding-right: var(--space-sm);
    color: var(--text-dim);
    font-variant-numeric: tabular-nums;
    text-align: right;
}

/*
   The box that collects a row, at the left edge where a column of them can be run down with the eye.

   As narrow as its own content and no narrower: the cell blanks itself for the game's own empires,
   which are in the table but not in the file, so a column of boxes with gaps in it is the shape of
   the answer rather than an omission.
*/
.sem-table .pick {
    width: 1%;
    padding-right: 0;
    text-align: center;
}

.sem-table .pick input {
    display: block;
    margin: 0 auto;
    cursor: pointer;
}

/*
   What can be done to a row, held against the right edge while the table scrolls under it.

   Not a column in the picker: it is not something an empire is, it is what can be done to one, and
   a table whose delete button can be turned off is a table with a setting nobody wants. Stuck
   rather than merely last, because with twenty-eight columns to choose from the far end of the
   table is somewhere a reader would have to go looking for.

   A background of its own, since the rows slide beneath it - and the hover repeated, because a
   sticky cell that keeps its own colour is the one cell of a row that does not light up.
*/
.sem-table .actions {
    position: sticky;
    right: 0;
    z-index: 1;

    width: 1%;
    white-space: nowrap;
    text-align: right;
    background: var(--bg-panel);
    border-left: 1px solid var(--border);
}

/* The buttons sit at the right of the cell; the heading reads left, like every other heading. */
.sem-table thead th.actions {
    background: var(--bg-raised);
    text-align: left;
}

/*
   What the game wrote about one of its own, as a panel of the card.

   Prose rather than a row of controls, so the body is one column of text at a readable measure -
   every other panel is a picture beside a list of choices, and the flex row that lays those out
   would set this as one very long line.
*/
.sem-lore-panel > .body {
    display: block;
}

.sem-lore-panel .about {
    color: var(--text-dim);
    font-size: var(--text-sm);
    line-height: 1.5;
}

.sem-table tbody tr:hover .actions {
    background: var(--bg-hover);
}

.sem-table .actions button.icon {
    width: 1.7rem;
    height: 1.7rem;
}

.sem-position {
    padding: 0 var(--space-2xs);
    background: none;
    border: none;
    color: var(--text-dim);
    font: inherit;
    font-variant-numeric: tabular-nums;
}

.sem-position:hover {
    background: none;
    color: var(--accent);
    text-decoration: underline;
}

/*
   The box for typing a row's new position.

   Sized to the digits it holds, and drawn without the browser's own arrows. Those were the whole
   problem: two tiny chevrons in the platform's colours, stacked inside a twenty-three pixel box in
   a thirty-five pixel row, which no amount of padding made look like part of this page. The way to
   move a row a step at a time is the grip on the card and the arrow keys on it; here a number is
   typed and entered, so the arrows were offering a third way to do it badly.

   No height of its own, which is the point: the app's ordinary control height is nine pixels taller
   than the number it replaces, and setting it made every row grow by three the moment one was
   pressed. It keeps the padding it had, and the row does not move.
*/
.sem-table .index input {
    width: 3.75rem;
    padding: var(--space-2xs) var(--space-sm);
    font-variant-numeric: tabular-nums;
    text-align: center;
}

.sem-table .index input::-webkit-outer-spin-button,
.sem-table .index input::-webkit-inner-spin-button {
    appearance: none;
    margin: 0;
}

/* Firefox's spelling of the same thing, and the reason the box needs no room for arrows. */
.sem-table .index input {
    appearance: textfield;
    -moz-appearance: textfield;
}

/*
   The name column, which is the row's heading rather than a cell of it - it is what the other
   cells are about, and a screen reader reading across says which empire it is reading.
*/
.sem-table tbody th.name {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    white-space: nowrap;
}

.sem-table .sem-flag.tiny {
    flex: none;
    width: 26px;
    height: 26px;
}

/*
   The way in for a keyboard. The row carries the same press for a pointer, which can land anywhere
   along a line eighteen columns wide, but a row cannot take focus and a table full of empires that
   can only be opened with a mouse is a table half the readers cannot use.
*/
.sem-row-open {
    padding: 0;
    background: none;
    border: none;
    color: var(--text);
    font: inherit;
    font-weight: 600;
    text-align: left;
}

.sem-row-open:hover {
    background: none;
    color: var(--accent);
}

/*
   A value in a cell that has nothing more to say about itself - a shipset, a species class, a
   starting system. Shaped like the chip beside it so a row of mixed cells reads as one row, but
   flat: there is no description behind it and no panel to open.
*/
.sem-cell-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2xs);

    padding: 1px var(--space-sm);
    margin-right: var(--space-2xs);

    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    white-space: nowrap;
}

.sem-cell-chip .sem-icon {
    width: var(--icon-sm);
    height: var(--icon-sm);
}

/*
    What the colour is, above the palette that sets it. Full width of the pickers row, so the
    palettes below keep their own widths.
*/
.sem-map-preview {
    display: flex;
    flex-basis: 100%;
    align-items: center;
    gap: var(--space-sm);
    margin-bottom: var(--space-sm);
}

/*
   The same line for a colour with no shape to draw it as: a dot, its name, and the switch that
   decides whether the name was chosen or worked out.
*/
.sem-chosen-color {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    margin-bottom: var(--space-sm);
}

.sem-chosen-color > .name {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/*
    A scrap of territory, drawn the way the galaxy map draws one - see MapBlob.razor for the shader
    lines these four layers come from.

    Top to bottom: the black rim the shader multiplies in near the edge; the one lerp between the
    border colour at the rim and the fill colour inside, as a ramp because saturate() is one; and
    the void underneath, which is what the half-alpha interior is composited over. An automatic slot
    paints nothing and lets the layer below show, which is the hollow the two rings used to say.

    The radius is four different corners because a country is not a circle, and a circle here read
    as one more swatch.
*/
.sem-blob {
    display: inline-block;
    flex: none;
    width: var(--blob-size, 14px);
    height: var(--blob-size, 14px);
    border-radius: 58% 42% 51% 49% / 46% 55% 45% 54%;
    background:
        radial-gradient(closest-side at 50% 50%, rgb(0 0 0 / 0) 50%, rgb(0 0 0 / 0.25) 100%),
        radial-gradient(closest-side at 50% 50%,
            color-mix(in srgb, var(--blob-fill) 55%, transparent) 0 42%,
            var(--blob-border) 76% 100%),
        var(--bg-void);

    /*
        A ring, for the same reason the dot has one: a dark territory on a dark row is a hole
        otherwise, and an automatic pair is very nearly transparent.
    */
    box-shadow: 0 0 0 1px var(--border-strong);
}

/* Big enough to see what the two colours do to each other, for the picker that sets them. */
.sem-blob.large {
    --blob-size: 34px;
}

/*
    A colour, said as a colour: what a flag's palette holds, what the map draws an empire in.

    Kept to a dot rather than given a shape of its own, because the shape would be inventing a
    meaning the colour does not have. The ring is what keeps a dark one visible against a dark row
    and a pale one against a pale row, without knowing which it is.
*/
.sem-dot {
    /*
        Inline-block rather than nothing, so a width and a height apply wherever it is put. Every
        use of it began inside a flex row, which blockifies its children and hid the omission; the
        first one that was not - a colour beside its name inside an ellipsised line - drew nothing.
        Flex containers ignore this, so it costs the older ones nothing.
    */
    display: inline-block;
    vertical-align: -1px;
    flex: none;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--dot, var(--text-dim));
    box-shadow: 0 0 0 1px var(--border-strong);
}

/*
   And a value that does have something to say wears the chip from the empire's own panel, which
   already carries the icon and opens the description and effects on hover.
*/
.sem-table .sem-chip {
    display: inline-block;
    margin-right: var(--space-2xs);
}

/*
   A table too wide for its box drops the words off every chip that has a picture.

   The picture is most of what a chip says - a player knows an ethic by its icon long before they
   read it - and the word beside it is what makes the column three times as wide. So the words go
   first, and the table scrolls only once even the pictures will not fit. Which chips lose them is
   asked of the chip: one with no icon keeps its word, since without it there would be nothing left.

   Whether the table is too wide is not a question CSS can ask, so the class comes from a script
   that measures it with the words on. What that script must not do is written down beside it.

   Each picture is excluded from its own rule by name, because "> span" cannot tell a word from a
   picture and a picture is not always an img. A colour is a span with a background, so the rule
   hid the colour itself: a Colours column has nothing in it but the dot, and compacting it emptied
   the cell rather than shortening it. The icon rule reads the same way and is safe only because
   its branch never runs without a path; :not(.sem-icon) is there so it stays safe if the
   placeholder - which is also a span - ever reaches a cell.
*/
.sem-table-wrap.compact .sem-chip > button:has(img) > span,
.sem-table-wrap.compact .sem-cell-chip:has(.sem-icon) > span:not(.sem-icon),
.sem-table-wrap.compact .sem-cell-chip:has(.sem-dot) > span:not(.sem-dot) {
    display: none;
}

/* With the word gone the padding round it is a chip that is wider than it is tall. */
.sem-table-wrap.compact .sem-chip > button:has(img) {
    padding: var(--space-2xs);
}

.sem-table-wrap.compact .sem-cell-chip:has(.sem-icon),
.sem-table-wrap.compact .sem-cell-chip:has(.sem-dot) {
    padding: 1px var(--space-2xs);
}

/*
   How many at a time on the left, the way through them in the middle. The size belongs to the list
   rather than to the view, and the two lists are not the same length - so it sits here rather than
   with the column picker at the top of the page.
*/
.sem-pager {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-md);
    margin-top: var(--space-md);
}

.sem-pager > .rows {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    color: var(--text-dim);
}

.sem-pager > .rows select {
    width: auto;
    padding: var(--space-2xs) var(--space-sm);
}

.sem-pager > .turn {
    display: flex;
    align-items: center;
    gap: var(--space-md);
}

.sem-empty-line {
    padding: var(--gap);
    background: var(--bg-panel);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    color: var(--text-dim);
    margin: 0;
}

/* ---------------------------------------------------------------- the select screen */

/*
    The lists, and nothing beside them.

    There was a preview panel here, a column-wide summary of an empire you had to open another page
    to change. Pressing an empire opens the empire itself now, over the list, so the room the panel
    took is the list's - which is a column and a half of cards on a wide screen.
*/
.sem-select {
    display: grid;

    /*
        minmax(0, 1fr) rather than 1fr, which is minmax(auto, 1fr) and so refuses to go below the
        widest thing inside it. The cards want 300px plus their padding, so on a 375px phone the one
        column came out 362 wide in a 343 box and the page scrolled sideways by three pixels. The
        wide-screen rule below already says minmax(0, 1fr) for the same reason; this is the case it
        did not cover.
    */
    grid-template-columns: minmax(0, 1fr);
    gap: var(--gap);
    align-items: start;
}

/* ---------------------------------------------------------------- empire showcase */

/*
    An empire shown the way the game shows one on its select screen: a flag beside the name, then a
    framed portrait beside the species and its government. Everything below that is ours.
*/
.sem-showcase {
    display: flex;
    flex-direction: column;
    gap: var(--space-lg);
    padding: var(--gap);
    background: var(--bg-panel);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

.sem-showcase .identity {
    display: flex;
    align-items: center;
    gap: var(--gap);
}

.sem-showcase .identity .titles { flex: 1; min-width: 0; }

.sem-showcase .identity .name {
    font-size: var(--text-xl);
    font-weight: 600;
    line-height: 1.2;
}

.sem-showcase .identity .sub {
    color: var(--text-dim);
    font-size: var(--text-sm);
}

.sem-showcase .identity .sub.faint { color: var(--text-faint); }

/* A row of glyphs now, rather than two worded buttons that spaced themselves. */
.sem-showcase .identity .actions {
    display: flex;
    gap: var(--space-sm);
    flex-shrink: 0;
}

/*
   The game frames the portrait and sets the species beside it; so does this.

   Recessed rather than raised: this is a readout inside the showcase card, and --bg-raised with a
   border is what a tile you can click looks like everywhere else in the sheet.

   The lite card's panels are boxes of their own, so only the parts inside are shared below - the
   portrait, the name and the kind under it. Sharing the container as well drew a panel inside a
   panel.
*/
.sem-showcase .species {
    display: flex;
    gap: var(--space-lg);
    padding: var(--space-md);
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

/*
    As tall as what it stands beside, rather than a number of its own. The species name, its kind
    and the two lines of government and origin come to whatever they come to — four lines here, and
    fewer for an empire with no origin worth naming — so a stated height left a band of empty panel
    under the portrait whenever the text ran longer, which after the compaction it always did.

    The minimum is what a portrait needs to be recognised at all, for the case where the text is
    shorter than the picture rather than longer.
*/
.sem-lite-panel .face-button > .face,
.sem-showcase .species .face {
    align-self: stretch;
    width: var(--face-lg-w);
    height: auto;
    min-height: var(--face-lg-h);
    object-fit: cover;
    object-position: top center;
    flex-shrink: 0;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    background: var(--bg-void);
}

.sem-lite-panel > .body > .about,
.sem-showcase .species .about {
    display: flex;
    flex-direction: column;
    gap: var(--space-2xs);
    min-width: 0;
}

.sem-lite-panel .name { font-weight: 600; }

.sem-showcase .species .name { font-weight: 600; }

.sem-lite-panel .kind,
.sem-showcase .species .kind {
    color: var(--text-dim);
    font-size: var(--text-sm);
    margin-bottom: var(--space-xs);
}

.sem-showcase .species .line {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    font-size: var(--text-sm);
}

/*
    A set of choices as a heading and a row of named icons. Wrapping rather than scrolling: an
    empire has at most a handful of each, and a reader wants to see all of them at once.
*/
.sem-showcase-chips {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-sm);
}

/* Fainter, because these headings repeat down a dense column of chips. */
.sem-showcase-chips .heading {
    width: 100%;
    color: var(--text-faint);
}

/*
    A chip is a button now, because what it does is open the description of the thing it names. It
    keeps the shape it had — the styling below is the old chip's, moved onto the button inside it —
    since nothing about it should look newly clickable except the cursor and the hover.
*/
.sem-chip > button {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: var(--space-2xs) var(--space-md) var(--space-2xs) 3px;
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    color: var(--text);
    font: inherit;
    font-size: var(--text-sm);
    text-align: left;
    cursor: help;
}

.sem-chip > button:hover { background: var(--bg-hover); }

/*
   A chip for something the empire already has.

   Only a plan's civics use it. A plan names the whole government it means to end up with, most of
   which is usually the government it has now, so the ones being added have to be the ones that
   read as added. Held back rather than hidden: what is not changing is still part of the answer to
   what this empire will be.
*/
.sem-chip > button.dim {
    opacity: 0.5;
}

.sem-chip > button.dim:hover { opacity: 1; }

.sem-chip > button img {
    width: var(--icon-sm);
    height: var(--icon-sm);
    object-fit: contain;
}

/*
    One chip with the rest of its set behind it.

    The lead is drawn as a chip rather than being one: it opens the list instead of a description of
    itself, and the list holds the real chips. Same pill, same padding, same icon size, so a row that
    is a stack and a row that is chips line up on the same baseline.
*/
.sem-chip-stack {
    position: relative;
    display: inline-flex;
}

.sem-chip-stack > .lead {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: var(--space-2xs) var(--space-xs) var(--space-2xs) 3px;
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    color: var(--text);
    font: inherit;
    font-size: var(--text-sm);
    text-align: left;
    cursor: pointer;
}

.sem-chip-stack > .lead:hover,
.sem-chip-stack > .lead:focus-visible {
    background: var(--bg-hover);
    border-color: var(--accent-dim);
}

.sem-chip-stack > .lead > img {
    width: var(--icon-sm);
    height: var(--icon-sm);
    object-fit: contain;
}

/*
    How many more there are, as the count rather than a chevron: an arrow says a list opens and a
    number says that and how much of one, which is the difference between pressing to find out and
    knowing whether it is worth pressing.
*/
.sem-chip-stack > .lead > .more {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    box-sizing: border-box;
    min-width: var(--icon-sm);
    height: var(--icon-sm);
    padding: 0 3px;
    border-radius: var(--radius-pill);
    background: var(--bg);
    color: var(--text-dim);
    font-size: 0.72em;
    font-variant-numeric: tabular-nums;
    line-height: 1;
}

.sem-chip-stack > .lead > .more::before { content: "+"; }

/*
    The list, in the top layer where nothing can clip it - which is the whole point, since these hang
    off a cell inside a table that scrolls in both directions.

    Down the page rather than across, and left-aligned under the chip, so it reads as the order it is
    in: likeliest first, and each one under the last.
*/
.sem-chip-stack-list {
    position: fixed;
    margin: 0;
    inset: auto;
    max-height: min(20rem, calc(100vh - 2rem));
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: var(--space-xs);
    background: var(--bg-panel);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
}

/*
    The layout waits until it is open, and it has to.

    A closed popover is hidden by one rule in the browser's own stylesheet - display: none while it
    is not :popover-open - and any display an author writes beats it, whatever the specificity, for
    the plain reason that author styles win over the browser's. Written on the panel itself, the
    flex here held every list on the page permanently open: they were never popovers at all, only
    fixed boxes sitting at whatever coordinates were last written into them, which is why nothing
    would close them and why asking which popovers were open answered none.

    The description panel beside this one never had the problem because it never set display. This
    one needs to, so it sets it where the browser is no longer saying otherwise.
*/
.sem-chip-stack-list:popover-open {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-2xs);
}

/*
    The description itself, drawn in the top layer.

    Placed from sem.js rather than by the browser: the top layer has no idea what a popover was
    opened from, and a panel that appeared in the middle of the screen would be describing a chip
    the reader has to go looking for. Fixed at nothing in particular here, since those coordinates
    are written in as it opens.
*/
.sem-chip-detail {
    position: fixed;
    margin: 0;
    inset: auto;
    width: min(22rem, calc(100vw - 2rem));

    /*
        Said here because the panel inherits from wherever its chip is written, and the top layer
        does not change that: it is drawn out of the flow, but its styles still come down the DOM.
        A table's cells are nowrap - one line per row, whatever is in it, which is what makes a
        column of empires comparable - and a paragraph of the game's prose inheriting that came out
        as one line the width of the sentence, with a horizontal scrollbar under it. The same panel
        on the lite card, whose ancestors say nothing about wrapping, always read properly.
    */
    white-space: normal;
    max-height: min(24rem, calc(100vh - 2rem));
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: var(--space-lg);
    background: var(--bg-panel);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    color: var(--text);
}

.sem-chip-detail h4 {
    margin: 0 0 var(--space-sm);
}

.sem-chip-detail .sem-detail-lore {
    margin-bottom: var(--space-md);
}

/*
    Everything the empire adds up to, ruled off at the foot of the panel as the game rules it off.
    Shared with the designer's own pane, so the rule is written against the component rather than
    against a wrapper only one of them has.
*/
.sem-showcase .sem-combined {
    padding-top: var(--space-lg);
    border-top: 1px solid var(--border);
}

.sem-showcase .sem-combined h4 { margin: 0; }

/* The heading and the switch that changes what is under it, on one line. */
.sem-combined .heading {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-md);
    margin-bottom: var(--space-sm);
}

/*
   Small text, but not a small target: 54 by 19 was the only control in the designer under 24 pixels
   in either direction, and it sits beside a panel that is scrolled with a thumb.
*/
.sem-combined .compare {
    min-height: 24px;
    padding: 0 var(--space-sm);
    font-size: var(--text-xs);
}

.sem-combined .compare[aria-pressed="true"] {
    color: var(--accent);
    border-bottom-color: var(--accent);
}

/*
   What an edit changed: the old number, an arrow, the new one, and the difference. Both numbers are
   shown because "-10%" says how far something moved without saying what it moved from, and comparing
   the two is the whole point of the view.
*/
/*
   Five columns shared by every row, rather than five widths per row.

   Each li laid its own out, so a "was" of "-10%" and one of "+2" took different widths and no two
   rows agreed where the arrow sat. subgrid gives them one set of tracks, which is what makes a
   column of numbers readable at all.
*/
.sem-modifiers.changes {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto auto auto auto;
    column-gap: var(--space-md);
}

.sem-modifiers.changes li {
    display: grid;
    grid-template-columns: subgrid;
    grid-column: 1 / -1;
    gap: 0;
}

.sem-modifiers.changes .was,
.sem-modifiers.changes .arrow {
    color: var(--text-faint);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.sem-modifiers.changes .value {
    min-width: 0;
    text-align: right;
}

.sem-modifiers.changes .delta {
    min-width: 3.5rem;
    font-variant-numeric: tabular-nums;
    text-align: right;
    white-space: nowrap;
}

.sem-modifiers.changes .delta.good { color: var(--good); }
.sem-modifiers.changes .delta.bad { color: var(--bad); }

/*
    The effects can run long, so they scroll within the panel rather than stretching it. Given more
    room they get more of it: a preview 620 wide can afford to show twice as many at once, and the
    list is the part a reader actually dwells on.
*/
.sem-showcase .sem-combined .sem-modifiers {
    max-height: 320px;
    overflow-y: auto;
}

@media (min-width: 1500px) {
    /*
        Two columns of modifiers rather than one long scroll. They are short rows — a label and a
        number — and stacking them singly in a panel this wide wasted most of it.
    */
    .sem-showcase .sem-combined .sem-modifiers {
        max-height: 420px;
        columns: 2;
        column-gap: var(--gap);
    }

    .sem-showcase .sem-combined .sem-modifiers li {
        break-inside: avoid;
    }
}

/* ---------------------------------------------------------------- appearance */

/*
    The scene at the top of the appearance tab, where every choice on it lands.

    Two thirds of the room's own 952 pixels rather than all of it. At full size the picture pushed
    the list of rooms it is a preview of past the bottom of the screen, so choosing one meant
    scrolling away from the thing that was changing — and the room, the city and the world are all
    recognisable well before the artwork's own width. Still capped, so it is never drawn larger than
    the picture behind it.
*/
.sem-scene {
    max-width: 640px;
    margin-bottom: var(--gap);
}

/*
    A list that scrolls in its own box rather than down the page, so whatever it is changing stays
    on screen while it is being changed. The rooms are the case that needs it: choosing one is
    choosing what the scene above looks like, and the scene had scrolled out of sight long before
    the list ran out.

    Sized against the window rather than in pixels, because what has to fit is the scene plus this,
    and the scene is a picture that scales. A third of the window: measured at 1440x900 the section
    above this comes to 481px — its heading, the 340px scene, the city field and the room label —
    which leaves 307 of the 788 the page chrome does not take. The floor keeps two rows of
    thumbnails visible on a short window, where a proportional height alone would leave a slot.
*/
.sem-scroll-list {
    max-height: clamp(240px, 34vh, 560px);
    overflow-y: auto;
    padding: var(--space-md);
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

/* The first heading inside the box has the box's own padding above it already. */
.sem-scroll-list > .sem-subhead:first-child { margin-top: 0; }

/*
    On a phone the box is the tighter of the two constraints, so its contents pack rather than fill.
    At the default two-hundred-pixel track a 375px screen fits one room per row, which put exactly
    one of sixty-six in a 276px box — scrolling a list one item at a time is not choosing from it.
    Half the track fits two, and the scene here is only 113px tall, so the box can take more of what
    is left as well.
*/
@media (max-width: 700px) {
    .sem-scroll-list { max-height: clamp(240px, 48vh, 560px); }

    .sem-picker > .sem-scroll-list { height: clamp(240px, 48vh, 560px); }

    .sem-scroll-list .sem-thumbs {
        grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
        gap: var(--space-sm);
    }
}

/*
    The flag patterns, at the seventy pixels the game's own pattern list uses. Each tile is a live
    flag rather than a picture, and there are sixty-three of them — masks are not lazily fetched the
    way images are, so the rows out of sight are told not to render until they are scrolled to.
*/
.sem-patterns {
    grid-template-columns: repeat(auto-fill, minmax(70px, 1fr));
    max-height: 300px;
    overflow-y: auto;
    margin-bottom: var(--gap);
}

/*
   Smaller tiles on a phone, because the number of them on screen is the whole of what makes a
   picker of two hundred and eighty-three emblems usable. At the desktop's sizes a phone shows four
   patterns and a dozen emblems, which is a scroll bar with a picker attached; at these it shows
   three times as many and the choosing starts to look like choosing.

   The tiles carry no words, so nothing gets harder to read on the way down - and both stay above
   the size a finger can reliably hit.

   Named through the box they sit in, because the phone's general rule for that box is - and has to
   remain - a hundred and twenty pixels: it is what the rooms and the ships want, and it is written
   as ".sem-scroll-list .sem-thumbs". Two class names beat one, so ".sem-patterns" alone lost to it
   and the patterns came out at the rooms' size.
*/
@media (max-width: 700px) {
    .sem-scroll-list .sem-thumbs.emblems {
        grid-template-columns: repeat(auto-fill, minmax(40px, 1fr));
    }

    .sem-scroll-list .sem-thumbs.sem-patterns {
        grid-template-columns: repeat(auto-fill, minmax(54px, 1fr));
    }

    /* The gap is sized for the wide pictures the default track holds, not for these. */
    .sem-picker .sem-thumbs.emblems,
    .sem-picker .sem-thumbs.sem-patterns { gap: var(--space-xs); }
}

.sem-patterns > .sem-thumb-option {
    content-visibility: auto;
    contain-intrinsic-size: 70px;
}

/* ------------------------------------------------------- a finger, not a pointer */

/*
    Desktop density is the point of this application, so nothing here changes for a mouse. What
    changes is what a finger can reliably hit.

    This block used to hold four things up and let everything else meet a finger at whatever size the
    desktop happened to use. That was already thin — the smallest control on a phone was 26 pixels —
    and it stopped being tenable when the desktop came in a step: a compaction measured for a
    trackpad would have taken a 26px target to 22. So the list is now every control that is pressed
    rather than read.

    Every value here is absolute. Written as a token these would have shrunk along with the desktop,
    which is exactly the leak the block exists to prevent — and two of them were, until this pass:
    the swatch gap and the trait row's padding both drew from the spacing scale.

    The floor here is 40 rather than the 44 the accessibility guidance asks for, and it is worth
    saying which number is being departed from and why. 44 assumes a control with room around it;
    these are grids of forty ethics and eighty traits where every extra pixel is multiplied by the
    row and pushes the next one off the screen. 40 with a real gap between neighbours misses less
    often than 44 with none. The two things that are pressed rather than chosen from — a text box and
    the burger — take the full 44.

    36 for a swatch, lower again, and the reason is arithmetic rather than preference: there are two
    hundred and sixteen of them. At 44 plus a gap the palette runs past the bottom of a phone and
    choosing a colour becomes scrolling for one. 36 with a 4px gap is the practical floor for a grid
    that dense, and the honest number to claim.
*/
@media (pointer: coarse) {
    /* The two hundred and sixteen swatches, at the floor argued for above. */
    .sem-swatches {
        grid-template-columns: repeat(auto-fill, 36px);
        gap: 4px;
    }

    .sem-swatch {
        width: 36px;
        height: 36px;
    }

    /* A row of text with an icon: the padding makes the target, so the icon comes back up with it. */
    .sem-trait-row { padding: 10px 8px; }

    .sem-trait-row img,
    .sem-trait-row .no-icon {
        width: 22px;
        height: 22px;
    }

    .sem-dlc {
        width: 40px;
        height: 40px;
    }

    /* Anything typed into or chosen from. A textarea grows by its padding and is left to. */
    input[type="text"], select {
        height: 44px;
    }

    textarea { padding: 10px 12px; }

    .sem-combo .toggle { width: 36px; }

    .sem-combo input[type="text"] { padding-right: 40px; }

    .sem-combo .options button { padding: 10px 8px; }

    /* Anything pressed. */
    button, .sem-button { padding: 10px 12px; }

    /*
        The view toggle's two halves. Named rather than left to the rule above, which a bare element
        selector loses to: they are a strip of two words with nothing beside them to widen the
        target, and at their desktop padding the whole control is 23 pixels tall.
    */
    .sem-view-toggle button { padding: 10px 14px; }

    button.icon,
    .sem-randomise,
    .sem-gender,
    .sem-stepper button {
        width: 40px;
        height: 40px;
    }

    .sem-spawn {
        width: 44px;
        height: 40px;
    }

    /*
        Both branches. The desktop rule names button.sem-tab as well, and that selector outranks a
        bare .sem-tab here — so written the short way this reached the Empires link and the Import
        label and left Create, Export, All and None at their desktop height.
    */
    .sem-tab,
    button.sem-tab { height: 40px; }

    .sem-header-burger {
        width: 44px;
        height: 44px;
    }

    .sem-combined .compare {
        min-height: 36px;
        padding: 0 10px;
    }

    /*
        An icon that is the whole control, with nothing beside it to widen the target. A floor rather
        than a size: the homeworld grid asks for larger tiles because a planet has to be recognised,
        and setting the variable itself would have shrunk them.
    */
    .sem-icon-grid { grid-template-columns: repeat(auto-fill, max(40px, var(--sem-icon-size, var(--tile)))); }
    .sem-ethics-grid { grid-auto-columns: max(40px, var(--sem-icon-size, var(--tile))); }
    .sem-nomad { width: max(40px, var(--sem-icon-size, var(--tile))); }

    /* 8 above and below an 18px icon, which is what makes 36 rather than the 32 six gave. */
    .sem-chip > button { padding: 8px 10px 8px 4px; }

    /*
        And the same chip inside a table squeezed narrow, which is the case that only ever happens
        on a phone - the compact rule below is more specific than the line above and was quietly
        holding the target at about twenty-two pixels in exactly the place it mattered.
    */
    .sem-table-wrap.compact .sem-chip > button:has(img) { padding: 8px 10px 8px 4px; }

    /*
        A row's own buttons. The table asks for 1.7rem, which is twenty-two pixels at this root and
        outranks the general rule below by two classes, so edit and delete stayed small on every
        touch device the floor was written for.
    */
    .sem-table .actions button.icon { width: 40px; height: 40px; }

    /*
        The plan's drag handle: wide enough to find, and always visible, since there is no pointer to
        hover it with and a control that appears on hover is one a finger never sees.
    */
    .sem-plan-grip {
        width: var(--icon-btn);
        height: var(--icon-btn);
        opacity: 1;
    }

    /* A picture chosen from a scrolling list: a portrait, a room, a flag pattern. */
    .sem-thumb-option { padding: 8px; }

    /* The rows that are the designer's own navigation, and the panels that fold. */
    .sem-section > .head,
    .sem-class-list button,
    .sem-fold > summary,
    .sem-picker-tabs button,
    .sem-showcase-problems > summary { padding: 12px 10px; }

    .sem-dlc-summary { padding: 12px 10px; }
}

/* ---------------------------------------------------------------- a question

   A question that stops everything until it is answered: deleting an empire, throwing away an
   evening's edits, walking away from unsaved ones.

   Fixed rather than absolute, so it covers the viewport however far down the page the scroll has
   got — an overlay measured from the document would sit at the top of a page the reader is halfway
   down. The sheet is capped at 30rem because these are one sentence and a row of buttons, and a
   question that stretches to a 1800px panel reads as an announcement rather than a question.

   These rules sat between two blocks belonging to the game-layout page and went out with it, which
   left every confirmation rendering inline near the top of the page: no overlay, nothing stopped,
   and the buttons somewhere above whatever the reader was looking at. They are here now, under
   their own heading, because a modal is not a page's furniture — it belongs to the application.
*/
.sem-modal {
    position: fixed;
    inset: 0;
    z-index: 100;
    display: grid;
    place-items: center;
    padding: var(--space-xl);
    background: rgb(0 0 0 / 0.6);

    /*
       The page behind holds still. A modal covers the viewport, so every wheel and every finger
       lands on it or on the sheet inside it - and when whichever of those is scrolling reaches its
       end, the browser hands the rest of the gesture to the page underneath, which then scrolls
       away beneath the modal and is somewhere else when it closes. Containing it stops that
       handing-on. It needs a scroll container to apply to, which is what overflow buys; the modal
       itself never has anything to scroll, since the sheet is capped at its height.
    */
    overflow: auto;
    overscroll-behavior: contain;
}

.sem-modal > .sheet {
    overscroll-behavior: contain;
}

.sem-modal > .sheet {
    background: var(--bg-panel);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    padding: var(--space-2xl);
    width: min(30rem, 100%);
    display: flex;
    flex-direction: column;
    gap: var(--gap);
}

/*
   Connecting to a provider: a little wider than a question, because it holds a list of files whose
   names are long and whose folders are longer.
*/
.sem-modal > .sheet.sem-cloud { width: min(36rem, 100%); }

/*
   The cloud button keeps its one word and says the rest on hover; the path itself is shown in the
   sheet, where it has room to be read. Marked when the sign-in has lapsed, because a save that
   will not work is worth knowing about before pressing Save rather than after.
*/
.sem-tab.sem-cloud-tab.ended {
    color: var(--bad);
    border-bottom-color: var(--bad);
}

/*
   The way down from the top of the drive, every step of it clickable.

   A trail rather than an up arrow, because the folder somebody wants is four deep under a device
   name they did not choose, and climbing back one rung at a time to reach the one above is the
   part that makes a file browser tiring.
*/
.sem-cloud-path {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-2xs);
    font-size: var(--text-sm);
}

.sem-cloud-path > button {
    padding: 2px var(--space-xs);
    background: none;
    border: none;
    color: var(--text-dim);
    font: inherit;
    font-size: var(--text-sm);
    cursor: pointer;
}

.sem-cloud-path > button:last-of-type { color: var(--text); }

.sem-cloud-path > button:hover:not(:disabled) {
    background: var(--bg-hover);
    color: var(--text);
}

.sem-cloud-path > span { color: var(--text-faint); }

/*
   The path of the connected file. Long, and worth reading end to end, so it wraps at the slashes
   rather than being cut short - and is set apart from the sentences either side of it so the eye
   can find the one line that says which file this actually is.
*/
.sem-cloud-where {
    margin: 0;
    padding: var(--space-md) var(--space-lg);
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    color: var(--text);
    font-size: var(--text-sm);
    overflow-wrap: anywhere;
}

.sem-cloud-find {
    display: flex;
    gap: var(--space-sm);
}

.sem-cloud-find > input { flex: 1; min-width: 0; }

/*
   The files found, as rows rather than as a picker: there are rarely more than a handful, and each
   needs two lines - the name, and the folder that tells two machines' copies apart.
*/
.sem-cloud-files {
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
    max-height: 16rem;
    overflow-y: auto;
    margin: 0;
    padding: 0;
    list-style: none;
}

.sem-cloud-files > li > button {
    display: flex;
    flex-direction: column;
    gap: 2px;
    width: 100%;
    text-align: left;
}

.sem-cloud-files .name { color: var(--text); }

/*
   A folder reads as a way in rather than as a thing to choose, which is the one distinction this
   list has to make and the only one it makes: the arrow, and no size beside the name.
*/
.sem-cloud-files > li > button.folder .name::before {
    content: "▸ ";
    color: var(--text-faint);
}

.sem-cloud-files > li > button.folder { color: var(--accent); }

.sem-cloud-files .where {
    color: var(--text-dim);
    font-size: var(--text-xs);

    /* A path is the one thing here that can outgrow the sheet, and it must not widen it. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/*
   A choice inside a question: what the answer will do, rather than what the answer is. It is one
   item in the sheet's own column, so the room above and below it is the dialog's; the box is its
   own, so that it reads as a control rather than as a second sentence.

   Not .sem-option, which is the picker's card and was lending this its padding and its background
   by accident. The two are unrelated and only looked alike.
*/
.sem-modal > .sheet .sem-choice {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding: var(--space-md) var(--space-lg);
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    color: var(--text-dim);
    cursor: pointer;
}

.sem-modal > .sheet .sem-choice:hover {
    border-color: var(--border-strong);
    color: var(--text);
}

/*
   The lite editor, over the list it was opened from.

   The card is the sheet: it carries its own panel background, its border and its padding, so
   another set drawn round it would be a box in a box. What is left for the dialog is where it sits
   and how tall it may be.

   Centred, and darker behind than a question is. A dialog with a page under it wants the page to
   stay legible; this one is the empire, drawn at the size the empire is drawn, and the list it came
   from is only there to be gone back to. At six tenths the cards read through the room artwork.

   Centring is safe because the sheet is capped at the height of the window: a card too tall to fit
   scrolls inside itself rather than growing, so there is no case where the middle of it is off the
   top of the screen.
*/
/*
   How wide the card is allowed to get: its two columns, the gap between them, the padding either
   side and the border. Written once because the sheet in the dialog and the shell on the page are
   the same card and have to stop at the same place - they held a copy each, and two copies of a
   measurement are two things to remember to change. The 760px the layout collapses at comes from
   the same sum, and a media query is the one place this cannot be read.
*/
:root {
    --lite-width: calc(952px + 360px + var(--gap) + (var(--space-lg) * 2) + 2px);
}

.sem-lite-modal {
    place-items: center;
    padding: var(--space-lg);
    background: rgb(0 0 0 / 0.85);
}

/*
   On a host that draws its own window, the editor stops at the header rather than covering it.

   The three window buttons live up there, and they are the only way this window has of being
   minimised or closed - a card laid over them leaves somebody holding an empire with no way out
   except the keyboard. So the modal starts below the header and the row stays both visible and
   usable, which is a thing worth giving up over a browser tab, where the same band is only tabs
   and covering it is ordinary modal behaviour.
*/
.sem-app.framed .sem-lite-modal { top: var(--header-height); }

.sem-lite-modal > .sheet {
    width: 100%;
    max-width: var(--lite-width);
    max-height: 100%;
    overflow-y: auto;

    padding: 0;
    background: none;
    border: none;
    border-radius: 0;
}

/*
   No ring on the sheet. It is given the keyboard when it opens, so Escape has somewhere to land,
   and a browser reading a programmatic focus as a keyboard one drew the accent outline round the
   whole card - which opening a shared link did every time, since there is no pointer press before
   it to say otherwise.
*/
.sem-lite-modal > .sheet:focus,
.sem-lite-modal > .sheet:focus-visible {
    outline: none;
}

/* A phone has no room to give a backdrop, and the card is the thing being looked at. */
@media (max-width: 760px) {
    .sem-lite-modal { padding: var(--space-xs); }
}

.sem-modal h2 {
    margin: 0;
    font-size: var(--text-lg);
}

/*
   A setting with two answers, written out rather than drawn as a switch. The button says which of
   the two is in force and pressing it takes the other, so there is no caption explaining what "on"
   would have meant - which for a rule about sharing a portrait is a sentence whichever way it sits.
*/
.sem-toggle {
    display: inline-flex;
    align-items: center;
    padding: var(--space-2xs) var(--space-sm);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: none;
    color: var(--text-dim);
    font: inherit;
    cursor: pointer;
}

.sem-toggle:hover {
    background: var(--bg-hover);
    color: var(--text);
}

.sem-toggle[aria-pressed="true"] {
    background: var(--bg-raised);
    border-color: var(--accent-dim);
    color: var(--text);
}

/* ------------------------------------------------------------------ the lite editor

   The card, with everything on it pressable.

   Two columns, as the sections' editor has: the empire on the left and what it adds up to on the
   right, following the page. The same breakpoints too - a card that reflowed at different widths
   from the editor it toggles with would be two answers to one question.

   It used to carry max-width: 56rem. With html at 81.25% that is 728px inside a container that
   allows 1800, so it used well under half the page.
*/
.sem-lite {
    display: grid;
    /*
       The panels stop at the width of the scene above them, which is the room artwork's own 952
       pixels. Past that the card would only be spreading two columns of short rows further apart.

       The cap and the centring live on the shell below, so the actions row keeps the card's edges
       rather than running the full width of a page the card no longer fills.
    */
    grid-template-columns: minmax(0, 952px) minmax(300px, 360px);

    /*
       Two rows, so the buttons can have the corner. The first is as tall as they are and the second
       is everything left, which is what keeps the totals measured against the panels rather than
       against the panels less a button.
    */
    grid-template-rows: auto minmax(0, 1fr);
    gap: var(--gap);

    /*
       Stretched on purpose: it is what makes the totals column exactly as tall as the panels beside
       it, which is the whole of "never taller than the preview". The sticking moved inside it.
    */
    align-items: stretch;

    background: var(--bg-panel);
    border: 1px solid var(--border);
    border-radius: var(--radius);

    /* Named because the narrow column's height has to subtract it, and it changes at 900. */
    --lite-pad: var(--space-lg);
    padding: var(--lite-pad);
}

/*
   No wider steps for the totals column. The panels stop at the scene's width, so a column that
   kept growing past it would be the only thing a bigger monitor changed - and the card is meant
   to stop growing, not to grow in one place.
*/

/*
   Where each of the three sits while there are two columns. Stated, because the row of buttons is
   written first and would otherwise take the top-left cell - and because the panels have to span
   both rows for the totals to be measured against all of them.
*/
/*
   Sticky, because the card is taller than the sheet it sits in and these are the things a reader
   reaches for after looking at something further down - Save most of all. The sheet is what
   scrolls, so this sticks to the top of it.

   Its own background, or the panels would scroll through it. Above them too: a sticky item is
   still in flow and would otherwise be painted in source order, which puts it under the scene.
*/
.sem-lite-actions {
    grid-column: 2;
    grid-row: 1;

    position: sticky;
    top: calc(var(--lite-pad) * -1);
    z-index: 3;
    padding: var(--lite-pad) 0;
    margin: calc(var(--lite-pad) * -1) 0 0;
    background: var(--bg-panel);
}

.sem-lite-main {
    grid-column: 1;
    grid-row: 1 / span 2;

    display: flex;
    flex-direction: column;
    gap: var(--gap);
    min-width: 0;
}

/*
   The right column, which cannot grow taller than the panels beside it.

   A stretched cell was not enough. A grid row is as tall as its tallest item, and a sticky child is
   in flow - so a long list of modifiers made the totals the tallest item, the row grew to fit it,
   and the column matched the panels only because it had just dragged them up to meet it. Measuring
   the two as equal proved nothing.

   Taking the inner out of flow is what settles it: the cell now has no content of its own, the row
   is sized by the panels alone, and inset:0 gives the inner exactly that height. It is the one
   arrangement where "never taller than the preview" is a fact about the box rather than a hope
   about its contents.

   The cost is the sticking, which an absolutely positioned box cannot do. Worth it: the column is
   the height of the card, so it is on screen for as long as the card is.
*/
.sem-lite-totals {
    grid-column: 2;
    grid-row: 2;

    position: relative;
    min-width: 0;
    min-height: 0;
}

.sem-lite-totals > .inner {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    gap: var(--gap);
    min-height: 0;
}

/*
   The problem list is as tall as it needs to be; the modifiers take the rest and scroll.

   Both were flex-shrink with no grow, which sizes each to its content and leaves the container's
   max-height to clamp whatever will not fit - so a long list pushed past the column and a short one
   never reached the bottom of it.
*/
.sem-lite-totals > .inner > .sem-showcase-problems {
    flex: 0 0 auto;
}

.sem-lite-totals > .inner > .sem-combined {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

/* The one scrolling thing in the panel, whichever view is drawn inside it. */
.sem-lite .sem-combined > .body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}

/*
   Below this the card is one column and the modifiers follow the panels down the page.

   Not where the panels stop being two columns, which is 1100 - that had the modifiers falling
   below at the very moment the panels made room for them, so a 1060-pixel window showed one narrow
   strip where two columns plainly fit. A column of panels wants about 300 and so do the modifiers,
   which with the gaps and the padding is a little over 650; 760 keeps a hand's width of slack and
   still leaves a tablet held upright with both columns.
*/
@media (max-width: 760px) {
    .sem-lite {
        grid-template-columns: minmax(0, 1fr);
        grid-template-rows: none;
    }

    /*
       Placed nowhere in particular, which in one column means in the order they are written: the
       buttons, the empire, then what it adds up to. Back and Save at the top of a phone rather than
       under everything they act on.
    */
    .sem-lite-actions,
    .sem-lite-main,
    .sem-lite-totals {
        grid-column: auto;
        grid-row: auto;
    }

    /*
       One column, so the totals follow the panels down the page - but no further than a screenful.
       Left to its content it was several times the height of the phone it was on, and a reader
       scrolling for the empire underneath had a list of modifiers to get past first.
    */
    .sem-lite-totals {
        position: static;

        /*
           What the header takes off the top. It is pinned over the page at this width, so that
           much of the screen is never the column's to use - the same budget the summary pane is
           given, the header and the gap under it.
        */
        --above: var(--sticky-top);
    }

    /*
       And a screenful is what is left of the screen, not the whole of it: the header above, and
       the card's padding and the page's below, which are under the column and part of what has to
       be on screen with it. The panel's own heading needs no term - it is inside the box being
       measured, and the list under it is what shrinks to make room.

       svh rather than vh, so a phone showing its address bar still fits: vh is the height the page
       gets with the bar hidden, which is not the height it has while the reader is looking at it.
    */
    .sem-lite-totals > .inner {
        position: static;
        max-height: calc(100vh - var(--above) - var(--lite-pad) - var(--space-xl));
        max-height: calc(100svh - var(--above) - var(--lite-pad) - var(--space-xl));
    }
}

/*
   As wide as the card wants and no wider, in the middle of whatever it is given.

   The cap is written out rather than left to fit-content, which was measuring the wrong thing:
   fit-content shrinks a box to its contents, and the contents of a one-column card are one panel
   wide. So below the breakpoint the whole editor collapsed to a 336-pixel strip however much room
   it had - on a 1060-pixel window, and on a phone, where it sat narrower than the device with the
   room artwork shrunk to match.

   The number is the card at its widest: the scene's own 952, the modifiers' 360, the gap between
   them, the padding either side and the border. Stated, so it stops growing where the card does and
   fills everything up to that.
*/
.sem-lite-shell {
    display: flex;
    flex-direction: column;
    gap: var(--gap);
    width: 100%;
    max-width: var(--lite-width);
    margin-inline: auto;
}

/*
   The buttons, in the corner of the card above everything the empire adds up to.

   Wrapping because five icons and a narrow column is not a promise that can be kept at every width,
   and a row that overflows puts Save off the edge of the card.
*/
.sem-lite-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-sm);
    justify-content: flex-end;
}

/*
   The panels, in two columns that are stated rather than balanced.

   Two real columns rather than CSS multicol: which panel belongs in which is a decision - what the
   empire is on the left, who is in it on the right - and multicol decides by height instead. Each
   column stacks its own, so a short panel does not leave a hole beside a tall one either.

   Narrow, the columns give their panels back with display:contents and the row becomes one flex
   column. Reading order there is not either column's order - a species belongs beside its ruler
   when there is room and after the government when there is not - so each panel carries the order
   it wants, and the flattened list is sorted by it.
*/
.sem-lite-panels {
    display: flex;
    flex-direction: column;
    gap: var(--gap);
}

.sem-lite-panels > .column {
    display: contents;
}

.sem-lite-panels > .column > .sem-lite-panel {
    order: var(--panel-order, 0);
}

@media (min-width: 1100px) {
    .sem-lite-panels {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        align-items: start;
    }

    .sem-lite-panels > .column {
        display: flex;
        flex-direction: column;
        gap: var(--gap);
        min-width: 0;
    }

    /* Stacked by their column now, so the order they would take alone does not apply. */
    .sem-lite-panels > .column > .sem-lite-panel { order: 0; }
}

/*
   One box, not a box inside a box: the panel is the frame and its body only arranges.

   Named for the card rather than for the sheet. There was once a generic .sem-panel carrying a
   padding and a margin of its own; this wore it, overrode its background and padding, and was left
   holding its margin-bottom - so every panel had a bottom margin on top of the grid's own gap.
   Nothing wears that class any more and it has gone.
*/
.sem-lite-panel {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
    min-width: 0;
    padding: var(--space-md);
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

/*
   The name, and on the same line the settings that are one glyph each - a gender, a leader class.
   They were labelled rows, which spent two lines of panel apiece saying what the picture says.

   This was a heading row until the heading came out: a panel's first line already names it, and a
   heading over that repeated it.
*/
.sem-lite-panel > .body > .about > .titles {
    display: flex;
    align-items: flex-start;
    gap: var(--space-sm);
}

.sem-lite-panel > .body > .about > .titles > .name-button {
    flex: 1;
    min-width: 0;
}

.sem-lite-panel button.badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: var(--icon-btn);
    height: var(--icon-btn);
    padding: 0;
    background: none;
    border: 1px solid transparent;
    border-radius: var(--radius);
    color: var(--text-dim);
    font: inherit;
    font-size: var(--text-xs);
    cursor: pointer;
}

.sem-lite-panel button.badge:hover {
    background: var(--bg-hover);
    border-color: var(--accent-dim);
}

.sem-lite-panel button.badge img {
    width: var(--icon);
    height: var(--icon);
    object-fit: contain;
}

/*
   The spawn setting, which is a picture rather than a glyph - three frames of the same phoenix, and
   telling them apart needs the size the game draws them at.

   Out of flow, in the panel's own corner. In the row it was twice the height of the name beside it,
   so it set the row's height and pushed every setting under it down by the difference. Sized to the
   picture rather than to a button, since the sprite carries its own margin.
*/
/*
   The spawn setting, in the panel's own corner.

   Its padding was already nothing and it still sat in a ring of space, because the space is inside
   the picture: the sprite is a 74x58 sheet carrying 50x34 of phoenix with twelve transparent pixels
   on every side. Drawn at 48 square it lost its shape as well - 74x58 is not square, and the bird
   was being squashed by a quarter.

   So it is drawn at its own proportions and the window is smaller than the picture, which crops the
   margin rather than displaying it. At 0.8 the sheet is 59x46 and the phoenix 40x27; a 46x33 window
   leaves three pixels of clear space around it, which is what the gender buttons have.
*/
.sem-lite-panel button.badge.large {
    position: absolute;
    top: var(--space-sm);
    right: var(--space-sm);

    display: flex;
    align-items: center;
    justify-content: center;

    width: 46px;
    height: 33px;
    padding: 0;
    overflow: hidden;
}

.sem-lite-panel button.badge.large img {
    flex: none;
    width: 59px;
    height: 46px;
    max-width: none;
}

/* Room for it, so a long name does not run underneath. */
.sem-lite-panel:has(button.badge.large) > .body > .about > .titles {
    padding-right: calc(46px + var(--space-sm));
}

.sem-lite-panel > .body {
    display: flex;
    gap: var(--space-lg);
    align-items: flex-start;
    flex: 1;
}

.sem-lite-panel > .body > .about {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
    flex: 1;
    min-width: 0;
}

/*
   The fixed box the portraits have always had, and the crop that goes with it.

   height:auto together with min-height:0 left nothing constraining the box, so an image rendered at
   its own ratio inside the 80px width and the object-fit:cover / object-position:top center above
   became no-ops - the box already matched the picture, so there was nothing to crop. The species
   faces went one way and the ruler figure, which states its height, stayed the other.
*/
.sem-lite-panel .face-button > .face {
    height: var(--face-lg-h);
    min-height: 0;

    /*
       Block, or the picture sits on a text baseline and the button keeps five pixels under it for
       the descenders of letters that are not there. Invisible until the button is hovered, and then
       a band of highlight along the bottom of every planet, ship and portrait - but not under the
       ruler, whose figure is a div and never sat on the line in the first place.
    */
    display: block;
}

/*
   One frame for both faces, drawn by the button.

   The species' border used to be on its image while the ruler's was on its button, which put the
   two outer edges two pixels apart and sat the panels beside them out of line. Worse on hover: the
   button's own border lights up, so a species showed an accent ring outside a solid one - the
   double edge that reads as a border inside a border.
*/
.sem-lite-panel .face-button,
.sem-lite-panel .face-button.figure {
    position: relative;
    width: var(--face-lg-w);
    height: var(--face-lg-h);
    align-self: flex-start;
    overflow: hidden;
    background: var(--bg-void);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
}

/*
   Whatever is inside fills that frame and draws no edge of its own: a face, or the flat thumbnail
   the ruler falls back to where no wardrobe was published - which is every desktop cache built
   before this, and is where the border-inside-a-border was actually seen. Cover from the top, which
   is what the species' face has always done.
*/
.sem-lite-panel .face-button > .face,
.sem-lite-panel .face-button.figure .sem-thumb {
    width: 100%;
    height: 100%;
    min-height: 0;
    object-fit: cover;
    object-position: top center;
    border: 0;
    border-radius: 0;
}

/*
   The ruler is drawn from stacked layers at the artwork's own size, which is several times a
   panel's width. Scaled as a whole rather than resized, since every layer's position is recorded
   against that size and the pieces are placed inside it rather than fitted to it.

   Scaled so the height fills rather than the width: the canvas is landscape and mostly empty either
   side of the figure, so fitting the width left a thumbnail of a ruler adrift in a tall box. Centred
   by origin rather than by an offset, because the canvas is a different width for every portrait.

   Measured off the frame rather than written down. It was scale(0.44) against the 0.4273 that
   covering a 220-tall canvas actually needs, so the ruler was drawn three per cent larger than the
   species beside it and lost its last three pixels to the clip - the same trick the room scene
   already uses, and it tracks the frame if the frame ever changes.
*/
.sem-lite-panel .face-button.figure {
    container-type: size;
}

.sem-lite-panel .face-button.figure .sem-figure {
    position: absolute;
    top: 0;
    left: 50%;
    transform: translateX(-50%) scale(calc(100cqh / 220px));
    transform-origin: top center;
}

/*
   Every pressable thing the card draws itself, named one by one.

   Not `.sem-lite button`. The card holds other components' controls - the gender icons, the spawn
   phoenix, the chips, the fold over the problem list - and a rule that broad beat every one of
   them on specificity and repainted them all as blank rectangles.
*/
.sem-lite-scene,
.sem-lite .face-button,
.sem-lite .name-button,
.sem-lite-chips button.value,
.sem-lite-chips .sem-lite-empty {
    font: inherit;
    color: inherit;
    text-align: left;
    background: none;
    border: 1px solid transparent;
    border-radius: var(--radius);
    padding: var(--space-2xs) var(--space-xs);
    cursor: pointer;
}

/* One of them shows a colour as well as naming it, and a dot in a line of text needs a line. */
.sem-lite-chips button.value.map {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2xs);
}

.sem-lite-scene:hover,
.sem-lite .face-button:hover,
.sem-lite .name-button:hover,
.sem-lite-chips button.value:hover,
.sem-lite-chips .sem-lite-empty:hover {
    background: var(--bg-hover);
    border-color: var(--accent-dim);
}

/*
    The same thing, standing still.

    A government's name and the personalities beside it are what the empire's other choices add up
    to rather than choices of their own, so there is nothing behind them to open. They keep the shape
    they had - the card's rows line up on it - and give up the cursor and the hover that said press
    me.
*/
.sem-lite .name-button.still,
.sem-lite-chips .sem-lite-empty.still {
    /* A span, where the others are buttons, so it has to be told to behave like a box. */
    display: block;
    cursor: default;
}

.sem-lite .name-button.still:hover,
.sem-lite-chips .sem-lite-empty.still:hover {
    background: none;
    border-color: transparent;
}

/*
   button.value rather than .value, because a chip's popover holds its modifiers in spans of the
   same name - and a descendant selector gave "-5%" a pointer and a hover highlight inside a panel
   nothing in it can be pressed.

   The label is not one of them. It says what the row is, and the row's value is on screen beside
   it, so pressing the word "Homeworld" was a second way to reach an editor that the homeworld
   itself already opens - and it lit up on hover, which is a promise that there is something here a
   press would tell you. Only the values answer now, and the label is left looking like the label
   it is.
*/
.sem-lite-chips > .heading {
    padding: var(--space-2xs) var(--space-xs);
    border: 1px solid transparent;
}

/*
   The scene, which is the biggest target and the one that looks least like a button.

   Capped at the room artwork's own width, so it is never drawn larger than the picture behind it -
   the same rule the appearance tab's scene keeps.
*/
.sem-lite-scene {
    position: relative;
    padding: 0;
    overflow: hidden;
    max-width: 952px;
}

/* The pencil that admits the scene is pressable, since nothing else about a photograph does. */
.sem-lite-hint {
    position: absolute;
    top: var(--space-sm);
    right: var(--space-sm);
    display: flex;
    padding: var(--space-2xs);
    border-radius: var(--radius);
    background: rgb(0 0 0 / 0.55);
    color: var(--text);
    opacity: 0;
}

.sem-lite-scene:hover .sem-lite-hint,
.sem-lite-scene:focus-visible .sem-lite-hint { opacity: 1; }

/* A finger has no hover, so there it is simply always shown. */
@media (hover: none) {
    .sem-lite-hint { opacity: 1; }
}

/*
   Top, not stretched. A flag or a face shorter than the rows beside it was pulled down the height
   of the panel and floated in the middle of it, level with nothing.
*/
.sem-lite .face-button {
    padding: 0;
    flex-shrink: 0;
    align-self: flex-start;
}

.sem-lite .name-button {
    display: block;
    width: 100%;
    margin-left: calc(var(--space-xs) * -1);
}

.sem-lite .name-button > .name {
    font-size: var(--text-lg);
    font-weight: 600;
    line-height: 1.2;
}

/*
   The kind under the name rather than after it. In the showcase these are divs and stack on their
   own; here they are spans inside one button, so "HUM" and "Humanoid" ran together into a phrase
   that read as neither.
*/
.sem-lite .name-button > .kind { display: block; }

/* A planet or a ship render is not a face: shown whole rather than cropped to fill. */
/*
   A planet or a ship render is not a face: shown whole rather than cropped, and centred rather than
   pinned to the top. The shared rule's object-position is there to keep a portrait's head in frame,
   which left a planet sitting against the ceiling of its box with the space below it.
*/
.sem-lite-panel .face-button > .face.contain {
    object-fit: contain;
    object-position: center;
    background: var(--bg-void);
}

.sem-lite .name-button > .aside {
    margin-left: var(--space-xs);
    color: var(--text-dim);
    font-style: italic;
}

.sem-lite .name-button > .aside.bad {
    color: var(--bad);
    font-style: normal;
}

.sem-lite .noface {
    display: block;
    background: var(--bg-raised);
    border: 1px solid var(--border);
}

/* The name of a row, in the small dim uppercase every label on the card wears. */
.sem-lite-chips > .heading {
    color: var(--text-dim);
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

/*
   The rows of choices, sharing one label gutter and one height.

   subgrid rather than a width on each heading: the rows are separate elements, so only a track they
   all inherit can make them agree, and a fixed gutter would either truncate "Authority" or waste
   the difference on "Traits". Without it every row started its chips at a different place, which is
   a ragged edge straight down the middle of the card.

   The height is stated for the same reason. A row holding a chip is taller than one holding a
   word, so a panel of both climbed and dropped down its own length; at one height they are a
   table, which is what they are.
*/
.sem-lite-choices {
    display: grid;
    grid-template-columns: fit-content(9rem) minmax(0, 1fr);
    gap: var(--space-2xs) var(--space-md);
    align-items: start;
}

.sem-lite-chips {
    display: grid;
    grid-template-columns: subgrid;
    grid-column: 1 / -1;
    align-items: center;
    min-height: var(--control-h);
}

/* A row whose panel already names it keeps no gutter of its own. */
.sem-lite-chips.bare {
    display: block;
    grid-column: 1 / -1;
}

/* start, not stretch: the hover border should hug the word, not the whole gutter. */
.sem-lite-chips > .heading {
    justify-self: start;
    align-self: center;
    white-space: nowrap;
}

/*
   min-height so a row holding a word is as tall as one holding a chip. It matters most where the
   gutter has collapsed and the label sits above its value: there the row's height is the label plus
   this, and without it a column of rows rose and fell by a few pixels a line.
*/
.sem-lite-chips > .chips {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-sm);
    min-width: 0;
    min-height: var(--control-h);
}

.sem-lite-chips .sem-lite-empty {
    color: var(--text-faint);
    font-size: var(--text-sm);
}

/*
   One line, cut where the panel ends. A biography is the case this is for: it runs to a thousand
   characters and the row shows its opening.
*/
.sem-lite-chips .value {
    color: var(--text-dim);
    font-size: var(--text-sm);
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/*
   The spawn button in a row rather than in a section. Its own 38x34 is what the section's column
   wants; here it has to be a row's height like everything beside it, or one line of the panel sits
   five pixels lower than the rest.
*/
.sem-lite-chips .sem-spawn {
    width: var(--icon-btn);
    height: var(--icon-btn);
}

.sem-lite-chips .sem-spawn-row {
    gap: var(--space-sm);
    font-size: var(--text-sm);
}

.sem-lite-chips .sem-spawn-row .name {
    color: var(--text-dim);
    font-weight: 400;
}

/* A value that is a picture: sized to the row rather than to the sprite, which is 24 or 28. */
.sem-lite-chips .value.icon {
    display: inline-flex;
    align-items: center;
    padding: 0 var(--space-2xs);
}

.sem-lite-chips .value.icon img {
    width: var(--icon);
    height: var(--icon);
}

/*
   The totals taken apart again, one option at a time.

   Spaced by the options themselves rather than by a gap on whatever holds them: the panel's body is
   one scrolling box shared by all three views, and only this one has options in it.
*/
.sem-combined .option + .option {
    margin-top: var(--space-md);
}

.sem-combined .option > .name {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    margin-bottom: var(--space-2xs);
    font-size: var(--text-sm);
}

.sem-combined .option > .name img {
    width: var(--icon-sm);
    height: var(--icon-sm);
}

/* Indented under the option that caused them, so the two levels are told apart at a glance. */
.sem-combined .option > .sem-effects {
    margin-left: calc(var(--icon-sm) + var(--space-sm));
}

.sem-combined .option > .sem-effects .sem-modifiers:last-child {
    margin-bottom: 0;
}

/*
   What the empire's choices do in words rather than numbers, under the arithmetic they cannot join.
*/
.sem-combined-worded {
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
    padding-top: var(--space-md);
    border-top: 1px solid var(--border);
}

.sem-combined-worded > .sem-effects-text {
    margin: 0;
    font-size: var(--text-sm);
}

/*
   The modifiers panel, and whether it may be folded away.

   Wide, it may not: the fold is a heading, the chevron is not drawn, and nothing suggests pressing
   it. display:contents so the button leaves no box of its own between the heading and its text.
*/
.sem-combined.collapsible > .heading > .fold {
    display: contents;
    background: none;
    border: none;
    padding: 0;
    color: inherit;
    font: inherit;
    cursor: default;
}

.sem-combined.collapsible > .heading > .fold .chevron { display: none; }

/*
   Narrow, it may - and only there, because that is where the panel sits above what the reader came
   for rather than beside it, and every modifier is a scroll away from choosing an empire.

   Every rule that makes it a fold lives in here, so a panel somebody folded on a phone is open
   again on a monitor. The same arrangement the pickers use, and for the same reason: a fold that
   can only be reopened by making the window narrow again is a fold that has swallowed the panel.
*/
@media (max-width: 1100px) {
    .sem-combined.collapsible > .heading > .fold {
        display: flex;
        align-items: baseline;
        gap: var(--space-sm);
        cursor: pointer;
    }

    .sem-combined.collapsible > .heading > .fold .chevron {
        display: inline;
        color: var(--text-faint);
    }

    .sem-combined.collapsible:not(.open) > .body { display: none; }
}

/* The three views, sized to sit beside a heading rather than above a picker. */
.sem-combined .heading > .sem-view-controls > .sem-view-toggle button {
    padding: var(--space-2xs) var(--space-sm);
    font-size: var(--text-xs);
}

/* The views and the switch that says which empire they are about, at the heading's right-hand end. */
.sem-combined .heading > .sem-view-controls {
    display: flex;
    align-items: center;
    gap: var(--space-md);
}

/*
   Not a fourth view, so not a fourth button: it qualifies the three beside it rather than joining
   them. A box and a word, which is what the plan switch in the editor's own heading looks like.
*/
.sem-combined .sem-plan-count {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2xs);
    font-size: var(--text-xs);
    color: var(--text-dim);
    cursor: pointer;
    white-space: nowrap;
}

.sem-combined .sem-plan-count:has(input:disabled) {
    opacity: 0.5;
    cursor: default;
}

@media (max-width: 900px) {
    .sem-lite { --lite-pad: var(--space-md); }

    /*
       A narrower gutter rather than none. Stacking each label over its value doubled the height of
       every row that holds a word - which is most of them - to buy a width the words did not need.
       The gutter shrinks instead, and a row of chips still wraps inside its own half.
    */
    .sem-lite-choices {
        grid-template-columns: fit-content(7rem) minmax(0, 1fr);
        column-gap: var(--space-sm);
    }
}

/*
   The header stops sticking at 720 and scrolls away with everything above it, so by the time the
   column is on screen the whole screen is the column's. Charging it for a header that is no longer
   there costs a phone sixty pixels of the list.
*/
@media (max-width: 720px) {
    .sem-lite-totals { --above: 0px; }
}

/* ------------------------------------------------------------------ the editor dialog

   The same backdrop and sheet as a confirmation, sized for an editor instead of a question.

   Three differences, and each is the reason this is not just a wider ConfirmDialog. It is capped in
   height rather than growing with its content, because the flag picker is three hundred emblems and
   the trait picker a hundred and seventeen rows. Its middle scrolls while its head and its Done stay
   put, so closing does not mean scrolling back. And its padding moves off the sheet onto the three
   parts, so the scrollbar runs the full height of the box rather than floating inside a margin.
*/
.sem-editor-modal > .sheet {
    width: min(64rem, 100%);
    max-height: min(85vh, 60rem);
    padding: 0;
    gap: 0;
    overflow: hidden;
}

/*
   A sheet sized to a form rather than to a picker.

   The wide one exists for the grids - three hundred emblems, a hundred and seventeen traits, a
   picker with its description panel beside it. A handful of labelled boxes in the same sheet is
   mostly empty sheet.

   And it must not clip. A ComboBox drops its list as an absolutely positioned child, so the sheet's
   overflow and the scrolling body between them would cut it off exactly as a section once did - the
   note further up this file records that fix. A form of three fields never needs to scroll, so it
   is given nothing to scroll in.
*/
.sem-editor-modal.sem-editor-compact > .sheet {
    width: min(34rem, 100%);
    overflow: visible;
}

.sem-editor-modal.sem-editor-compact > .sheet > .body {
    overflow: visible;
}

.sem-editor-modal > .sheet > .head {
    display: flex;
    align-items: center;
    gap: var(--space-lg);
    padding: var(--space-lg) var(--space-xl);
    border-bottom: 1px solid var(--border);
}

.sem-editor-modal > .sheet > .head h2 { flex: 1; min-width: 0; }

/* The one part that scrolls. min-height:0 because a flex child will not shrink below its content
   without it, and without that the sheet grows past its own max-height and the page scrolls
   instead — which is the failure this whole block exists to avoid. */
.sem-editor-modal > .sheet > .body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: var(--space-xl);
}

.sem-editor-modal > .sheet > .choices {
    padding: var(--space-lg) var(--space-xl);
    border-top: 1px solid var(--border);
}

/*
   Nothing here overrides the pickers. They lay themselves out with auto-fit against the space they
   actually have rather than against the window, so the sheet's own width is what they measure and
   the two-column flag picker still gets two columns inside it. Checked rather than assumed.
*/

@media (max-width: 720px) {
    .sem-editor-modal > .sheet {
        max-height: 100%;
        width: 100%;
    }

    .sem-editor-modal > .sheet > .head,
    .sem-editor-modal > .sheet > .body,
    .sem-editor-modal > .sheet > .choices {
        padding-left: var(--space-lg);
        padding-right: var(--space-lg);
    }
}

.sem-modal p {
    margin: 0;
    color: var(--text-dim);
}

/*
   The answers run to the right and in increasing order of consequence, so the destructive one is
   never where the harmless one was a moment ago. They wrap to full width on a narrow screen rather
   than being squeezed to three unreadable words each.
*/
.sem-modal .choices {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: var(--space-md);
}

.sem-modal .choices > button {
    flex: 0 1 auto;
}

@media (max-width: 420px) {
    .sem-modal .choices > button {
        flex: 1 1 100%;
    }
}

/*
    A section whose header carries a control of its own.

    The header is a button, and a control nested inside one never gets its own press - so the switch
    sits beside it and the two are laid out as a single row, with the body spanning both beneath.
*/
.sem-section.led {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: center;
}

.sem-section.led > .lead {
    display: flex;
    align-items: center;
    padding-left: var(--space-lg);
}

.sem-section.led > .head {
    padding-left: var(--space-sm);
    border-radius: 0 calc(var(--radius) - 1px) 0 0;
}

.sem-section.led:not(:has(> .body)) > .head {
    border-radius: 0 calc(var(--radius) - 1px) calc(var(--radius) - 1px) 0;
}

.sem-section.led > .body {
    grid-column: 1 / -1;
}

/*
    The plan's switch, in the heading of whichever thing is showing the plan.

    No label of its own: the word beside it is the heading, which already says Plan. A bare box
    still has to say what it is to anything not reading the heading, so it carries an aria-label
    and a title instead.
*/
.sem-plan-switch {
    display: flex;
    align-items: center;
    margin-bottom: 0;
}

.sem-editor-modal .head > .sem-plan-switch {
    margin-right: calc(var(--space-xs) * -1);
}

/*
    A number carried on a chip, drawn where the chip's icon goes.

    Kept apart from the glyph buttons above by being a span: those rules name button.badge, since
    what they style is a control, and this one is a label. They collided while both were written as
    .badge - the button's own width won, and every share came out the width of a button.

    For the AI personalities, which are not one answer but a draw: the empire allows several and the
    game picks one, so each says how likely it is to be the one. They have no artwork in the game, so
    the number takes the icon's place and its size - it is what tells one of these chips from the
    next, and behind the name it was a footnote on a row of names that all looked alike.

    Coloured by that likelihood, red through yellow to green, which the component hands over as a
    hue: nought degrees to a hundred and twenty is exactly that ramp, so nothing here interpolates.
    Drawn as a ring with the number inside rather than as a filled disc, because a filled one has to
    choose a text colour that works over both a dark red and a bright yellow, and there is none.

    A pill rather than a strict circle so that "100" fits. It is a circle at one and two characters,
    which is every share but the one an empire that allows a single personality gets.
*/
.sem-chip > button > .badge,
.sem-chip-stack > .lead > .badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    box-sizing: border-box;
    min-width: var(--icon-sm);
    height: var(--icon-sm);
    padding: 0 3px;
    border: 1px solid hsl(var(--badge-hue, 0) 60% 45%);
    border-radius: var(--radius-pill);
    background: color-mix(in srgb, hsl(var(--badge-hue, 0) 60% 45%) 20%, transparent);
    color: hsl(var(--badge-hue, 0) 55% 68%);
    font-size: 0.72em;
    font-variant-numeric: tabular-nums;
    line-height: 1;
    white-space: nowrap;
}

/*
    On a light ground the same hue has to come down to stay readable: a 68% lightness that reads as
    a colour against a dark panel is nearly white against a pale one.
*/
@media (prefers-color-scheme: light) {
    :root:not([data-theme="dark"]) .sem-chip > button > .badge,
    :root:not([data-theme="dark"]) .sem-chip-stack > .lead > .badge {
        color: hsl(var(--badge-hue, 0) 55% 32%);
    }
}

:root[data-theme="light"] .sem-chip > button > .badge,
:root[data-theme="light"] .sem-chip-stack > .lead > .badge {
    color: hsl(var(--badge-hue, 0) 55% 32%);
}
