/* TaskStrider
 *
 * One stylesheet, no framework, no build step. Every colour and size is a
 * custom property on :root, and the two other themes below override only
 * those properties - so a theme is six lines rather than a second
 * stylesheet that drifts out of step with this one.
 *
 *   default      the owner: navy and a medium blue on a soft blue-grey
 *   body.floor   the same blue, everything bigger, for gloved hands
 *   body.admin   green, because that account can create shops and reset
 *                passwords and should never be mistaken for a shop's own
 *
 * The look, since the refresh of October 2026: white cards with soft
 * shadows rather than hairline boxes, pill buttons, taller fields with a
 * blue glow where the cursor is, a navy bar along the top of every screen
 * inside the app, and the sign-in screens on a low-poly backdrop.
 *
 * Nothing here is load bearing. Every screen is semantic HTML that works
 * with this file missing: tables keep border="1", every control is a real
 * button or link, and no behaviour depends on a rule below.
 */

/* The wordmark's own font: Inter Black Italic, SIL Open Font License
 * (static/fonts/Inter-OFL.txt), cut down to the ten letters of
 * TaskStrider - 2 kB. The one font file the app loads, and the only url()
 * in this file; the security policy allows fonts from this app alone.
 * Before it the wordmark was each device's own heaviest italic, and
 * looked different on every one. A different name from Inter's own, so
 * nothing mistakes a ten-letter cut for the whole family. */
@font-face {
    font-family: "TaskStrider Wordmark";
    font-style: italic;
    font-weight: 900;
    font-display: swap;
    src: url("/static/fonts/inter-black-italic-taskstrider.woff2") format("woff2");
}

:root {
    --bg: #eef2f7;
    --panel: #ffffff;
    --ink: #0e1a2e;
    --muted: #4a5870;
    --line: #d8e0ea;

    --brand: #1f4583;
    --brand-dark: #0a2350;
    --brand-tint: #e7eef8;
    --on-brand: #ffffff;

    /* The bar along the top, and the employee's door on the front page.
     * Its own tokens rather than --brand-dark, because dark mode turns
     * brand-dark LIGHT (it means emphasis there) and the bar must stay a
     * dark surface in both. */
    --navy: #0a2350;
    --on-navy: #ffffff;
    --on-navy-muted: #c6d3e8;
    --navy-line: #ffffff66;
    --navy-hover: #ffffff1f;
    /* A phone's pill row: a band of its own under the bar's first row,
     * lighter than the bar so it reads as a separate strip - and the
     * shadow at an end of it that has more pills past it. */
    --navy-band: #1a3d78;
    --navy-shade: #00000080;

    /* A card's edge. Barely there in daylight, where the shadow does the
     * work; dark mode needs a visible one, because a shadow cannot show
     * on a dark ground. */
    --edge: #e5ebf2;

    /* The ring around a field that has the cursor. */
    --focus: #1f458359;

    /* The one colour that is not blue, and it means exactly one thing:
     * this job has passed the hours it was quoted at. That comparison is
     * the product, so it gets the only accent in the palette. */
    --accent: #9a4a06;
    --accent-bg: #fdf1e3;
    --accent-line: #e7c69c;

    /* Late. A SECOND mark, and deliberately not the accent: amber means
     * a job cost more than it was quoted and it means only that, so
     * spending it on a second thing would leave the shop reading two
     * different problems in one colour. Red for a date that has passed,
     * which is the one convention nobody has to be taught. */
    --late: #a4231c;
    --late-bg: #fdecea;
    --late-line: #f0b9b3;

    /* The lift under a card: a wide soft shadow, and a tight one that
     * sits it on the page. EIGHT-DIGIT HEX, not rgba(), so each is a
     * colour token like every other one and the dark theme can replace
     * it: a blue-tinted shadow is invisible on a dark surface, which is
     * not wrong so much as pointless. This was written out three times
     * as rgba(18, 63, 125, 0.12) and could not follow a theme at all. */
    --shadow: #0a23501c;
    --shadow-near: #0a235012;

    /* The sign-in screens' backdrop, its facets darkest to lightest: 1 to
     * 5 are the frame, 6 to 8 the light middle the words sit on. */
    --poly-1: #061a3e;
    --poly-2: #0a2350;
    --poly-3: #14336b;
    --poly-4: #1f4583;
    --poly-5: #4b6b9f;
    --poly-6: #c3cfdf;
    --poly-7: #e2e8f1;
    --poly-8: #f4f7fb;

    /* WHAT THE BROWSER PAINTS ITSELF. Not a colour - a declaration that
     * this page works in both schemes, which is what makes the user
     * agent draw select popups, date and time pickers, checkboxes,
     * scrollbars and autofill backgrounds to match. None of those read
     * the tokens above: they are drawn outside the page's styling, and
     * without this line a dark page keeps a white dropdown and a white
     * date picker over the top of it. */
    color-scheme: light dark;

    --font: 16px;
    --pad: 12px;
    --gap: 18px;
    --radius: 12px;
    --card-radius: 16px;
    --tap: 44px;
    /* A text field is taller than a button: it is where a thumb lands to
     * type, and the extra height is what makes it read as a field. */
    --field: 50px;
}

/* The floor. A tablet on a bench, tapped with a glove on, read from
 * standing. Bigger everything; the palette does not change, because a
 * worker and an owner are looking at the same shop. */
body.floor {
    --font: 20px;
    --pad: 16px;
    --gap: 22px;
    --tap: 64px;
    --field: 68px;
}

/* The admin area. Green so that "this account can create shops" is
 * legible before reading a single word - the bar included - in the same
 * shapes as everywhere else. */
body.admin {
    --bg: #f1faf5;
    --panel: #ffffff;
    --ink: #0d1f18;
    --muted: #3f5b4e;
    --line: #cfe6da;
    --brand: #0f7a52;
    --brand-dark: #0a5a3c;
    --brand-tint: #e2f3ea;
    --navy: #0a3d2b;
    --navy-band: #12583f;
    --on-navy-muted: #c2e3d3;
    --focus: #0f7a5259;
    --edge: #d9ece2;
}

/* ------------------------------------------------------------------
 * Dark mode: static/dark.css
 *
 * The dark palette is a sheet of its own since 2026-10-06, so a device
 * can choose Auto, Light or Dark (a cookie; see base.html). Auto links it
 * for a dark device only, which is what this file's media query used to
 * do - the operating system still decides unless the device has said
 * otherwise. Every colour is still a token, and still written once.
 * ------------------------------------------------------------------ */

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
    margin: 0;
    background: var(--bg);
    color: var(--ink);
    font: var(--font)/1.5 system-ui, -apple-system, "Segoe UI", Roboto, Arial,
        sans-serif;
}

main.page {
    max-width: 62rem;
    margin: 0 auto;
    padding: var(--gap) var(--pad) 4rem;
}

/* A plain heading. It had a brand-blue rule under it until the bar along
 * the top took over saying where you are. */
h1 {
    margin: 0 0 var(--gap);
    color: var(--brand-dark);
    font-size: 1.6em;
    letter-spacing: -0.01em;
    line-height: 1.2;
}

h2 {
    margin: calc(var(--gap) * 1.5) 0 var(--gap);
    color: var(--brand-dark);
    font-size: 0.95em;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

p { margin: 0 0 var(--gap); }

a { color: var(--brand); }
a:hover { color: var(--brand-dark); }

hr {
    height: 1px;
    margin: calc(var(--gap) * 1.5) 0;
    border: 0;
    background: var(--line);
}

code {
    display: inline-block;
    max-width: 100%;
    padding: 0.5em 0.7em;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--panel);
    font-size: 0.95em;
    overflow-wrap: anywhere;
}

/* ------------------------------------------------------------------
 * Messages. A flash is a paragraph whose whole first child is strong,
 * which is what every error, notice and confirmation in this app already
 * was before it had a stylesheet.
 * ------------------------------------------------------------------ */

.flash,
.banner {
    padding: var(--pad);
    border: 1px solid var(--edge);
    border-left: 4px solid var(--brand);
    border-radius: var(--card-radius);
    background: var(--panel);
    box-shadow: 0 1px 2px var(--shadow-near), 0 8px 24px var(--shadow);
}

.banner {
    max-width: 62rem;
    margin: var(--gap) auto 0;
    border-left-color: var(--accent);
    background: var(--accent-bg);
    border-color: var(--accent-line);
}

/* ------------------------------------------------------------------
 * Forms
 * ------------------------------------------------------------------ */

form { margin: 0 0 var(--gap); }

input,
select {
    width: 100%;
    max-width: 26rem;
    min-height: var(--field);
    padding: 0.6em 0.9em;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--panel);
    color: inherit;
    font: inherit;
    transition: border-color 0.15s, box-shadow 0.15s;
}

/* A field with the cursor in it glows rather than taking an outline -
 * the ring is the focus indicator, 4px of the brand blue. */
input:focus,
select:focus {
    outline: none;
    border-color: var(--brand);
    box-shadow: 0 0 0 4px var(--focus);
}

button:focus-visible,
a:focus-visible {
    outline: 3px solid var(--brand);
    outline-offset: 3px;
}

/* A pill. It never holds two lines: no label in the app needs two at
 * 375px, and the ellipsis is only for a name typed absurdly long into
 * "Remove ..." or "Delete ...", where the page above names it in full. */
button {
    max-width: 100%;
    min-height: var(--tap);
    padding: 0.55em 1.5em;
    overflow: hidden;
    border: 1px solid var(--brand);
    border-radius: 999px;
    background: var(--brand);
    color: var(--on-brand);
    font: inherit;
    font-weight: 600;
    text-overflow: ellipsis;
    white-space: nowrap;
    box-shadow: 0 1px 2px var(--shadow-near);
    cursor: pointer;
}

button:hover {
    border-color: var(--brand-dark);
    background: var(--brand-dark);
}

/* A button that cannot be pressed here, with its reason beside it - DONE
 * on a job somebody else is working (wall item 6, 2026-10-26). Faded, not
 * hidden, so the row still says what the button is for; never coloured
 * differently, so a colour alone never carries the meaning. */
button:disabled,
button:disabled:hover {
    border-color: var(--brand);
    background: var(--brand);
    opacity: 0.4;
    box-shadow: none;
    cursor: not-allowed;
}

/* Its reason on a line of its own under it, not wrapped around it. */
td.action .hint {
    display: block;
    margin-top: 0.4em;
}

/* An icon at the front of a button's label - the DONE prompt's Pause and
 * Finished, which must not look alike but must weigh the same. */
.btn-icon {
    width: 1.15em;
    height: 1.15em;
    margin-right: 0.5em;
    vertical-align: -0.2em;
}

/* A secondary action: still a real button, plainly not what this screen
 * is for.
 *
 * Sign out - "Not me" until slice 3 - sits directly above a column of
 * START buttons on the floor.
 * In the same filled blue it reads as the first job's button, and a
 * worker in a hurry who taps it signs himself out mid-shift. Outlined and
 * grey, it stops competing with the thing the screen exists for.
 *
 * A class, rather than form[action="/logout"] button, which would have
 * styled it without touching the template at all: styling keyed on a
 * route reverts silently the day that route moves, and what reverts here
 * is exactly the mis-tap this prevents. */
.quiet {
    border-color: var(--line);
    background: var(--panel);
    color: var(--muted);
    font-weight: 500;
    box-shadow: none;
}

.quiet:hover {
    border-color: var(--muted);
    background: var(--bg);
    color: var(--ink);
}

/* ------------------------------------------------------------------
 * Adding a job, collapsed
 *
 * The form is most of a phone screen on its own. Above the tables it
 * pushed the live status down as far as the finished list used to; below
 * them the manager scrolled past twenty finished jobs to reach it. As a
 * <details> it is one line at the top, and one tap.
 *
 * The summary carries what the heading carried, so the screen still
 * reads the same with no stylesheet - an ordinary summary a browser
 * opens and closes on its own, with no script anywhere.
 * ------------------------------------------------------------------ */

.adder {
    margin: 0 0 var(--gap);
    border: 1px solid var(--edge);
    border-radius: var(--card-radius);
    background: var(--panel);
    box-shadow: 0 1px 2px var(--shadow-near), 0 8px 24px var(--shadow);
}

.adder > summary {
    display: list-item;
    min-height: var(--tap);
    padding: var(--pad);
    border-radius: var(--card-radius);
    color: var(--brand-dark);
    font-size: 1.15em;
    font-weight: 600;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    cursor: pointer;
}

.adder[open] > summary {
    border-bottom: 1px solid var(--line);
    border-radius: var(--card-radius) var(--card-radius) 0 0;
}

.adder > form,
.adder > p {
    margin: var(--gap) var(--pad);
}

/* The manager's job filters, in the same card.
 *
 * Everything is shared on purpose - border, radius, tap height, colour,
 * weight - because it is the same control doing the same thing on
 * another screen, and a second pattern would be one more thing to keep
 * in step.
 *
 * One exception. That summary is a whole sentence when a filter is on
 * ("Showing paused jobs for properties matching ..."), and the uppercase
 * that suits the two words ADD A JOB shouts a sentence. Written with
 * both classes so it wins on specificity rather than on being further
 * down the file. */
.adder.filters > summary {
    text-transform: none;
}

/* ------------------------------------------------------------------
 * Tables
 *
 * The wrapper scrolls, not the page: a wide table on a phone should move
 * inside its own box rather than push the whole screen sideways.
 * ------------------------------------------------------------------ */

/* position: relative because the wrapper is what a table's hidden labels
 * (the week's "+ Mon", for a screen reader) are placed against. With
 * nothing positioned they were placed against the page, and when the week
 * overflowed its card they pushed the whole page sideways. */
.scroll {
    position: relative;
    margin: 0 0 var(--gap);
    overflow-x: auto;
    border: 1px solid var(--edge);
    border-radius: var(--card-radius);
    background: var(--panel);
    box-shadow: 0 1px 2px var(--shadow-near), 0 8px 24px var(--shadow);
}

/* No hard border. border="1" stays on every table, for the page with no
 * stylesheet; the TABLE's own edge from that attribute was never reset
 * here, and it was the dark line round every list. */
table {
    width: 100%;
    border: 0;
    border-collapse: collapse;
}

th,
td {
    padding: var(--pad);
    border: 0;
    border-bottom: 1px solid var(--line);
    text-align: left;
    vertical-align: middle;
}

th {
    padding-top: calc(var(--pad) * 0.8);
    padding-bottom: calc(var(--pad) * 0.8);
    background: var(--brand-tint);
    color: var(--brand-dark);
    font-size: 0.85em;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    white-space: nowrap;
}

tr:last-child td { border-bottom: 0; }

/* A form inside a cell is an action, not a section: no trailing gap. */
td form { margin: 0; }
td input { min-width: 6rem; }

/* More than one action in a cell - a manager's Send a new link beside
 * Deactivate. Side by side where they fit, wrapped where they do not, and
 * never touching: with no gap the two stacked flush, the second button's
 * top edge on the first one's bottom, and the button a slip lands on is
 * the one that signs somebody out. */
/* One gap both ways. The row gap used to be three-quarters of the
 * column gap, and when an employee card's Set up and Turn off wrapped
 * onto two lines at 375px they ended up 8px apart - the closest pair on
 * a card whose every other gap is 14px, and they are opposite actions. */
.row-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--gap);
}

/* The group spaces its own forms; a form's usual trailing gap would knock
 * one out of line with its neighbour. On an employee's card, which is not
 * a table cell, the group takes the margin the card gives its forms and
 * paragraphs - all four sides. With only the bottom one it sat flush
 * against the card's edge, 10px left of every other button, measured at
 * 375px. */
.row-actions form { margin: 0; }
.person > .row-actions { margin: var(--gap) var(--pad); }

/* ------------------------------------------------------------------
 * An employee's card
 *
 * One <details> each. Collapsed it is a name and a status; open it is
 * every form for that person. As a table it put one employee on a whole
 * phone screen before the next name appeared.
 *
 * The browser's own element: no script, which the security policy blocks
 * in any case, and with this file missing it is still a list of names
 * that open and close.
 *
 * The summary keeps its own marker and carries the tap height, so the
 * whole line opens the card, not just the triangle.
 * ------------------------------------------------------------------ */

.person {
    margin: 0 0 var(--pad);
    border: 1px solid var(--edge);
    border-radius: var(--card-radius);
    background: var(--panel);
    box-shadow: 0 1px 2px var(--shadow-near), 0 8px 24px var(--shadow);
}

.person > summary {
    display: list-item;
    min-height: var(--tap);
    padding: var(--pad);
    border-radius: var(--card-radius);
    font-weight: 600;
    cursor: pointer;
}

.person[open] > summary {
    border-bottom: 1px solid var(--line);
    border-radius: var(--card-radius) var(--card-radius) 0 0;
}

.person-status {
    color: var(--muted);
    font-weight: 400;
}

/* The forms inside sit in from the card's edge, and the last one does not
 * leave a gap below itself. */
.person > form,
.person > p {
    margin: var(--gap) var(--pad);
}

.person > form:last-child,
.person > p:last-child {
    margin-bottom: var(--pad);
}

/* ------------------------------------------------------------------
 * The one accent
 * ------------------------------------------------------------------ */

/* A badge never holds two lines; one that wraps is a rounded blob with a
 * broken sentence in it. So it holds only the news - "over by 1h 05m" -
 * and the figure sits beside it in bold, outside it: short enough to stay
 * on one line in a narrow column, and the cell breaks BETWEEN the two
 * when it must. The figure is still <strong>, so it is never colour
 * alone. */
.over {
    display: inline-block;
    padding: 0.1em 0.6em;
    border: 1px solid var(--accent-line);
    border-radius: 999px;
    background: var(--accent-bg);
    color: var(--accent);
    white-space: nowrap;
}

/* Past its due date. The SAME SHAPE as .over on purpose - same padding,
 * same border, same radius - so the two read as one kind of thing said
 * about two different columns, and only the colour tells them apart. A
 * row can carry both: over-quote sits in Actual, late sits in Due. */
.late {
    display: inline-block;
    padding: 0.1em 0.6em;
    border: 1px solid var(--late-line);
    border-radius: 999px;
    background: var(--late-bg);
    color: var(--late);
    white-space: nowrap;
}

/* A person's name never breaks across lines - on a board card, a week's
 * row, anywhere it is marked so. "Erin / Shaw" read as two people. */
.name {
    white-space: nowrap;
}

/* ------------------------------------------------------------------
 * The DONE prompt
 *
 * Two choices of identical weight. Neither is the default, because a
 * screen where one option is a button and the other is a link trains a
 * reflexive tap - and here that closes unfinished jobs and corrupts the
 * hours. Same class, same rule, same size, and when the screen is too
 * narrow they stack while staying equal rather than one shrinking.
 * ------------------------------------------------------------------ */

.choices {
    display: flex;
    flex-wrap: wrap;
    gap: var(--gap);
    margin: 0 0 var(--gap);
}

.choices form {
    flex: 1 1 14rem;
    margin: 0;
}

.choices button { width: 100%; }

/* ------------------------------------------------------------------
 * The floor's name list: every name a full-width target.
 * ------------------------------------------------------------------ */

/* START and DONE fill their cell, so the target is the row, not the word. */
body.floor td form button { width: 100%; }

/* ------------------------------------------------------------------
 * The floor's two views: My jobs and All jobs
 *
 * The landing page's two doors, reused - the same shape, border, weight
 * and shadow, with the fills inverted between them. On the landing page
 * the fill only tells the two doors apart. Here it means something: the
 * view you are on is solid blue and the other is white, and they swap as
 * you switch, so the colour says where you are. Side by side at every
 * width, because the labels are two short words.
 * ------------------------------------------------------------------ */

.views {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--pad);
    margin: 0 0 var(--gap);
}

.view-link {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: var(--tap);
    padding: 0.5em 0.75em;
    border: 2px solid var(--brand);
    border-radius: 999px;
    background: var(--panel);
    color: var(--brand);
    font-weight: 700;
    text-align: center;
    text-decoration: none;
    box-shadow: 0 6px 16px var(--shadow);
}

.view-link:hover {
    background: var(--brand-tint);
    color: var(--brand-dark);
}

.view-link.current {
    background: var(--brand);
    color: var(--on-brand);
}

.view-link.current:hover {
    border-color: var(--brand-dark);
    background: var(--brand-dark);
    color: var(--on-brand);
}

/* ------------------------------------------------------------------
 * A checkbox and its label
 *
 * The input rule above makes every input a full-width, tap-height text
 * box, which turns a checkbox into a stretched square. A checkbox gets its
 * own size back, and the LABEL carries the tap height instead, so the
 * words are part of the target.
 * ------------------------------------------------------------------ */

/* min-width too: "td input" gives every input in a table cell 6rem, which
 * left the box centred in a wide gap, well away from its own words.
 * Radio buttons too (slice 4's pickers and the swaps setting): without
 * this each was a 64px circle on a line of its own, above its name. */
input[type="checkbox"],
input[type="radio"] {
    width: 1.4em;
    height: 1.4em;
    min-width: 0;
    min-height: 0;
    max-width: none;
    margin: 0 0.5em 0 0;
    padding: 0;
    accent-color: var(--brand);
}

.check { margin: 0 0 var(--pad); }

.check label {
    display: inline-flex;
    align-items: center;
    min-height: var(--tap);
    cursor: pointer;
}

/* ------------------------------------------------------------------
 * The board on a phone
 *
 * A six column table in a scrolling box puts the hours - the number this
 * product exists to show - off the right hand edge, where an owner has to
 * find it by dragging. Below this width those rows become cards instead,
 * one field per line, each keeping the heading it would otherwise lose
 * when the header row goes.
 *
 * A table opts in with "stack". The job lists go further and opt into
 * "compact" as well - see "Compact cards" below - because one labelled
 * block per field made a single job fill a phone screen. The Employees
 * screen and the admin shop list keep the plain stacked card.
 * ------------------------------------------------------------------ */

@media (max-width: 44rem) {
    .scroll.stack {
        overflow-x: visible;
        border: 0;
        background: none;
        box-shadow: none;
    }

    .scroll.stack table,
    .scroll.stack tbody,
    .scroll.stack tr,
    .scroll.stack td {
        display: block;
        width: 100%;
    }

    /* The header row, whose labels move onto the cells below. */
    .scroll.stack tr:first-child { display: none; }

    .scroll.stack tr {
        margin: 0 0 var(--gap);
        border: 1px solid var(--edge);
        border-radius: var(--card-radius);
        background: var(--panel);
        box-shadow: 0 1px 2px var(--shadow-near), 0 8px 24px var(--shadow);
    }

    .scroll.stack td { border: 0; border-bottom: 1px solid var(--line); }
    .scroll.stack tr td:last-child { border-bottom: 0; }

    /* An action cell carries no label - its button already says what
     * it does - and must not get an empty line where one would be. */
    .scroll.stack td:not([data-label])::before { display: none; }

    .scroll.stack td::before {
        content: attr(data-label);
        display: block;
        color: var(--muted);
        font-size: 0.75em;
        letter-spacing: 0.04em;
        text-transform: uppercase;
    }

    /* A page heading costs less here than it does at a desk. */
    h1 {
        margin-bottom: var(--pad);
        font-size: 1.35em;
    }

    /* The floor's heading repeats the view button just pressed, so it
     * leaves the screen at this width - and stays in the page for a
     * screen reader, and for no stylesheet at all. */
    body.floor .view-title {
        position: absolute;
        width: 1px;
        height: 1px;
        margin: -1px;
        padding: 0;
        border: 0;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }

    /* --------------------------------------------------------------
     * Compact cards: the job lists
     *
     * The stacked card above gives every field its own labelled block.
     * Measured at phone size, that made the floor's job list 348px a
     * card - 2.2 jobs a screen, and one whole job on the first - and
     * the manager's list 488px, 1.6 a screen. The job lists opt into
     * something denser with "compact", and each cell says what part it
     * plays:
     *
     *   td.lead      the first line, bold: the customer
     *   td.meta      the smaller text after it, running together with a
     *                dot between, wrapping only where it must
     *   td.labelled  a meta cell whose value needs its label beside it -
     *                "Due Sep 16", "Actual 2h 10m" - because a bare date
     *                or number could be anything
     *   td.newline   a meta cell that starts a fresh line
     *   td.whole     a meta cell of more than one word that must not be
     *                split across a line break - "No password yet"
     *   td.empty     a cell with nothing to say - no due date, no quote -
     *                left out rather than shown as "Due -"
     *   td.action    the button or link, on a line of its own
     *
     * The cells become inline text, so the dots and labels are
     * generated in front of them and a long job name wraps like a
     * sentence. Every rule is inside this phone-width query: on a tablet
     * or a desk these are the same tables they always were, and with no
     * stylesheet the classes do nothing at all.
     * -------------------------------------------------------------- */

    .scroll.stack.compact tr {
        padding: var(--pad);
        line-height: 1.4;
    }

    .scroll.stack.compact td {
        display: inline;
        padding: 0;
        border: 0;
    }

    .scroll.stack.compact td.lead {
        display: block;
        margin-bottom: 0.1em;
        font-weight: 700;
    }

    .scroll.stack.compact td.meta {
        color: var(--muted);
        font-size: 0.85em;
    }

    /* A generated prefix is plain text in the cell's own size and colour,
     * not the small capitals of a stacked label. */
    .scroll.stack.compact td::before {
        display: inline;
        color: inherit;
        font-size: inherit;
        letter-spacing: normal;
        text-transform: none;
    }

    /* Only a labelled detail prints a column name. Everything else must
     * say "content: none" explicitly, because the stacked card above sets
     * every cell's ::before to its data-label - which showed up as
     * "JobShaft keyway cut" and "Statusopen" the moment the rules that
     * used to override it were replaced. */
    .scroll.stack.compact td.lead::before,
    .scroll.stack.compact td.meta::before,
    .scroll.stack.compact td.action::before {
        content: none;
    }

    /* The dot between details belongs to the detail BEFORE it, not the
     * one after. Written as a prefix it travelled with the detail it
     * introduced, so when that detail wrapped, the dot began the new line
     * - "· Due Sep 18" hanging there on its own. As a suffix glued on
     * with a non-breaking space it stays at the end of the line it was
     * already on, and the detail moves without it.
     *
     * Which details carry one is decided in the template, by the only
     * thing that knows what comes next: a detail followed by another on
     * the same line gets "sep". Nothing here has to guess whether the
     * next cell is hidden or starts a line of its own.
     *
     * "\00A0" is the non-breaking space and "\00B7" the middle dot. The
     * space between details is the line break between the template's <td>
     * tags, which inline cells render as one space. */
    .scroll.stack.compact td.sep::after {
        content: "\00A0\00B7";
    }

    .scroll.stack.compact td.labelled::before {
        content: attr(data-label) " ";
    }

    /* "\A" is a line break, and white-space: pre is what makes it one. */
    .scroll.stack.compact td.newline::before {
        content: "\A" attr(data-label) " ";
        white-space: pre;
    }

    /* A label stays with its value. As plain inline text a line could
     * break inside "Assigned to Sam" or between "Due" and "Sep 16", and
     * the first look at a phone did exactly that. inline-block moves the
     * pair to the next line whole instead, dot and all.
     * A newline cell stays inline: its "\A" only breaks in inline text.
     * An empty cell stays hidden - hence :not(.empty).
     *
     * width: auto matters. The stacked card gives every cell width: 100%,
     * which plain inline text ignores and an inline-block obeys - the
     * first version of this rule put every labelled detail on a line of
     * its own, the opposite of the point. */
    .scroll.stack.compact td.labelled:not(.newline):not(.empty) {
        display: inline-block;
        width: auto;
    }

    /* The same treatment for a detail that has no label but is still more
     * than one word - "No password yet" broke after "No" on the Managers
     * screen at 375px. Multi-word statuses are normal in this app; the
     * Employees screen alone has "On a job now" and "Waiting 5 min". This
     * is td.labelled's rule without the generated label: the phrase moves
     * to the next line whole rather than splitting down the middle. */
    .scroll.stack.compact td.whole:not(.newline):not(.empty) {
        display: inline-block;
        width: auto;
    }

    .scroll.stack.compact td.empty {
        display: none;
    }

    .scroll.stack.compact td.action {
        display: block;
        margin-top: calc(var(--pad) * 0.75);
    }
}

@media (max-width: 34rem) {
    :root { --pad: 10px; --gap: 14px; }
    main.page { padding-left: 10px; padding-right: 10px; }
    input, select { max-width: none; }
}

/* ------------------------------------------------------------------
 * The bar along the top of every screen inside the app
 *
 * The wordmark, the screens as pills - the one you are on filled - who
 * is signed in, and the way out. It replaced a line under the heading
 * ("Signed in as Kyle - All jobs - Schedule ... Sign out") and, on most
 * manager screens, nothing but a "Back to the board" link.
 *
 * One row at a desk. Below 44rem, two rows: the wordmark, "Hi, Kyle"
 * and the way out, then the pills on a band of their own, which slide
 * sideways rather than wrapping - so the bar is two rows however many
 * screens there are. The markup order is mark, who, nav, out
 * (templates/_bar.html); the order properties below do the arranging.
 * ------------------------------------------------------------------ */

.topbar {
    background: var(--navy);
    color: var(--on-navy);
}

/* Wider than the page under it. A manager's bar - the wordmark, seven
 * pills, "Signed in as" and a name, and Sign out - needs about 1,150px,
 * and held to the page's 62rem it broke into two rows on every desk. */
.topbar-in {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem 1rem;
    max-width: 80rem;
    margin: 0 auto;
    padding: 0.5rem var(--pad);
}

.topbar .mark {
    order: 1;
    color: var(--on-navy);
    font-family: "TaskStrider Wordmark", system-ui, -apple-system, "Segoe UI",
        Roboto, "Helvetica Neue", Arial, sans-serif;
    font-size: 1.35em;
    font-style: italic;
    font-weight: 900;
    letter-spacing: -0.04em;
    line-height: 1;
    text-decoration: none;
    transform: skewX(-8deg);
}

.topnav {
    display: flex;
    order: 2;
    gap: 0.3rem;
    overflow-x: auto;
    scrollbar-width: none;
}

.topnav a {
    display: inline-flex;
    flex: none;
    align-items: center;
    min-height: var(--tap);
    padding: 0 1em;
    border-radius: 999px;
    color: var(--on-navy);
    font-weight: 600;
    text-decoration: none;
    white-space: nowrap;
}

.topnav a:hover {
    background: var(--navy-hover);
    color: var(--on-navy);
}

.topnav a[aria-current="page"] {
    background: var(--on-navy);
    color: var(--navy);
}

.topbar .who {
    order: 3;
    margin-left: auto;
    color: var(--on-navy-muted);
    white-space: nowrap;
}

.topbar .who strong {
    color: var(--on-navy);
}

.topbar .out {
    order: 4;
    margin: 0;
}

/* The theme button (the quick pass): the last thing in the manager's pill
 * row, outlined like Sign out - a control, not a screen - so it slides
 * with the pills on a phone and never adds a row to the bar. */
.topnav .theme {
    flex: none;
    margin: 0;
}

.topnav .theme button { white-space: nowrap; }

/* The way out, on the bar: outlined, never filled, so it never reads as
 * the screen's own action - on the floor it sits above START. */
.topbar .quiet {
    min-height: var(--tap);
    padding: 0.3em 1.1em;
    border-color: var(--navy-line);
    background: transparent;
    color: var(--on-navy);
}

.topbar .quiet:hover {
    background: var(--navy-hover);
}

.topbar a:focus-visible,
.topbar button:focus-visible {
    outline-color: var(--on-navy);
}

@media (max-width: 44rem) {
    /* Row one: the wordmark, "Hi, Kyle" and the way out. A long name
     * gives way first and ends in an ellipsis, so the way out never
     * leaves the row. */
    .topbar .who {
        order: 2;
        min-width: 0;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    .topbar .out {
        order: 3;
    }

    /* Row two: the pills, apart from the name - a row of their own, the
     * bar's full width, on a band of its own colour. Until the quick pass
     * before slice 3 they shared a row with the name, the same white on
     * the same navy, and read as more of it.
     *
     * The row slides sideways, and must plainly say so. A soft fade at
     * its end said it too quietly. Now:
     *
     *   - A SHADOW AT WHICHEVER END HAS MORE PILLS PAST IT, and none at an
     *     end that has none. Each shadow is fixed to the band (scroll);
     *     over it lies a cover in the band's own colour that travels with
     *     the pills (local), sitting on the shadow only when that end of
     *     the row is in view. So the shadow on the right shows until the
     *     last pill arrives, the one on the left once the first has
     *     gone, and a row short enough not to scroll - the admin's one
     *     pill - shows neither. The one way CSS alone, with no script,
     *     knows where a row is scrolled to.
     *   - EACH PILL OUTLINED, so the one the screen's edge cuts through is
     *     plainly a cut pill rather than a word that ends early.
     *
     * The shadows sit under the pills, not over them, so nothing lies on
     * top of a pill to catch a tap. Not the floor's bar: its two pills
     * never scroll. */
    body:not(.floor) .topnav {
        order: 4;
        flex: 0 0 calc(100% + 2 * var(--pad));
        margin: 0 calc(-1 * var(--pad)) -0.5rem;
        padding: 0.45rem var(--pad);
        background:
            linear-gradient(to right, var(--navy-band) 35%, transparent)
                left / 1.5rem 100% no-repeat local,
            linear-gradient(to left, var(--navy-band) 35%, transparent)
                right / 1.5rem 100% no-repeat local,
            radial-gradient(farthest-side at 0 50%, var(--navy-shade), transparent)
                left / 1rem 100% no-repeat scroll,
            radial-gradient(farthest-side at 100% 50%, var(--navy-shade), transparent)
                right / 1rem 100% no-repeat scroll,
            var(--navy-band);
    }

    body:not(.floor) .topnav a {
        border: 1px solid var(--navy-line);
    }
}

/* THE FLOOR'S BAR. The shop tablet's Jobs screen is tapped all day, and
 * the signed-in line it replaced was one row; the bar must not be more.
 *
 *   - Pills only on the shop tablet, where My schedule already sat on
 *     the signed-in line (decision 23). A personal device's bar is the
 *     name and Sign out; its My schedule link stays at the foot of Jobs.
 *     Which bar a screen gets is the template's choice, not this file's.
 *   - No wordmark below 50rem. At the floor's size the wordmark, the
 *     name, two pills and Sign out need about 700px; without the wordmark
 *     they fit one row at 600px, the tablet the floor is measured on.
 *   - No band: the generic phone layout above puts the pills on a row
 *     of their own on purpose, and the floor wants one row wherever it
 *     fits. On a phone set up as the shop tablet it wraps by itself. */
@media (max-width: 50rem) {
    body.floor .topbar .mark { display: none; }
}

@media (max-width: 44rem) {
    /* Since the second quick pass the tablet has three pills, 393px with
     * "Notices 12": a phone set up as the shop tablet is narrower than
     * that, so the pills may shrink to the bar and wrap among themselves
     * rather than push the page sideways. Wider, they never need to. */
    body.floor .topnav { order: 2; flex: 0 1 auto; min-width: 0; flex-wrap: wrap; }
    body.floor .topbar .who { order: 3; margin-left: 0; }
    body.floor .topbar .out { order: 4; margin-left: auto; }

    /* With pills, the name goes right, beside Sign out: straight after My
     * schedule, in the same white, it read as a third pill. */
    body.floor .topbar-in:has(.topnav) .who { margin-left: auto; }
    body.floor .topbar-in:has(.topnav) .out { margin-left: 0; }
}

/* ------------------------------------------------------------------
 * The sign-in screens: the front door at /, the manager's sign-in, the
 * name list and the PIN - body.signin
 *
 * Phone first: one column, centred on the screen. The front door's two
 * ways in sit one under the other on a phone and side by side from
 * 40rem, the manager's light and the employee's navy, so neither reads as
 * the default - and the crew's is the solid one, like the buttons they
 * tap all day.
 *
 * The backdrop is low-poly, navy round the edge and pale in the middle:
 * two inline SVGs - a tall one for a phone, a wide one for a desk - in
 * templates/_poly.html, inside a wrapper carrying the hidden attribute.
 * The display: block below is the only thing that shows it, so with this
 * file missing there is no decoration at all. Each facet's shade is a
 * class, .s1 to .s8, and every shade a token, so the backdrop follows the
 * theme like everything else. Drawn in the page rather than loaded as an
 * image: img-src 'self' allows no data: URL, and a file would bring its
 * own colours with it.
 * ------------------------------------------------------------------ */

body.signin {
    min-height: 100vh;
    overflow-x: hidden;
}

body.signin main.page {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    max-width: 44rem;
    padding: 2.5rem 1rem;
    text-align: center;
}

/* Everything on the backdrop sits above it. */
body.signin main.page > :not(.poly) {
    position: relative;
    z-index: 1;
}

/* Which of the two draws is decided by the backdrop's OWN shape, not the
 * window's: it is a size container, and its box is set by inset: 0, never
 * by what is inside it. A browser without container queries shows the
 * tall one everywhere, which is still a backdrop. */
.poly {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 0;
    overflow: hidden;
    pointer-events: none;
    container-type: size;
}

.poly svg {
    display: block;
    width: 100%;
    height: 100%;
}

.poly .wide { display: none; }

@container (min-aspect-ratio: 1/1) {
    .poly .tall { display: none; }
    .poly .wide { display: block; }
}

/* Each facet is stroked in its own colour too, which closes the hairline
 * seams anti-aliasing leaves between two shades. */
.poly path {
    stroke-width: 1.5;
    stroke-linejoin: round;
}

.poly .s1 { fill: var(--poly-1); stroke: var(--poly-1); }
.poly .s2 { fill: var(--poly-2); stroke: var(--poly-2); }
.poly .s3 { fill: var(--poly-3); stroke: var(--poly-3); }
.poly .s4 { fill: var(--poly-4); stroke: var(--poly-4); }
.poly .s5 { fill: var(--poly-5); stroke: var(--poly-5); }
.poly .s6 { fill: var(--poly-6); stroke: var(--poly-6); }
.poly .s7 { fill: var(--poly-7); stroke: var(--poly-7); }
.poly .s8 { fill: var(--poly-8); stroke: var(--poly-8); }

/* The wordmark: its own font, black and italic, then slanted a little
 * more. Real text, so it is still the heading with no stylesheet and
 * still read out by a screen reader. */
.wordmark {
    margin: 0;
    color: var(--brand-dark);
    font-family: "TaskStrider Wordmark", system-ui, -apple-system, "Segoe UI",
        Roboto, "Helvetica Neue", Arial, sans-serif;
    font-size: clamp(2.75rem, 12vw, 5rem);
    font-style: italic;
    font-weight: 900;
    letter-spacing: -0.04em;
    line-height: 1.05;
    transform: skewX(-8deg);
}

/* Smaller above a sign-in card than on the front door. */
.wordmark.small {
    margin-bottom: 1.25rem;
    font-size: clamp(2rem, 9vw, 2.75rem);
}

.tagline {
    margin: 0.5rem 0 2rem;
    color: var(--ink);
    font-size: clamp(1.2rem, 4.5vw, 1.75rem);
}

/* The two ways in. Each card is one link: the pill is the link, stretched
 * over the whole card, so the note inside the employee's card is not part
 * of the link's name but a tap on it still goes in.
 *
 * On a phone a card is one row - the icon beside the pill - because two
 * mockup-tall cards stacked fill the screen and push the wordmark into
 * the backdrop's frame. From 40rem they stand up. */
.entry {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1.25rem;
    width: 100%;
    max-width: 22rem;
    margin: 0 auto;
}

.entry-card {
    position: relative;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.75rem 1rem;
    padding: 1rem 1.25rem;
    border: 1px solid var(--edge);
    border-radius: 20px;
    box-shadow: 0 2px 4px var(--shadow-near), 0 14px 32px var(--shadow);
}

.entry-card.manager {
    background: var(--panel);
    color: var(--brand-dark);
}

.entry-card.employee {
    border-color: var(--navy);
    background: var(--navy);
    color: var(--on-navy);
}

.entry-card:hover { box-shadow: 0 2px 4px var(--shadow-near), 0 18px 40px var(--shadow); }
.entry-card.manager:hover { background: var(--brand-tint); }
.entry-card.employee:hover { box-shadow: inset 0 0 0 999px var(--navy-hover); }

/* The icon's wrapper carries the hidden attribute in the page; this is
 * what shows it. */
.entry-icon {
    display: block;
    flex: none;
    width: 2.75rem;
    height: 2.75rem;
}

.entry-icon svg {
    display: block;
    width: 100%;
    height: 100%;
}

.entry-pill {
    display: inline-flex;
    flex: 1;
    align-items: center;
    justify-content: center;
    min-height: 3rem;
    padding: 0 1.5rem;
    border: 2px solid currentColor;
    border-radius: 999px;
    color: inherit;
    font-size: 1.1em;
    font-weight: 700;
    text-decoration: none;
    white-space: nowrap;
}

.entry-pill:hover { color: inherit; }

.entry-pill::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: 20px;
}

.entry-card.employee .entry-pill:focus-visible { outline-color: var(--on-navy); }

/* Only on a device that is not set up: a crew tablet that is enrolled
 * must never tell the crew to go and ask their manager. */
.entry-note {
    flex-basis: 100%;
    margin: 0;
    color: var(--on-navy-muted);
    font-size: 0.9em;
    line-height: 1.4;
    text-align: center;
}

/* The one line out to /about, under the doors and quieter than both: a
 * pill of its own so it reads on any facet behind it, the whole sentence
 * the target so a thumb can hit it. */
.newcomer { margin: 1.5rem 0 0; }

.newcomer a {
    display: inline-flex;
    align-items: center;
    min-height: 2.75rem;
    padding: 0 1.1rem;
    border-radius: 999px;
    background: var(--panel);
    color: var(--muted);
    font-size: 0.95em;
    box-shadow: 0 1px 2px var(--shadow-near);
}

@media (min-width: 40rem) {
    .entry {
        grid-template-columns: 1fr 1fr;
        max-width: 40rem;
    }

    /* From the top, not centred: the employee's card carries the note,
     * and centring put its pill higher than the manager's. */
    .entry-card {
        flex-direction: column;
        flex-wrap: nowrap;
        justify-content: flex-start;
        gap: 0.9rem;
        min-height: 13rem;
        padding: 2rem 1.25rem 1.5rem;
    }

    .entry-icon { width: 3.25rem; height: 3.25rem; }
    .entry-pill { flex: none; width: 100%; max-width: 15rem; }
    .entry-note { flex-basis: auto; }
}

/* A sign-in form - the manager's, the name list, the PIN - in one card on
 * the backdrop. */
.signin-card {
    width: 100%;
    max-width: 26rem;
    padding: 1.5rem;
    border: 1px solid var(--edge);
    border-radius: 20px;
    background: var(--panel);
    box-shadow: 0 2px 4px var(--shadow-near), 0 14px 32px var(--shadow);
    text-align: left;
}

.signin-card h1 {
    margin: 0 0 1rem;
    font-size: 1.4em;
}

/* A message inside the card is part of it: no second lift. */
.signin-card .flash { box-shadow: none; }
.signin-card form { margin: 0; }
.signin-card input { max-width: none; }
.signin-card button { width: 100%; }
.signin-card > :last-child,
.signin-card form:last-child > p:last-child { margin-bottom: 0; }

.signin-card .alt {
    margin: 1rem 0 0;
    text-align: center;
}

.signin-card .alt a {
    display: inline-flex;
    align-items: center;
    min-height: var(--tap);
}


/* One of a leaver's jobs on the Remove page: what it is, what state it
 * is in, and where to put it. Boxed so four of them read as four things
 * to decide rather than one wall of selects. */
.held {
    margin: 0 0 var(--gap);
    padding: var(--pad);
    border: 1px solid var(--edge);
    border-left: 3px solid var(--brand);
    border-radius: var(--card-radius);
    background: var(--panel);
    box-shadow: 0 1px 2px var(--shadow-near), 0 8px 24px var(--shadow);
}

.held p { margin: 0 0 0.5rem; }
.held form { margin: 0; }
.held small { color: var(--muted); }


/* ------------------------------------------------------------------
 * What TaskStrider does, at /about
 *
 * The page a newcomer is sent to from the front door, on the front
 * door's own low-poly backdrop (.poly, in the sign-in section above), so
 * it looks like where they came from. The rest is the same vocabulary
 * throughout: the panel white, the --line hairline, a card's corner
 * radius and shadow. The one colour NOT used is --accent, which means
 * "this job has passed its quoted hours" everywhere else and would stop
 * meaning it if it were spent on decoration.
 *
 * Phone first: one card under another, three across from 40rem. Every
 * link is at least 44px tall, because the whole page is read on a phone
 * held in one hand.
 * ------------------------------------------------------------------ */

body.about {
    min-height: 100vh;
    overflow-x: hidden;
}

body.about main.page {
    max-width: 64rem;
    padding: 1.5rem 1.25rem 3rem;
}

body.about .back,
body.about h1,
body.about .group,
body.about .price,
body.about .contact {
    position: relative;
    z-index: 1;
}

/* EVERYTHING HERE SITS ON A PANEL OF ITS OWN, because the backdrop is
 * fixed to the screen and the page scrolls over it. The front door is one
 * centred screen in the backdrop's pale middle; this page is long, and
 * wherever a line of it is scrolled to, the navy edge can be behind it.
 * Measured: at 1440 the heading sat on navy facets and could barely be
 * read, and on a phone scrolled to the end the last "Back to sign in"
 * was dark blue on navy. The cards and the contact block were panels
 * already; the heading gets a plate, and the two links the front door's
 * own "New here?" pill. */
body.about h1 {
    display: inline-block;
    margin: 1.5rem 0 2rem;
    padding: 0.4em 0.8em;
    border-radius: calc(var(--radius) * 1.5);
    background: var(--panel);
    box-shadow: 0 6px 16px var(--shadow);
}

/* One under another on a phone, three across when there is room. */
body.about .cards {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1rem;
    margin: 0 0 2rem;
}

/* Drawn, not stacked: the panel, a hairline, the doors' radius and
 * shadow, and a brand edge down the left, so a card reads as a card at a
 * glance rather than as three paragraphs in a row. */
body.about .card {
    padding: 1.25rem;
    border: 1px solid var(--line);
    border-left: 3px solid var(--brand);
    border-radius: calc(var(--radius) * 1.5);
    background: var(--panel);
    box-shadow: 0 6px 16px var(--shadow);
}

/* Each group's small heading (2026-10-06: scheduling, then the job
 * board) on a plate of its own, like the page's heading - the backdrop
 * is behind it wherever the page is scrolled. */
body.about .group-title {
    display: inline-block;
    margin: 0 0 0.75rem;
    padding: 0.3em 0.9em;
    border-radius: 999px;
    background: var(--panel);
    font-size: 0.95em;
    box-shadow: 0 1px 2px var(--shadow-near);
}

body.about .card h3 {
    margin: 0 0 0.6rem;
    font-size: 1em;
    letter-spacing: 0.01em;
    text-transform: none;
}

body.about .card p {
    margin: 0;
    line-height: 1.5;
}

/* The price (billing slice 9) is the contact block's panel, one above
 * the other: what it costs, then who to call. */
body.about .price,
body.about .contact {
    max-width: 26rem;
    padding: 1.25rem 1.25rem 0.75rem;
    border: 1px solid var(--line);
    border-radius: calc(var(--radius) * 1.5);
    background: var(--panel);
}

body.about .price { margin: 0 0 1rem; }

body.about .price h2,
body.about .contact h2 {
    margin: 0 0 0.5rem;
    font-size: 0.95em;
}

body.about .price p,
body.about .contact p { margin: 0 0 0.25rem; }

body.about .price p { line-height: 1.5; padding-bottom: 0.5rem; }

body.about .contact-name { font-weight: 700; }

/* Tap-to-call and tap-to-email, one per line and thumb-sized. */
body.about .contact a {
    display: inline-flex;
    align-items: center;
    min-height: 2.75rem;
    font-size: 1.1em;
    font-weight: 700;
}

/* A way back at both ends: somebody on a phone has scrolled past the top
 * one by the time they reach the bottom. */
body.about .back { margin: 0; }

body.about .back a {
    display: inline-flex;
    align-items: center;
    min-height: 2.75rem;
    padding: 0 1.1rem;
    border-radius: 999px;
    background: var(--panel);
    font-size: 0.95em;
    box-shadow: 0 1px 2px var(--shadow-near);
}

body.about .contact + .back { margin-top: 1.5rem; }

/* THREE ACROSS, or one above another - never two. Each group has three
 * cards, and three in two columns leaves one on a row of its own looking
 * like an afterthought. Three across only where each card is still wide
 * enough for its fifty-odd words not to become a tall narrow strip: the
 * page widens to 64rem for it. */
@media (min-width: 60rem) {
    body.about .cards { grid-template-columns: repeat(3, 1fr); }
}

/* ------------------------------------------------------------------
 * The Terms and the Privacy Policy (slice 1 of the billing plan,
 * 2026-10-17). The About page's layout - body.about - with the document
 * on one panel at a reading width: long lines of legal text are hard to
 * follow, and the backdrop is behind wherever the page is scrolled.
 * ------------------------------------------------------------------ */

body.legal main.page { max-width: 48rem; }

body.legal .legal-doc {
    position: relative;
    z-index: 1;
    margin: 0 0 1.5rem;
    padding: 1.25rem 1.5rem 0.5rem;
    border: 1px solid var(--line);
    border-radius: calc(var(--radius) * 1.5);
    background: var(--panel);
    box-shadow: 0 6px 16px var(--shadow);
    line-height: 1.55;
}

body.legal .legal-doc h2 {
    margin: 1.75rem 0 0.5rem;
    font-size: 1.05em;
    letter-spacing: 0;
    text-transform: none;
}

body.legal .legal-doc p,
body.legal .legal-doc ul { margin: 0 0 0.85rem; }

body.legal .legal-doc li { margin: 0 0 0.35rem; }

/* The one table (who the Privacy Policy shares with): two plain columns
 * that fit a phone without scrolling sideways. */
.legal-table {
    width: 100%;
    margin: 0 0 0.85rem;
    border-collapse: collapse;
}

.legal-table th,
.legal-table td {
    padding: 0.45rem 0.6rem;
    border: 1px solid var(--line);
    text-align: left;
    vertical-align: top;
}

/* The app's tables drop their last row's rule (tr:last-child td); this
 * one is boxed on every side, so its last row keeps it. */
.legal-table tr:last-child td { border-bottom: 1px solid var(--line); }

/* A placeholder still to be filled in once the LLC is formed - marked,
 * so nobody reads one as finished text. */
mark.placeholder {
    padding: 0 0.2em;
    border-radius: 4px;
    background: var(--accent-bg);
    color: var(--accent);
    font-weight: 700;
}

/* The two links, on every way in: pills of the front door's "New here?"
 * kind, so each reads on whatever facet of the backdrop is behind it. */
.legal-links {
    position: relative;
    z-index: 1;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.5rem;
    margin: 1.25rem 0 0;
}

.legal-links a {
    display: inline-flex;
    align-items: center;
    min-height: 2.75rem;
    padding: 0 1rem;
    border-radius: 999px;
    background: var(--panel);
    color: var(--muted);
    font-size: 0.9em;
    box-shadow: 0 1px 2px var(--shadow-near);
}

/* On /about and the documents the two sit above the last way back, so a
 * long page still ends on one. */
/* The owner's billing page (slice 4, 2026-10-17): the price, plainly, on
 * a panel with the brand's edge, its one line - "Free for 30 days, then
 * $49.99/month plus sales tax. Cancel anytime." - the largest thing in it. */
.price-box {
    max-width: 32rem;
    margin: 0 0 1.25rem;
    padding: 1rem 1.25rem;
    border: 1px solid var(--line);
    border-left: 3px solid var(--brand);
    border-radius: var(--radius);
    background: var(--panel);
}

.price-box p { margin: 0 0 0.4rem; }
.price-box p:last-child { margin-bottom: 0; }

.price-line {
    font-size: 1.15em;
    font-weight: 700;
}

/* The Terms box: the whole line is the label, so the target is the line. */
.agree label {
    display: flex;
    gap: 0.6rem;
    align-items: flex-start;
    max-width: 32rem;
}

.agree input { flex: none; margin-top: 0.2rem; }

body.about .legal-links { justify-content: flex-start; margin: 1.5rem 0 0; }
body.legal .legal-links { margin-top: 0; }
body.about .legal-links + .back { margin-top: 1rem; }

/* ------------------------------------------------------------------
 * The schedule (CLAUDE.md item 7, db/023)
 *
 * Every colour below is a token, so the dark theme and the admin theme
 * need nothing of their own here. Nothing is load bearing: with this
 * file missing the week is a plain bordered table of links, the lists
 * are lists, and every control is still a real link or button.
 * ------------------------------------------------------------------ */

/* Earlier and Later either side of the week, each a full tap target. */
.week-nav {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--pad);
    margin: 0 0 var(--pad);
}

.week-nav a {
    display: inline-flex;
    align-items: center;
    min-height: var(--tap);
}

.week-range,
.week-note,
.hint {
    color: var(--muted);
    font-size: 0.9em;
}

.week-range { margin: 0 0 var(--gap); }
.week-note { margin: calc(var(--gap) * -0.5) 0 var(--pad); }

/* Where the week stands with the people on it. A panel rather than a
 * flash: it is always there, and it is not news. */
.week-state {
    padding: var(--pad);
    border: 1px solid var(--edge);
    border-radius: var(--card-radius);
    background: var(--panel);
    box-shadow: 0 1px 2px var(--shadow-near), 0 8px 24px var(--shadow);
}

/* "draft": in words, small, and the same grey everywhere it appears. */
.draft {
    color: var(--muted);
    font-size: 0.75em;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

/* The week. A shift is a soft chip that is a link; one not yet as its
 * person sees it has a dashed edge and a small DRAFT tag inside it. An
 * empty day is a quiet "+" that shows its box only when pointed at - a
 * week of seven people used to be forty dashed boxes.
 *
 * NOTHING MAKES A ROW TALLER BUT A SECOND SHIFT. On a day with a shift
 * the "+" sits beside the last chip, not under it, and DRAFT is inside
 * the chip rather than a line of its own below it - each of those used
 * to add a line to every row it appeared in. Names never wrap.
 *
 * ALL SEVEN DAYS THE SAME WIDTH, each wide enough for a draft chip of
 * "9am - 5:30pm" and its "+": 12rem. A wider one - "10:30am - 11:30pm"
 * as a draft - still widens its own day rather than spill out of it. */
.sched td {
    vertical-align: top;
}

.sched td.day {
    font-size: 0.85em;
    padding-left: calc(var(--pad) * 0.5);
    padding-right: calc(var(--pad) * 0.5);
}

.sched td.day,
.sched td.who {
    white-space: nowrap;
}

/* At a desk: THE WEEK FITS WHOLE AT 1280PX, every day the same width.
 *
 * It used to give every day 12rem and let the week slide - 1,666px of
 * week in a 1,239px card at 1280, and the days still came out unequal,
 * because a draft chip widened its own. Now the layout is fixed: the
 * names column is set, and the seven days share what is left exactly.
 * Below 75rem of week the card slides again, names pinned, rather than
 * squeeze a day narrower than its widest chip.
 *
 * What made a day narrow enough, measured against the widest label the
 * format makes, "10:30am - 11:30pm" as a draft: DRAFT stays in words but
 * sits on the chip's top edge as a tab - in the cell's own top padding,
 * so it takes no width and adds no height - and the chip, its padding
 * and the "+" beside it are each a little tighter. A long name ends in an
 * ellipsis, its whole self in a tooltip; rows still grow for nothing but
 * a split shift.
 *
 * Desk only. On a phone each person is a card and none of this applies. */
@media (min-width: 44.01rem) {
    .sched table {
        table-layout: fixed;
        width: 100%;
        min-width: 75rem;
    }

    .sched tr > th:first-child { width: 9rem; }

    /* With room to spare, room for a long name: at 1920 a 9rem column
     * cut "Bartholomew Fitzgerald-Hughes" short beside 205px days. */
    @media (min-width: 110rem) {
        .sched tr > th:first-child { width: 14rem; }
    }

    .sched td.who {
        overflow: hidden;
        text-overflow: ellipsis;
    }

    .sched td.day {
        padding-left: calc(var(--pad) * 0.35);
        padding-right: calc(var(--pad) * 0.35);
    }
}

/* The week's page alone is wider than every other screen's 62rem, and
 * the week slides sideways inside its card when even that is not enough,
 * the names staying put so a row never loses whose it is. Above phone
 * width only - on a phone each person is a card and nothing slides. */
main.page:has(.sched) {
    max-width: 100rem;
}

@media (min-width: 44.01rem) {
    .sched tr > :first-child {
        position: sticky;
        left: 0;
        z-index: 1;
        background: var(--panel);
        box-shadow: 1px 0 0 var(--line);
    }

    .sched tr > th:first-child {
        background: var(--brand-tint);
    }
}

.sched .shift {
    display: inline-flex;
    align-items: center;
    gap: 0.4em;
    margin: 0;
    padding: 0.35em 0.5em;
    border: 1px solid transparent;
    border-radius: 10px;
    background: var(--brand-tint);
    color: var(--brand-dark);
    font-weight: 600;
    text-decoration: none;
    vertical-align: middle;
}

/* A split shift: every chip but the last on a line of its own, so the
 * "+" still lands beside the last one. */
.sched .shift:has(+ .shift) {
    display: flex;
    width: max-content;
    margin-bottom: 0.3em;
}

.sched .shift.is-draft { border-style: dashed; border-color: var(--brand); }

.sched .shift .draft {
    padding: 0.1em 0.4em;
    border-radius: 999px;
    background: var(--panel);
    font-size: 0.65em;
    font-weight: 700;
}

/* The "+" that is tap-to-assign: the whole width of an empty day. */
.sched .add {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 2.2em;
    border: 1px solid transparent;
    border-radius: 10px;
    color: var(--muted);
    font-size: 1.15em;
    font-weight: 500;
    text-decoration: none;
}

/* Beside a chip it is a small square the chip's height. */
.sched td.has-shift .add {
    display: inline-flex;
    width: 1.7em;
    min-height: 1.9em;
    margin-left: 0.15em;
    vertical-align: middle;
}

.sched .add:hover,
.sched .add:focus-visible {
    border-color: var(--line);
    background: var(--brand-tint);
    color: var(--brand-dark);
}

/* At a desk, the chip, DRAFT and the "+" arranged to fit an equal day -
 * see "THE WEEK FITS WHOLE AT 1280PX" above. Here rather than there
 * because each overrides a rule just above it. */
@media (min-width: 44.01rem) {
    .sched .shift {
        position: relative;
        padding: 0.35em;
        font-size: 0.9em;
    }

    /* A tab on the chip's top edge: half above the chip, inside the
     * cell's top padding. The chip's own dashed edge, so it reads as part
     * of the chip it marks. */
    .sched .shift .draft {
        position: absolute;
        top: 0;
        right: 0.6em;
        transform: translateY(-55%);
        padding: 0 0.45em;
        border: 1px dashed var(--brand);
        font-size: 0.72em;
        line-height: 1.3;
    }

    .sched td.has-shift .add { width: 1.3em; }
}

/* The day's name after the "+", for a screen reader and for the phone
 * layout below - the column heading already says it at a desk. */
.sched .add-day {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* A list of one person's shifts: My schedule, My shifts, one person's
 * schedule for the manager. A finished shift is dimmed, not dropped;
 * today's carries an edge in the brand colour and the word Today. */
.shift-list {
    margin: 0 0 var(--gap);
    padding: 0;
    list-style: none;
}

.shift-list li {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: 0.25em 1em;
    margin: 0 0 0.5em;
    padding: var(--pad);
    border: 1px solid var(--edge);
    border-radius: var(--card-radius);
    background: var(--panel);
    box-shadow: 0 1px 2px var(--shadow-near), 0 8px 24px var(--shadow);
}

/* On the manager's view of one person the times open the shift - a tap
 * target like every other, not a 20px line of text. */
.shift-list a {
    display: inline-flex;
    align-items: center;
    min-height: var(--tap);
}

.shift-list li { align-items: center; }

.shift-list li.today { border-left: 4px solid var(--brand); }
.shift-list li.past { color: var(--muted); }
.shift-list .shift-time { font-weight: 700; }

/* The repeat: a box of seven day names, each a tap-height target. */
.repeat {
    margin: 0 0 var(--gap);
    padding: var(--pad);
    border: 1px solid var(--line);
    border-radius: var(--radius);
}

.repeat legend { padding: 0 0.3em; font-weight: 600; }

/* A row of buttons in a form - the edit form's two saves - is spaced
 * like the paragraph it stands in for. Without it the note under the
 * second save sat against the button, measured at 375px. */
form > .row-actions { margin: 0 0 var(--gap); }

.day-box {
    display: inline-flex;
    align-items: center;
    min-height: var(--tap);
    margin-right: 0.9em;
    cursor: pointer;
}

/* What publishing changes, person by person, inside each week's card. */
.adder > ul { margin: 0 var(--pad) var(--gap); }
.adder > p.change-who { margin-bottom: 0.3em; }
.change-kind { font-weight: 700; }

/* The floor's two cards after the PIN, on a personal device: one under
 * the other on a phone, side by side from a tablet up, each a whole-card
 * target, in the card shape every other screen uses. */
.doors {
    display: grid;
    gap: var(--gap);
    margin: 0 0 var(--gap);
}

.door {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 0.3em;
    min-height: 7em;
    padding: var(--gap);
    border: 1px solid var(--edge);
    border-radius: 20px;
    background: var(--panel);
    color: var(--muted);
    text-decoration: none;
    box-shadow: 0 1px 2px var(--shadow-near), 0 8px 24px var(--shadow);
}

.door strong { color: var(--brand-dark); font-size: 1.3em; }

/* The way across between My schedule and the job list, at the foot of
 * each - a whole tap target, set apart from the list above it. */
.other-half {
    margin: var(--gap) 0 0;
    padding-top: var(--pad);
    border-top: 1px solid var(--line);
}

.other-half a {
    display: inline-flex;
    align-items: center;
    min-height: var(--tap);
    font-weight: 700;
}
.door:hover { background: var(--brand-tint); }

@media (min-width: 40rem) {
    .doors { grid-template-columns: 1fr 1fr; }
}

/* The week on a phone. Eight columns at 375px is 45px a day, which no
 * shift fits in, so each person becomes a card instead: the name, then a
 * line per day they work, then the days they do not as small "+ Wed"
 * targets in a row - in day order throughout. The stacked-card rules
 * above give every cell a labelled block; these undo that for the week.
 * The "+" on a day that already has a shift is left off here, where a
 * second "+" per day would double the card; a second shift that day is
 * added from the form. */
@media (max-width: 44rem) {
    .scroll.stack.sched tr { padding: var(--pad); }

    .scroll.stack.sched td {
        display: inline-block;
        width: auto;
        min-width: 0;
        padding: 0;
        border: 0;
        font-size: 1em;
    }

    .scroll.stack.sched td.who {
        display: block;
        margin: 0 0 0.4em;
        font-weight: 700;
    }

    /* A worked day is a chip too - its day name, then the shift - and
     * flows with the rest, two to a line. One a line measured 355px for
     * a five-day week at 375px, three screens for seven people. */
    .scroll.stack.sched td.has-shift {
        display: inline-flex;
        flex-wrap: wrap;
        align-items: center;
        gap: 0 0.35em;
        margin: 0 0.4em 0.4em 0;
    }

    .scroll.stack.sched td.has-shift::before {
        display: inline;
        color: var(--muted);
        font-size: 0.85em;
        letter-spacing: 0;
        text-transform: none;
    }

    .scroll.stack.sched td.has-shift .add,
    .scroll.stack.sched td:not(.has-shift)::before { display: none; }

    .scroll.stack.sched .shift {
        display: inline-flex;
        align-items: center;
        min-height: var(--tap);
        margin: 0;
        font-size: 0.9em;
    }

    .scroll.stack.sched td:not(.has-shift) { margin: 0 0.4em 0.4em 0; }

    /* The empty days are soft chips in a row - "+ Wed" - with no
     * outline: six outlined pills on a part-timer's card was the same
     * noise as the dashed boxes, in a rounder shape. */
    .scroll.stack.sched .add {
        min-height: var(--tap);
        padding: 0 0.8em;
        border-color: transparent;
        border-radius: 999px;
        background: var(--bg);
        font-size: 0.9em;
    }

    .scroll.stack.sched .add-day {
        position: static;
        width: auto;
        height: auto;
        overflow: visible;
        clip-path: none;
        margin-left: 0.25em;
        font-weight: 500;
    }
}

/* ------------------------------------------------------------------
 * Notices, and templates (scheduling slice 2, db/024)
 * ------------------------------------------------------------------ */

/* A person's unread schedule notices, as one panel: the flash's shape,
 * because it is news, with the brand edge, because it is theirs. Got it
 * is an ordinary button - the one way to clear it. */
.notice {
    margin: 0 0 var(--gap);
    padding: var(--pad);
    border: 1px solid var(--edge);
    border-left: 4px solid var(--brand);
    border-radius: var(--card-radius);
    background: var(--panel);
    box-shadow: 0 1px 2px var(--shadow-near), 0 8px 24px var(--shadow);
}

.notice > p { margin: 0 0 0.4em; }
.notice > ul { margin: 0 0 var(--pad); padding-left: 1.2em; }
.notice > ul li { margin: 0 0 0.2em; }
.notice > form { margin: 0; }

/* Fill this week: the collapsed card the job filters use, its forms and
 * links inset like theirs. */
.adder.fill > p { margin: var(--gap) var(--pad); }

/* A template's lines, day by day, inside its card. */
.template-lines {
    margin: 0 var(--pad) var(--gap);
    padding: 0;
    list-style: none;
}

.template-lines li { margin: 0 0 0.3em; }
.template-lines .shift-day {
    display: inline-block;
    min-width: 2.6em;
    color: var(--muted);
}

.person > .week-note { margin: var(--pad) var(--pad) var(--pad); }

/* ------------------------------------------------------------------
 * Open shifts, availability and Who's on (scheduling slice 3, db/025)
 *
 * A WARNING IS NEVER AMBER OR RED. Amber means over quote and red means
 * past due, and each means only that; "can't work" is neither. It is in
 * WORDS everywhere it appears, with the brand's strongest blue behind
 * it - the colours a button already uses, so nothing new to measure.
 * ------------------------------------------------------------------ */

/* The week's first row: shifts with nobody on them. Their chips are
 * white with a hairline, so an open one never reads as somebody's. */
.sched tr#open td.who {
    color: var(--muted);
    font-style: italic;
    font-weight: 600;
}

.sched tr#open .shift:not(.is-draft) {
    border-color: var(--line);
    background: var(--panel);
}

/* A day somebody has said they can't work: faint stripes in the cell,
 * and the words on top of them - "Can't work", "Can't work from 3pm". */
.sched td.day.cant {
    background: repeating-linear-gradient(135deg, var(--brand-tint) 0 6px,
                                          transparent 6px 12px);
}

/* The words, on an EMPTY day: a day with a shift says it on the shift,
 * so the note there would only say it twice and make the row taller. */
.cant-note {
    display: block;
    margin: 0 0 0.2em;
    color: var(--muted);
    font-size: 0.8em;
    font-weight: 600;
}

.sched td.day.has-shift .cant-note { display: none; }

/* A shift that falls then: a solid edge, and CAN'T WORK in words. */
.sched .shift.is-cant { border-color: var(--brand-dark); }

.cant-tag {
    padding: 0.1em 0.4em;
    border-radius: 999px;
    background: var(--brand-dark);
    color: var(--on-brand);
    font-size: 0.65em;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

/* At a desk, a tab on the chip's BOTTOM edge - DRAFT has the top one -
 * in the cell's own padding, so like DRAFT it takes no width and adds no
 * height to a week that has to fit at 1280px. */
@media (min-width: 44.01rem) {
    /* An empty day's note and its "+" on one line: no taller row. */
    .sched td.day.cant:not(.has-shift) .cant-note {
        display: inline-block;
        margin: 0 0.3em 0 0;
        vertical-align: middle;
    }

    .sched td.day.cant:not(.has-shift) .add {
        display: inline-flex;
        width: 1.7em;
        vertical-align: middle;
    }

    .sched .shift .cant-tag {
        position: absolute;
        bottom: 0;
        left: 0.6em;
        transform: translateY(55%);
        padding: 0 0.45em;
        font-size: 0.72em;
        line-height: 1.3;
    }
}

/* On a phone a day is a chip in a row, striped when they can't work it;
 * an empty one reads "+ Thu  can't work" - the words after the day, so
 * they are not read as part of the "+". */
@media (max-width: 44rem) {
    .scroll.stack.sched td.day.cant {
        display: inline-flex;
        align-items: center;
        gap: 0 0.35em;
        border-radius: 999px;
    }

    .scroll.stack.sched td.day.cant:not(.has-shift) .cant-note {
        display: inline;
        order: 2;
        margin: 0 0.8em 0 0;
    }
}

/* Lists of what somebody has marked - theirs, and the manager's view of
 * one person. Each line a card; on the worker's own, Delete beside it. */
.cant-list {
    margin: 0 0 var(--gap);
    padding: 0;
    list-style: none;
}

.cant-list li {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.25em 1em;
    margin: 0 0 0.5em;
    padding: var(--pad);
    border: 1px solid var(--edge);
    border-radius: var(--card-radius);
    background: var(--panel);
    font-weight: 700;
    box-shadow: 0 1px 2px var(--shadow-near), 0 8px 24px var(--shadow);
}

.cant-list form { margin: 0; }

/* The publish page's heads-up: a flash that holds a list, spaced like a
 * paragraph flash from the Publish button under it. */
section.flash { margin: 0 0 var(--gap); }
section.flash > p { margin: 0 0 0.4em; }
section.flash > ul { margin: 0 0 0.4em; padding-left: 1.2em; }
section.flash > p:last-child { margin: 0; }

.intro { color: var(--muted); }

/* My schedule's way to When I can't work: a whole tap target. */
.cant-link a {
    display: inline-flex;
    align-items: center;
    min-height: var(--tap);
    font-weight: 700;
}

/* Take: a real button's look on a link that opens the page that asks. */
a.take {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: var(--tap);
    padding: 0 1.2em;
    border-radius: 999px;
    background: var(--brand);
    color: var(--on-brand);
    font-weight: 700;
    text-decoration: none;
}

a.take:hover {
    background: var(--brand-dark);
    color: var(--on-brand);
}

.take-shift { font-size: 1.2em; }

/* Who's on: a day's heading, then one card for the day with a line per
 * person - fifteen people on a Monday is one card, not fifteen. */
main.page h3 {
    margin: var(--gap) 0 0.4em;
    color: var(--brand-dark);
    font-size: 1em;
}

.crew-list {
    margin: 0 0 var(--pad);
    padding: 0 var(--pad);
    border: 1px solid var(--edge);
    border-radius: var(--card-radius);
    background: var(--panel);
    list-style: none;
    box-shadow: 0 1px 2px var(--shadow-near), 0 8px 24px var(--shadow);
}

.crew-list li {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.25em 1em;
    min-height: var(--tap);
    padding: 0.4em 0;
    border-top: 1px solid var(--line);
}

.crew-list li:first-child { border-top: 0; }
.crew-list .crew-name { font-weight: 700; }
.crew-list li.you .crew-name { color: var(--brand-dark); }
.crew-list li.open .crew-name { color: var(--muted); font-style: italic; }
.crew-list li.past { color: var(--muted); }

/* ------------------------------------------------------------------
 * Scheduling slice 4: call-offs, swaps and covers
 *
 * Called off is said in the late red the board's PAST DUE uses, always
 * with the words as well - --late on --late-bg and on --panel are both
 * pairs check_styling measures. Amber stays the over-quote badge's alone.
 * ------------------------------------------------------------------ */

/* A quieter Take: an outlined pill, for the link on every shift's row -
 * "Can't make it" eight times in the brand fill would shout. */
a.take.quiet {
    border: 1px solid var(--brand);
    background: var(--panel);
    color: var(--brand);
}

a.take.quiet:hover {
    background: var(--brand-tint);
    color: var(--brand-dark);
}

/* CALLED OFF, in words, in a pill. */
.called {
    padding: 0.1em 0.45em;
    border: 1px solid var(--late-line);
    border-radius: 999px;
    background: var(--late-bg);
    color: var(--late);
    font-size: 0.75em;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    white-space: nowrap;
}

/* On a list of shifts: a red edge, the times struck through. */
.shift-list li.called-off { border-left: 4px solid var(--late); }
.shift-list li.called-off s { color: var(--muted); }

/* Where a request about a shift stands: a line of its own under it. */
.shift-state {
    flex-basis: 100%;
    color: var(--muted);
    font-size: 0.9em;
}

/* On the week, a called-off chip keeps its place: a red edge. */
.sched .shift.is-called-off { border-color: var(--late); }
.sched .shift.is-called-off s { color: var(--muted); }
.sched .shift .called { font-size: 0.65em; }

/* At a desk, CALLED OFF is a tab on the chip's top edge, at the left -
 * DRAFT has the right, CAN'T WORK the bottom - so like them it adds no
 * width and no height to a week that has to fit at 1280px. */
@media (min-width: 44.01rem) {
    .sched .shift .called {
        position: absolute;
        top: 0;
        left: 0.6em;
        transform: translateY(-55%);
        padding: 0 0.45em;
        font-size: 0.72em;
        line-height: 1.3;
    }
}

/* Called off, above the week: one card with a red edge, a line each. */
.called-off-list {
    margin: 0 0 var(--gap);
    padding: var(--pad);
    border: 1px solid var(--edge);
    border-left: 4px solid var(--late);
    border-radius: var(--card-radius);
    background: var(--panel);
    box-shadow: 0 1px 2px var(--shadow-near), 0 8px 24px var(--shadow);
}

.called-off-list h2 {
    margin: 0 0 0.3em;
    color: var(--late);
    font-size: 1em;
}

.called-off-list ul {
    margin: 0;
    padding: 0;
    list-style: none;
}

.called-off-list li {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.25em 1em;
    padding: 0.3em 0;
    border-top: 1px solid var(--line);
}

.called-off-list li:first-child { border-top: 0; }

.called-off-list a {
    display: inline-flex;
    align-items: center;
    min-height: var(--tap);
    font-weight: 700;
}

/* The manager's bar: a call-off in the next day, first in the row, the
 * same red pill wherever the pills are drawn. */
.topnav a.alert,
body:not(.floor) .topnav a.alert {
    border-color: var(--late-line);
    background: var(--late-bg);
    color: var(--late);
    font-weight: 700;
}

/* The home card's note: a coworker has asked them something. */
.door-note {
    color: var(--brand);
    font-weight: 700;
}

/* Asked of you: the words, then Answer. */
.asked-words { flex: 1 1 12em; }

/* The pickers: a coworker or a shift to a line, each a whole tap target;
 * one already on then is a line of its own, not a choice. */
.pick {
    margin: 0 0 var(--gap);
    padding: 0;
    border: 0;
}

.pick legend {
    margin: 0 0 0.4em;
    font-weight: 700;
}

.pick label {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0 0.6em;
    min-height: var(--tap);
}

.pick .busy {
    margin: 0.3em 0;
    color: var(--muted);
}

/* The reader's own can't-work, beside a shift they would take on. */
.cant-mine {
    color: var(--ink);
    font-weight: 700;
}

/* The managers' swaps page: a card a request. */
.swap-card {
    margin: 0 0 var(--pad);
    padding: var(--pad);
    border: 1px solid var(--edge);
    border-radius: var(--card-radius);
    background: var(--panel);
    box-shadow: 0 1px 2px var(--shadow-near), 0 8px 24px var(--shadow);
}

.swap-card p { margin: 0 0 0.4em; }
.swap-card .row-actions { margin-top: 0.4em; }

/* My schedule: an upcoming shift's whole card is the link to its page -
 * the card was already a tap target, so the way to Can't make it costs a
 * short line of words, not a button's height on every row. */
.shift-list li.go { padding: 0; }

.shift-list a.shift-go {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.25em 1em;
    width: 100%;
    min-height: var(--tap);
    padding: var(--pad);
    border-radius: inherit;
    color: inherit;
    text-decoration: none;
}

.shift-list a.shift-go:hover { background: var(--brand-tint); }

.go-cue {
    margin-left: auto;
    color: var(--brand);
    font-size: 0.85em;
    font-weight: 700;
}

/* The call-off number: one unbroken number, a link that dials it. */
a.callnum {
    font-weight: 700;
    white-space: nowrap;
}

/* Yes and No on a request: the pair is a paragraph's worth of space from
 * the link under it - flush against it, measured at 375px. */
.row-actions.answer { margin: 0 0 var(--gap); }


/* ------------------------------------------------------------------
 * Scheduling slice 5: PTO
 *
 * Time off is not a problem, so it is never amber or red: the brand's
 * blue, in words, as CAN'T WORK is - and on the week it borrows CAN'T
 * WORK's stripes and tab, so it adds nothing to a week that has to fit
 * at 1280px.
 * ------------------------------------------------------------------ */

/* What somebody has: a card, the hours left first and large. */
.pto-summary {
    margin: 0 0 var(--gap);
    padding: var(--pad);
    border: 1px solid var(--edge);
    border-radius: var(--card-radius);
    background: var(--panel);
    box-shadow: 0 1px 2px var(--shadow-near), 0 8px 24px var(--shadow);
}

.pto-summary p { margin: 0 0 0.4em; }
.pto-summary p:last-child { margin: 0; }
.pto-left strong { font-size: 1.35em; }

/* A request's state, under its days. */
.pto-status {
    color: var(--muted);
    font-size: 0.9em;
    font-weight: 600;
}

/* Approved PTO under a week's title: in words, in the brand's blue. */
.week-note.pto-line {
    margin: 0 0 var(--pad);
    color: var(--brand);
    font-weight: 700;
}

/* Everybody's numbers: the whole row is the way to their page - an
 * inline name link was 20px tall, under the tap height - the name, then
 * the hours, under it on a phone. */
.pto-people li { padding: 0; }

.pto-people a.pto-go {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.25em 1em;
    width: 100%;
    min-height: var(--tap);
    padding: var(--pad);
    border-radius: inherit;
    color: inherit;
    text-decoration: none;
}

.pto-people a.pto-go:hover { background: var(--brand-tint); }

.pto-people a.pto-go strong {
    color: var(--brand);
    text-decoration: underline;
}

.pto-people a.pto-go span {
    color: var(--muted);
    font-weight: 600;
}

/* My schedule and My shifts: the PTO line straight under When I can't
 * work - two tap targets, flush, rather than a paragraph's gap apart. */
.cant-link + .pto-link { margin-top: calc(var(--gap) * -1); }

/* At a desk, a split shift whose tabs face each other - CAN'T WORK or ON
 * PTO on one chip's bottom edge, DRAFT or CALLED OFF on the next one's
 * top - gets the room its tabs need. Measured at 1280: with 0.3em
 * between the chips they sat on top of each other. */
@media (min-width: 44.01rem) {
    .sched .shift:has(.cant-tag):has(+ .shift),
    .sched .shift:has(+ .shift.is-draft),
    .sched .shift:has(+ .shift.is-called-off) { margin-bottom: 0.75em; }

    .sched .shift:has(.cant-tag):has(+ .shift.is-draft),
    .sched .shift:has(.cant-tag):has(+ .shift.is-called-off) { margin-bottom: 1.15em; }
}

/* Settings: a month, a day or a number of hours inside a sentence rather
 * than a field of its own - narrow enough to share the line. */
select.inline { width: auto; }
input.inline { width: 6.5em; }


/* ------------------------------------------------------------------
 * The quick pass after scheduling slice 5
 * ------------------------------------------------------------------ */

/* The floor's unread count, beside the name: a light pill on the navy,
 * the way to Notifications, there only while something is unread. */
.topbar .bar-new {
    display: inline-flex;
    order: 3;
    align-items: center;
    min-height: var(--tap);
    padding: 0 0.9em;
    border-radius: 999px;
    background: var(--on-navy);
    color: var(--navy);
    font-weight: 700;
    text-decoration: none;
    white-space: nowrap;
}

/* The manager's Notices pill carries its count in a small badge - turned
 * round when the pill is the page being shown, so it never vanishes. */
.topnav .count {
    display: inline-block;
    min-width: 1.5em;
    margin-left: 0.4em;
    padding: 0 0.4em;
    border-radius: 999px;
    background: var(--on-navy);
    color: var(--navy);
    font-size: 0.85em;
    font-weight: 700;
    text-align: center;
}

.topnav a[aria-current="page"] .count {
    background: var(--navy);
    color: var(--on-navy);
}

/* The week's days read "Mon 28th", as written - every other table's
 * headers are capitals, and "28TH" is not how anybody writes a date. */
.sched th { letter-spacing: 0.02em; text-transform: none; }

/* The week's month and year, over its grid. */
.week-month {
    margin: 0 0 0.5rem;
    color: var(--brand-dark);
    font-weight: 700;
}

/* Notifications: a card a notice, the unread ones edged in the brand. */
.notice-list {
    margin: 0 0 var(--gap);
    padding: 0;
    list-style: none;
}

.notice-list > li {
    margin: 0 0 0.5em;
    padding: var(--pad);
    border: 1px solid var(--edge);
    border-radius: var(--card-radius);
    background: var(--panel);
    box-shadow: 0 1px 2px var(--shadow-near), 0 8px 24px var(--shadow);
}

.notice-list > li.is-new { border-left: 4px solid var(--brand); }
.notice-list p { margin: 0 0 0.25em; }
.notice-list ul { margin: 0.25em 0 0; padding-left: 1.2em; }

.notice-when {
    color: var(--muted);
    font-size: 0.9em;
}

.new-tag {
    padding: 0.1em 0.45em;
    border-radius: 999px;
    background: var(--brand);
    color: var(--on-brand);
    font-size: 0.75em;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

/* The floor's theme switch, at the foot of Jobs and My schedule - the
 * floor's bar has no room for it at 600px. */
.theme-foot { margin: 0; }
.theme-foot p { margin: 0 0 var(--gap); }

/* ------------------------------------------------------------------
 * The second quick pass after email to managers (Kyle, 2026-10-07)
 * ------------------------------------------------------------------ */

/* The schedule's pill row: Week, Templates, Swaps, PTO - My schedule's
 * Mine and Who's on, as many as the shop has, one row at every width,
 * each an equal share. Narrower side padding than those two, so four fit
 * a phone. */
.views.tabs {
    grid-template-columns: none;
    grid-auto-columns: 1fr;
    grid-auto-flow: column;
    gap: 0.4rem;
}

.views.tabs .view-link { padding: 0.5em 0.3em; }

/* What waits, inside its pill - "PTO 1" - a small badge, turned round on
 * the pill being shown so it never vanishes. The bar's .topnav .count is
 * the same thing on the navy. */
.view-link .count {
    display: inline-block;
    min-width: 1.5em;
    margin-left: 0.35em;
    padding: 0 0.4em;
    border-radius: 999px;
    background: var(--brand);
    color: var(--on-brand);
    font-size: 0.85em;
    text-align: center;
}

.view-link.current .count {
    background: var(--on-brand);
    color: var(--brand);
}

/* Notifications: each notice's head is its own checkbox's label, so the
 * words are part of the tap; the two Clear buttons share a row. */
.notice-head label.check {
    display: inline-flex;
    align-items: center;
    min-height: var(--tap);
    margin: 0;
    cursor: pointer;
}

.clear-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem 1rem;
    margin: 0 0 var(--gap);
}

/* A demo shop on the admin's list (db/031): made-up people and work,
 * rebuilt by reset_demos.py, never billed - said in the row's own name. */
.demo-tag {
    padding: 0.1em 0.45em;
    border: 1px solid currentColor;
    border-radius: 999px;
    font-size: 0.75em;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

/* The manager's bar at a desk: ONE ROW from 80rem (1280px) - Sign out
 * never wraps (Kyle, 2026-10-07). Measured at 110px, two rows, with a
 * call-off due, the theme button and an ordinary name: the pills alone
 * were about 1,005px. Narrower pills and gaps make room for an ordinary
 * name; a long one gives way, cut short with an ellipsis; and the pills
 * shrink and slide only as the very last resort - their flex-shrink is a
 * thousandth of the name's, so the name always gives first. Below 80rem the
 * bar wraps as it always has. */
@media (min-width: 80rem) {
    body:not(.floor) .topbar-in { flex-wrap: nowrap; column-gap: 0.75rem; }
    body:not(.floor) .topnav { flex: 0 0.001 auto; min-width: 0; }
    body:not(.floor) .topnav a { padding: 0 0.6em; }
    body:not(.floor) .topnav .theme button { padding-right: 0.8em; padding-left: 0.8em; }
    body:not(.floor) .topbar .mark,
    body:not(.floor) .topbar .out { flex-shrink: 0; }
    body:not(.floor) .topbar .who {
        flex: 0 1 auto;
        min-width: 4em;
        overflow: hidden;
        text-overflow: ellipsis;
    }
}

/* ------------------------------------------------------------------
 * The admin page upgrade (designed and decided 2026-10-10)
 * ------------------------------------------------------------------ */

/* Where a shop stands, for its managers, under the bar: past due in the
 * amber of a warning, paused and cancelled in the red of a refusal. As
 * wide as the page's own column, and inside the same side gutter, so at
 * 375px it never touches the screen's edge. */
.standing-banner,
.read-only-line {
    width: calc(100% - 2 * var(--pad));
    max-width: calc(62rem - 2 * var(--pad));
    margin: var(--gap) auto 0;
    padding: var(--pad);
    border: 1px solid var(--accent-line);
    border-left: 4px solid var(--accent);
    border-radius: var(--card-radius);
    background: var(--accent-bg);
}

.standing-banner p { margin: 0; }
.standing-banner a { color: inherit; font-weight: 700; }

.standing-banner.standing-paused,
.standing-banner.standing-cancelled {
    border-color: var(--late-line);
    border-left-color: var(--late);
    background: var(--late-bg);
}

/* The floor's one line: neutral - no money words and no alarm colour. A
 * worker did nothing wrong and can do nothing about it. */
.read-only-line {
    border-color: var(--edge);
    border-left-color: var(--muted);
    background: var(--panel);
}

/* The admin's search: the box and its button on one row at every width. */
.shop-search label { display: block; margin: 0 0 0.3em; }
.search-row { display: flex; gap: 0.5rem; }
.search-row input { flex: 1; min-width: 0; }
.search-row button { flex: none; }

/* The filter row: All and each standing with a shop in it. Up to seven
 * pills, so they wrap rather than share one row the way the schedule's
 * four do. */
.views.standing-filter {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.views.standing-filter .view-link { padding: 0.4em 1em; box-shadow: none; }

/* A shop's card: an employee's card's shape. Closed, the name, its
 * standing and price on the first line and the next date on the second. */
.shop-card {
    margin: 0 0 var(--pad);
    border: 1px solid var(--edge);
    border-radius: var(--card-radius);
    background: var(--panel);
    box-shadow: 0 1px 2px var(--shadow-near), 0 8px 24px var(--shadow);
}

.shop-card > summary {
    display: list-item;
    min-height: var(--tap);
    padding: var(--pad);
    border-radius: var(--card-radius);
    cursor: pointer;
}

.shop-card[open] > summary {
    border-bottom: 1px solid var(--line);
    border-radius: var(--card-radius) var(--card-radius) 0 0;
}

.shop-name { margin-right: 0.2em; overflow-wrap: anywhere; }
.shop-price { margin-left: 0.2em; }
.shop-next { display: block; color: var(--muted); font-size: 0.9em; }

/* The standing, as a badge in its word - never colour alone. */
.standing {
    display: inline-block;
    padding: 0.05em 0.55em;
    border: 1px solid var(--line);
    border-radius: 999px;
    color: var(--brand-dark);
    font-size: 0.8em;
    font-weight: 700;
    white-space: nowrap;
}

.standing-trial .standing { border-color: var(--brand); background: var(--brand-tint); }
.standing-demo .standing { color: var(--muted); }

.standing-past_due .standing {
    border-color: var(--accent-line);
    background: var(--accent-bg);
    color: var(--accent);
}

.standing-paused .standing,
.standing-cancelled .standing {
    border-color: var(--late-line);
    background: var(--late-bg);
    color: var(--late);
}

/* Founding: the demo tag's shape - a fact about the price, not a state. */
.founding-tag {
    padding: 0.05em 0.45em;
    border: 1px solid currentColor;
    border-radius: 999px;
    color: var(--brand-dark);
    font-size: 0.7em;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    white-space: nowrap;
}

.shop-body { padding: 0 var(--pad) var(--pad); }
.shop-body h2 { margin: var(--gap) 0 0.4em; }
.shop-body p { margin: 0 0 var(--pad); }
.shop-body .row-actions { margin: var(--pad) 0 var(--gap); }

/* The facts: one column on a phone, side by side at a desk. */
.shop-facts,
.shop-forms {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
    column-gap: calc(var(--gap) * 1.5);
}

.shop-forms { grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr)); }
.shop-forms form { margin: 0 0 var(--pad); }

textarea {
    width: 100%;
    max-width: 26rem;
    min-height: calc(var(--field) * 2);
    padding: 0.6em 0.9em;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--panel);
    color: inherit;
    font: inherit;
}

textarea:focus {
    outline: none;
    border-color: var(--brand);
    box-shadow: 0 0 0 4px var(--focus);
}

.shop-history ul,
.history-list {
    margin: 0;
    padding: 0;
    list-style: none;
}

.shop-history li,
.history-list li {
    padding: 0.4em 0;
    border-top: 1px solid var(--line);
    overflow-wrap: anywhere;
}

.shop-history li:first-child,
.history-list li:first-child { border-top: 0; }

/* ------------------------------------------------------------------
 * The third quick pass (Kyle, 2026-10-11)
 * ------------------------------------------------------------------ */

/* A running job on the floor: its row tinted and edged in the brand
 * colour, and RUNNING in words beside the customer - never colour alone.
 * A table row on a tablet; the compact card on a phone. */
tr.running > td { background: var(--brand-tint); }
tr.running > td:first-child { box-shadow: inset 4px 0 0 var(--brand); }

@media (max-width: 44rem) {
    .scroll.stack tr.running {
        border-color: var(--brand);
        border-left-width: 6px;
        background: var(--brand-tint);
    }

    .scroll.stack tr.running > td { background: none; box-shadow: none; }
}

.running-tag {
    display: inline-block;
    margin-left: 0.4em;
    padding: 0.05em 0.55em;
    border-radius: 999px;
    background: var(--brand);
    color: var(--on-brand);
    font-size: 0.75em;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    vertical-align: 0.1em;
    white-space: nowrap;
}

/* The week page's head: Earlier and Later as pill buttons either side of
 * a larger, bolder "Week of". On a phone the heading takes its own row
 * and the two pills share the one below it. The floor's My schedule keeps
 * its plain links - .week-head is the manager's alone. */
.week-head {
    display: grid;
    grid-template-columns: auto 1fr auto;
    grid-template-areas: "prev title next";
    align-items: center;
    gap: var(--pad);
}

.week-head .week-step:first-child { grid-area: prev; }
.week-head .week-step:last-child { grid-area: next; }

.week-title {
    grid-area: title;
    margin: 0;
    color: var(--brand-dark);
    font-size: 1.45em;
    font-weight: 800;
    letter-spacing: -0.01em;
    text-align: center;
    text-transform: none;
}

.week-step {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: var(--tap);
    padding: 0.4em 1.1em;
    border: 2px solid var(--brand);
    border-radius: 999px;
    background: var(--panel);
    color: var(--brand);
    font-weight: 700;
    text-decoration: none;
    white-space: nowrap;
}

.week-step:hover {
    background: var(--brand-tint);
    color: var(--brand-dark);
}

@media (max-width: 34rem) {
    .week-head {
        grid-template-columns: 1fr 1fr;
        grid-template-areas: "title title" "prev next";
    }

    .week-head .week-step:first-child { justify-self: start; }
    .week-head .week-step:last-child { justify-self: end; }
}

/* ------------------------------------------------------------------
 * Undo publish and changing a whole day (Kyle, 2026-10-11)
 * ------------------------------------------------------------------ */

/* Each day's own page, from the week: pills that wrap, inside a card
 * that opens like Fill this week. */
.day-links {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem 0.5rem;
    margin: var(--gap) var(--pad) var(--pad);
}

.adder.day-change > p { margin: 0 var(--pad) var(--pad); }

.day-links a {
    display: inline-flex;
    align-items: center;
    min-height: var(--tap);
    padding: 0.2em 0.9em;
    border: 1px solid var(--brand);
    border-radius: 999px;
    background: var(--panel);
    color: var(--brand);
    font-weight: 600;
    text-decoration: none;
    white-space: nowrap;
}

.day-links a:hover { background: var(--brand-tint); color: var(--brand-dark); }

/* A day's page: the shifts as ticked lines in a card, then the two times
 * side by side where they fit. */
.day-shifts {
    margin: 0 0 var(--gap);
    padding: var(--pad);
    border: 1px solid var(--edge);
    border-radius: var(--card-radius);
    background: var(--panel);
}

.day-shifts legend { padding: 0 0.3em; font-weight: 700; }
.day-shifts .check { margin: 0; }

.day-times {
    display: flex;
    flex-wrap: wrap;
    gap: 0 var(--gap);
}

.day-times p { flex: 1 1 10rem; margin-bottom: var(--pad); }
