
/* Self-hosted variable fonts (latin subsets, vendored: no CDN, no IP leak to
   Google, works offline). Source: Google Fonts, SIL OFL. Inter carries an
   optical-size axis, so display sizes tighten by themselves. */
@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 100 900;
    font-display: swap;
    src: url('fonts/inter.woff2') format('woff2');
}

@font-face {
    font-family: 'JetBrains Mono';
    font-style: normal;
    font-weight: 400 500;
    font-display: swap;
    src: url('fonts/jetbrains-mono.woff2') format('woff2');
}

/* ── Design tokens ── */
/* Every colour is defined ONCE here as light-dark(light, dark), steered by
   color-scheme below. Never add a per-theme token block: the old ones drifted.
   The values are the canvas's UI language v2 (docs/frontend.md). */
:root {
    --font-display: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
    --font-body: var(--font-display);
    --font-mono: 'JetBrains Mono', ui-monospace, monospace;

    /* The open board's colour; brand violet everywhere else. Tokens below re-light it. */
    --board-color: #7048e8;

    /* Warm neutrals, OKLCH hue 85. */
    --fg: light-dark(#1c1a17, #f3f2ef);
    --fg-2: light-dark(#5d5a56, #b6b4b0);
    --fg-3: light-dark(#6b6964, #a9a7a3);

    /* Ground (bg-0) holds trays (bg-2), which hold raised tiles (bg-1); a dialog is
       a panel of white groups. bg-0 and bg-2 take the board's tint, below. */
    --bg-1: light-dark(#ffffff, #292725);
    --bg-3: color-mix(in oklab, var(--fg) 10%, var(--bg-1));
    --bg-panel: light-dark(#f2f1ee, #1f1e1b);
    --bg-group: light-dark(#ffffff, #2c2b29);
    /* A segmented control's chosen segment. */
    --bg-thumb: light-dark(#ffffff, #51504c);
    /* Translucent fills for fields, tracks and hovers, so they sit on any surface. */
    --fill-1: light-dark(rgb(28 26 23 / 0.065), rgb(243 242 239 / 0.065));
    --fill-2: light-dark(rgb(28 26 23 / 0.1), rgb(243 242 239 / 0.1));
    --fill-3: light-dark(rgb(28 26 23 / 0.16), rgb(243 242 239 / 0.16));

    --border-1: light-dark(rgb(28 26 23 / 0.08), rgb(243 242 239 / 0.08));
    --border-2: light-dark(rgb(28 26 23 / 0.12), rgb(243 242 239 / 0.12));
    --border-3: light-dark(rgb(28 26 23 / 0.16), rgb(243 242 239 / 0.16));

    --success: light-dark(#1d7d3e, #76cf8a);
    --danger: light-dark(#c51e21, #fd736d);
    --danger-bg: color-mix(in oklab, var(--danger) 13%, transparent);
    /* Soon: due today or tomorrow. */
    --warning: light-dark(#a05000, #f3ae58);
    --warning-bg: color-mix(in oklab, var(--warning) 15%, transparent);
    /* On a --danger fill; dark danger is a light red. */
    --danger-contrast: light-dark(#ffffff, #2d0f0f);

    /* Dark in both themes, raised a little in dark, where near-black would vanish. */
    --toast-bg: light-dark(rgb(37 36 33 / 0.92), rgb(55 53 50 / 0.95));
    --toast-fg: #f3f2ef;
    --scrim: light-dark(rgb(42 39 35 / 0.3), rgb(0 0 0 / 0.45));

    /* Concentric: a child's radius is its parent's minus the gap between them. */
    --radius-lg: 26px;
    --radius: 22px;
    --radius-sm: 14px;
    --radius-btn: 12px;
    --radius-xs: 10px;
    --radius-pill: 999px;

    /* Depth: what holds things is recessed, what you act on is raised. The colours
       are tokens, so one recipe serves both themes; in dark, raised surfaces also
       catch a hairline of light on their top edge. */
    --sh-edge: light-dark(rgb(0 0 0 / 0.07), rgb(0 0 0 / 0.55));
    --sh-key: light-dark(rgb(0 0 0 / 0.05), rgb(0 0 0 / 0.35));
    --sh-soft-1: light-dark(rgb(0 0 0 / 0.06), rgb(0 0 0 / 0.5));
    --sh-soft-2: light-dark(rgb(0 0 0 / 0.18), rgb(0 0 0 / 0.6));
    --sh-soft-3: light-dark(rgb(0 0 0 / 0.22), rgb(0 0 0 / 0.7));
    --sh-soft-4: light-dark(rgb(0 0 0 / 0.3), rgb(0 0 0 / 0.75));
    --sh-inset: light-dark(rgb(0 0 0 / 0.07), rgb(0 0 0 / 0.5));
    --sh-highlight: light-dark(rgb(255 255 255 / 0), rgb(255 255 255 / 0.09));

    --shadow-recessed: inset 0 1px 2.5px var(--sh-inset), inset 0 0 0 0.5px var(--sh-edge);
    --shadow-xs: 0 0 0 0.5px var(--sh-edge), 0 1px 1.5px var(--sh-key);
    --shadow-sm: inset 0 0.5px 0 var(--sh-highlight), 0 0 0 0.5px var(--sh-edge), 0 1px 1.5px var(--sh-key), 0 3px 8px -2px var(--sh-soft-1);
    --shadow-md: inset 0 0.5px 0 var(--sh-highlight), 0 0 0 0.5px var(--sh-edge), 0 4px 10px -3px var(--sh-soft-1), 0 14px 28px -12px var(--sh-soft-2);
    --shadow-lg: inset 0 0.5px 0 var(--sh-highlight), 0 0 0 0.5px var(--sh-edge), 0 2px 6px var(--sh-key), 0 18px 44px -14px var(--sh-soft-3);
    --shadow-xl: inset 0 0.5px 0 var(--sh-highlight), 0 0 0 0.5px var(--sh-edge), 0 12px 24px -8px var(--sh-soft-2), 0 34px 70px -20px var(--sh-soft-4);
    --shadow-card: var(--shadow-sm);
    --shadow-card-hover: var(--shadow-md);
    --shadow-brand: var(--shadow-md);

    /* What floats on the page as material (the sidebar, the phone's tab bar and
       its buttons): a hairline inside and out and a soft drop. */
    --shadow-material: inset 0 0 0 0.5px var(--border-1), 0 0 0 0.5px var(--sh-edge), 0 10px 30px -14px var(--sh-soft-2);

    /* Casts upwards only: a phone sheet rises from the bottom edge, and a downward
       half has nowhere to fall. */
    --shadow-sheet: inset 0 0.5px 0 var(--sh-highlight), 0 0 0 0.5px var(--sh-edge), 0 -2px 6px var(--sh-key), 0 -18px 44px -14px var(--sh-soft-3);

    --ease: cubic-bezier(0.22, 0.68, 0, 1);
    --ease-out: cubic-bezier(0, 0, 0.2, 1);
    --duration: 200ms;

    /* The column's one inset: header label, cards and add button all start on
       it, or the column reads as having two left edges. */
    --column-rail: 8px;

    /* The column's second line: where the words inside a card start. */
    --card-inset: 12px;

    /* The top bar's height, 0 where the header is a sidebar; a board page's title
       bar, set where one shows; the room the sidebar takes. Pages subtract these. */
    --header-h: 64px;
    --titlebar-h: 0px;
    --sidebar-w: 0px;

    /* The phone's floating tab bar: how far it floats off the bottom edge (the
       safe area, if larger), and its footprint, the 62px bar with the same gap
       again above it. Everything that stops at the bar subtracts --phone-nav-h. */
    --phone-nav-bottom: max(0.75rem, env(safe-area-inset-bottom, 0px));
    --phone-nav-h: calc(62px + 0.75rem + var(--phone-nav-bottom));

    /* A board page's .board-phone-bar on a phone: the 56px row of buttons and the
       large title with its line under it (62px). Subtracted like the tab bar. */
    --phone-board-title-h: 118px;

    /* The column switcher's strip on a phone (44px and the gap above it), set
       where there is one; the board subtracts it. */
    --phone-pager-h: 0px;

    color-scheme: light dark;
}

/* The board's colour re-lit in OKLCH to one lightness per theme, so every hue
   reads alike and white on the fill passes AA. Declared wherever --board-color
   is: :root (BoardTint on a board's pages) and an element that wears its own
   board inline, such as a Home tile. --brand fills; --brand-ink is for text,
   icons and focus rings, which need more contrast than a fill. */
:root,
[style*="--board-color"] {
    --brand: light-dark(oklch(from var(--board-color) 0.52 min(c, 0.17) h), oklch(from var(--board-color) 0.54 min(c, 0.17) h));
    --brand-bright: light-dark(oklch(from var(--board-color) 0.58 min(c, 0.17) h), oklch(from var(--board-color) 0.6 min(c, 0.17) h));
    --brand-dim: light-dark(oklch(from var(--board-color) 0.46 min(c, 0.17) h), oklch(from var(--board-color) 0.48 min(c, 0.17) h));
    --brand-ink: light-dark(oklch(from var(--board-color) 0.48 min(c, 0.17) h), oklch(from var(--board-color) 0.78 min(c, 0.14) h));
    --brand-glow: color-mix(in oklab, var(--brand) 13%, transparent);
    --brand-surface: color-mix(in oklab, var(--brand) 18%, transparent);
    --brand-contrast: #ffffff;
    /* The ground and its trays take a breath of the board's colour. */
    --bg-0: light-dark(color-mix(in oklab, var(--board-color) 5%, oklch(0.968 0.004 85)), color-mix(in oklab, var(--board-color) 7%, oklch(0.2 0.004 85)));
    --bg-2: light-dark(color-mix(in oklab, oklch(0.3 0.01 85) 6.5%, var(--bg-0)), color-mix(in oklab, #000 26%, var(--bg-0)));
    --material: light-dark(color-mix(in oklab, color-mix(in oklab, var(--board-color) 9%, oklch(0.99 0.003 85)) 62%, transparent), color-mix(in oklab, color-mix(in oklab, var(--board-color) 12%, oklch(0.29 0.005 85)) 64%, transparent));
    /* A toast is dark in both themes, so its button takes a light accent in both. */
    --toast-accent: oklch(from var(--board-color) 0.83 min(c, 0.13) h);
}

/* Browsers with light-dark() but without relative colours (or without math on a
   channel) would leave the block above invalid, and every var(--brand) unset: a
   primary button with no fill. They get brand violet, already re-lit, and no
   board tint. */
@supports not (color: oklch(from red 0.5 min(c, 0.17) h)) {
    :root,
    [style*="--board-color"] {
        --brand: light-dark(#6752c3, #6d58ca);
        --brand-bright: light-dark(#7864d7, #7e6ade);
        --brand-dim: light-dark(#573faf, #5d45b6);
        --brand-ink: light-dark(#5d45b6, #b3a8ff);
        --bg-0: light-dark(#eeecf2, #1c1b21);
        --bg-2: light-dark(#e0dee3, #0f0e12);
        --material: light-dark(rgb(238 237 249 / 0.62), rgb(51 49 62 / 0.64));
        --toast-accent: #b3a8ff;
    }
}

/* A pinned choice. UA parts (scrollbars, controls, dialog backdrops) follow too. */
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"] { color-scheme: dark; }

/* Set for one frame by App.razor's theme switch, so the swap is one repaint
   rather than hundreds of transitions. Not a general "no transitions" switch. */
:root[data-theme-switching] *,
:root[data-theme-switching] *::before,
:root[data-theme-switching] *::after {
    transition: none !important;
}

/* ── Base reset ── */

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

/* The browser's own ticks (a hand-drawn one needs telling assistive tech about
   twice), in the app's colour and size everywhere. */
input[type="checkbox"],
input[type="radio"] {
    accent-color: var(--brand);
    width: 1.05rem;
    height: 1.05rem;
    flex: 0 0 auto;
    cursor: pointer;
}

h1:focus { outline: none; }

body {
    font-family: var(--font-body);
    margin: 0;
    background: var(--bg-0);
    color: var(--fg);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    line-height: 1.6;
    font-size: 15px;
}

code, pre {
    font-family: var(--font-mono);
}

::selection {
    background: var(--brand);
    color: var(--brand-contrast);
}

/* ── Shared button system ── */

/* The draft's capsules (Components · Controls) in three sizes: regular 34px in the
   Button style, .btn-sm 28px for inline actions in groups and rows, .btn-lg 44px
   for the auth panel and a phone's sheets. Every button is one of the three. */
.btn-primary,
.btn-secondary,
.btn-danger,
.btn-flat {
    display: inline-flex;
    /* A fixed height, so never squeezed by a scrolling flex column. */
    flex: none;
    align-items: center;
    justify-content: center;
    gap: 6px;
    height: 34px;
    padding: 0 16px;
    border: none;
    border-radius: var(--radius-pill);
    cursor: pointer;
    font: inherit;
    font-family: var(--font-body);
    font-size: 13.5px;
    font-weight: 600;
    line-height: 16px;
    letter-spacing: -0.003em;
    transition: all var(--duration) var(--ease);
    position: relative;
    white-space: nowrap;
    /* Some are <a>, and there is no global reset on `a`. */
    text-decoration: none;
}

/* A plus before the words sits nearer the edge: the draft's 14px. */
.btn-primary.btn-add { padding-left: 14px; }

.btn-primary {
    background: var(--brand);
    color: var(--brand-contrast);
    box-shadow: var(--shadow-sm);
}

.btn-primary:hover:not(:disabled) {
    background: var(--brand-bright);
}

.btn-primary:active:not(:disabled),
.btn-secondary:active:not(:disabled),
.btn-danger:active:not(:disabled),
.btn-flat:active:not(:disabled) {
    scale: 0.97;
}

.btn-primary:disabled,
.btn-secondary:disabled,
.btn-danger:disabled,
.btn-flat:disabled {
    opacity: 0.4;
    cursor: not-allowed;
    box-shadow: none;
}

.btn-danger {
    background: var(--danger-bg);
    color: var(--danger);
}

.btn-danger:hover:not(:disabled) {
    background: var(--danger);
    color: var(--danger-contrast);
}

.btn-secondary {
    background: var(--fill-1);
    color: var(--fg);
}

.btn-secondary:hover:not(:disabled) {
    background: var(--fill-2);
}

.btn-flat {
    padding-inline: 10px;
    background: none;
    color: var(--brand-ink);
}

/* Not on touch, where a tap leaves :hover on it and the tint would stay. */
@media (hover: hover) {
    .btn-flat:hover:not(:disabled) { background: var(--brand-glow); }
}

.btn-sm {
    height: 28px;
    padding: 0 12px;
    font-size: 12px;
    font-weight: 500;
    letter-spacing: 0;
}

/* The SDK's sign-in form renders its own button. */
.btn-lg,
.login-card form button[type="submit"] {
    height: 44px;
    padding: 0 22px;
    font-size: 16px;
    line-height: 21px;
    letter-spacing: -0.01em;
}

/* ── Board header ── */

.board-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 1.75rem;
    height: var(--header-h);
    color: var(--fg);
    position: sticky;
    top: 0;
    z-index: 100;
    box-shadow: 0 1px 0 var(--border-1);
}

/* The material on a layer of its own: a backdrop-filter on the header would make
   it the containing block of every fixed child, and the menu's click-catcher
   would shrink to the header's box. */
.board-header::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    background: var(--material);
    -webkit-backdrop-filter: blur(28px) saturate(1.4);
    backdrop-filter: blur(28px) saturate(1.4);
}

.board-header h1 {
    margin: 0;
    font-size: 15.5px;
    font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--fg);
    line-height: 20px;
}

.board-user {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    font-size: 14px;
    color: var(--fg-2);
    position: relative;
}

/* No default style for buttons in .board-user on purpose: each control styles
   itself, so nothing has to out-specify anything. */

.board-nav-link {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    height: 28px;
    color: var(--fg-2);
    text-decoration: none;
    font-size: 13.5px;
    font-weight: 600;
    letter-spacing: -0.003em;
    padding: 0 14px;
    border-radius: var(--radius-pill);
    transition: all var(--duration) var(--ease);
}

.board-nav-link:hover {
    color: var(--fg);
    text-decoration: none;
}

.board-nav-link-active {
    background: var(--bg-thumb);
    color: var(--fg);
    box-shadow: var(--shadow-sm);
    text-decoration: none;
}

.board-title-link {
    text-decoration: none;
    color: inherit;
}

.board-title-link:hover { opacity: 0.8; }

/* ── Board layout ── */

.board-phone-bar { display: none; }

.board {
    display: flex;
    /* Stretch, so the emptiest column still offers a full-height drop target. */
    align-items: stretch;
    /* `safe`: a plain center on an overflowing scroll container pushes the
       first column out of reach. */
    justify-content: safe center;
    gap: 14px;
    padding: 0 1.5rem 18px;
    overflow-x: auto;
    overflow-y: hidden;
    /* Definite height: columns scroll internally, the page never does. */
    height: calc(100vh - var(--header-h) - var(--titlebar-h));
    height: calc(100dvh - var(--header-h) - var(--titlebar-h));
    scroll-behavior: smooth;
    scrollbar-width: thin;
    scrollbar-color: var(--border-2) transparent;
}

/* A tray recessed into the board, its cards raised out of it. Not clipped: a
   column's menu hangs out of a short one, and the list scrolls inside its own
   box, which sits within the rounded corners. */
.column {
    background: var(--bg-2);
    border: none;
    border-radius: var(--radius);
    padding: 4px 0 0;
    width: 320px;
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    /* Fills the board's content box, padding and scrollbar included. */
    max-height: 100%;
    box-shadow: var(--shadow-recessed);
}

/* Name, count, the deck's action, and the menu at the far end. 44px tall. */
.column-header {
    position: relative;
    display: flex;
    align-items: center;
    gap: 8px;
    height: 44px;
    padding: 0 calc(var(--column-rail) + 2px) 0 var(--column-rail);
}

.column-name {
    margin: 0;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-family: var(--font-display);
    font-size: 14px;
    font-weight: 600;
    line-height: 19px;
    letter-spacing: -0.006em;
    color: var(--fg);
}

/* A raised pill on the tray. */
.column-count {
    flex: none;
    padding: 3px 7px;
    border-radius: var(--radius-pill);
    background: var(--bg-1);
    box-shadow: inset 0 0 0 0.5px var(--border-2);
    color: var(--fg-2);
    font-size: 12px;
    font-weight: 600;
    line-height: 14px;
    font-variant-numeric: tabular-nums;
    text-align: center;
}

/* Filtered, the badge counts what is on screen, not what the column holds. */
.column-count-filtered {
    color: var(--brand-ink);
    background: var(--brand-glow);
    box-shadow: none;
}

/* ── A column's menu: its tag filter, and the way to its settings ── */

.column-menu-toggle,
.board-menu-toggle {
    display: inline-grid;
    place-items: center;
    flex: none;
    padding: 0;
    border: none;
    border-radius: var(--radius-pill);
    background: transparent;
    color: var(--fg-2);
    cursor: pointer;
    transition: background var(--duration) var(--ease), color var(--duration) var(--ease);
}

.column-menu-toggle {
    width: 28px;
    height: 28px;
    margin-left: auto;
}

.column-menu-toggle:hover,
.column-menu-toggle[aria-expanded="true"] {
    background: var(--fill-1);
    color: var(--fg);
}

/* Filtering outlives the menu, so the button carries the state. */
.column-menu-toggle-active,
.column-menu-toggle-active:hover {
    background: var(--brand-glow);
    color: var(--brand-ink);
}

/* Under its button, inside the tray's width. */
.column-menu {
    top: calc(100% - 2px);
    right: var(--column-rail);
    max-width: calc(100% - 2 * var(--column-rail));
}

/* The list is the tray's drop target, so it fills it; Add card follows the last
   card inside it. Inline padding is the rail, so the header label, the cards and
   the add button share one left edge. */
.column-cards {
    display: flex;
    flex-direction: column;
    overflow-y: auto;
    padding: 8px var(--column-rail) var(--column-rail);
    flex: 1;
    /* A property, because the deck's edges are pulled up through it. */
    --card-gap: 8px;
    gap: var(--card-gap);
    scrollbar-width: thin;
    scrollbar-color: var(--border-2) transparent;
}

/* An empty tray is still a tray: it says what to do and takes a drop. */
.column-empty {
    margin: 0;
    padding: 26px 10px;
    color: var(--fg-2);
    font-size: 13px;
    line-height: 18px;
    text-align: center;
}

/* A card in here is one being dragged in. display:none, not opacity: only then
   does SortableJS's lastChild() skip the placeholder; otherwise the drop lands
   after it, at the bottom of the column. */
.column-cards:has(.card) .column-empty {
    display: none;
}

/* ── The deck: the last column, folded (docs/ui.md) ── */

/* Its top card over the edges of two beneath. The edges are the list's own
   ::before and ::after, not elements, which SortableJS would count as drop
   places. As flex items ordered after the card and pulled up under it, they
   follow its height. Flex items paint in that order, so z-indexes stack them
   back to front: the card, the nearer ::before, the farther ::after. None while
   the top card is in the air; the clone a touch drag carries keeps its own. */
.column-cards-folded > .card:not(.card-dragging) {
    z-index: 1;
}

.column-cards-folded:has(> .card:not(.card-ghost, .card-dragging))::before,
.column-cards-folded:has(> .card:not(.card-ghost, .card-dragging))::after {
    content: "";
    order: 1;
    flex: none;
    height: 1.5rem;
    margin: calc(7px - 1.5rem - var(--card-gap)) 2.25% 0;
    border-radius: var(--radius-sm);
    background: var(--bg-1);
    box-shadow: var(--shadow-sm);
}

.column-cards-folded:has(> .card:not(.card-ghost, .card-dragging))::before {
    z-index: 0;
}

.column-cards-folded:has(> .card:not(.card-ghost, .card-dragging))::after {
    margin-inline: 4.5%;
    opacity: 0.7;
}

/* A card dragged over the deck shows where it will land: on top. */
.column-cards-folded > .card-ghost {
    order: -1;
}

/* The count beneath is quieter than Show all, which is a .btn-flat beside the name;
   both lay the deck out. A small button's size. */
.column-deck-more {
    order: 2;
    align-self: center;
    height: 28px;
    padding: 0 12px;
    border: none;
    border-radius: var(--radius-pill);
    background: transparent;
    color: var(--fg-2);
    font: inherit;
    font-size: 12px;
    font-weight: 500;
    line-height: 16px;
    cursor: pointer;
    transition: background var(--duration) var(--ease), color var(--duration) var(--ease);
    flex: none;
}

/* Not on touch, where a tap leaves :hover on it. */
@media (hover: hover) {
    .column-deck-more:hover {
        background: var(--fill-1);
        color: var(--fg);
    }
}

/* Laid out, the cards beneath come out from under the top one. Only those new
   to the page: the top card stays put, and a ghost is re-inserted on every move. */
@media (prefers-reduced-motion: no-preference) {
    @starting-style {
        .column-cards-spread > .card:not(.card-ghost) {
            opacity: 0;
            translate: 0 -0.5rem;
        }
    }
}

/* ── Cards ── */

/* The title, then one meta line. A card's words start --card-inset in. */
.card {
    display: flex;
    flex-direction: column;
    gap: 8px;
    background: var(--bg-1);
    border: none;
    border-radius: var(--radius-sm);
    padding: var(--card-inset);
    cursor: pointer;
    /* The origin moves to the point pressed (sortable-interop.js) as the press
       starts; gliding there, it would make the card drift as it pressed in. */
    transition: all var(--duration) var(--ease), transform-origin 0s;
    position: relative;
    box-shadow: var(--shadow-card);
}

/* Hover lift only for a real pointer: on touch, :hover sticks to the tapped card
   and its shadow repainted the board under the dialog's entrance. Touch gets
   only the press, on `scale`. Must stay before .card-chosen. */
@media (hover: hover) {
    .card:hover {
        box-shadow: var(--shadow-card-hover);
        transform: translateY(-1px);
    }

    .card:active {
        transform: none;
        box-shadow: var(--shadow-sm);
    }
}

/* Pressed in, which is also where a drag starts (docs/frontend.md). */
@media (prefers-reduced-motion: no-preference) {
    .card:active {
        scale: 0.97;
    }
}

/* Up to three lines; a pasted URL is one unbreakable word. */
.card-title {
    margin: 0;
    font-family: var(--font-display);
    font-size: 14px;
    font-weight: 600;
    line-height: 19px;
    letter-spacing: -0.006em;
    color: var(--fg);
    overflow-wrap: anywhere;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    line-clamp: 3;
    overflow: hidden;
}

/* Add card follows the last card in the list (flex order, past the deck's parts),
   on the rail, its glyph on the cards' words. */
.column-add {
    order: 3;
    flex: none;
    display: flex;
    align-items: center;
    gap: 7px;
    min-height: 40px;
    margin: 0;
    padding: 0 var(--card-inset);
    border: none;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--fg-2);
    text-align: left;
    cursor: pointer;
    font-family: var(--font-body);
    font-size: 14px;
    font-weight: 600;
    line-height: 20px;
    transition: all var(--duration) var(--ease);
}

.column-add svg { flex: none; }

.column-add:hover {
    color: var(--fg);
    background: var(--fill-1);
}

/* ── Modal ── */

/* The scrim fades on ::before, behind the dialog: an opacity animated on the
   dialog's parent composites the whole dialog offscreen every frame, which was
   what made it sluggish on phones. */
.modal-backdrop {
    position: fixed;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1000;
    padding: 1rem;
}

/* -1 in the backdrop's stacking context: under the dialog, over the page. */
.modal-backdrop::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    background: var(--scrim);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    animation: fadeIn var(--duration) var(--ease-out);
}

@keyframes fadeIn {
    from { opacity: 0; }
    to   { opacity: 1; }
}

@keyframes slideUp {
    from { opacity: 0; transform: translateY(20px) scale(0.96); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
}

.modal {
    background: var(--bg-panel);
    border: none;
    border-radius: var(--radius-lg);
    width: min(720px, 100%);
    max-height: calc(100vh - 2rem);
    display: flex;
    flex-direction: column;
    box-shadow: var(--shadow-xl);
    animation: slideUp 300ms var(--ease-out);
}

/* The card dialog's language for every dialog (docs/ui.md): no hairlines, a bar
   with the title and a 32px round close, white groups on the panel, actions at
   the foot on the right. */
.modal-header {
    display: flex;
    align-items: center;
    gap: 4px;
    min-height: 48px;
    padding: 0 10px 0 16px;
    flex-shrink: 0;
}

.modal-header h2 {
    margin: 0 auto 0 0;
    font-size: 17px;
    font-weight: 600;
    line-height: 22px;
    letter-spacing: -0.008em;
}

.modal-close {
    flex: none;
    display: grid;
    place-items: center;
    width: 32px;
    height: 32px;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: none;
    color: var(--fg);
    cursor: pointer;
    transition: background var(--duration) var(--ease);
}

.modal-close:hover { background: var(--fill-1); }

.modal-close:disabled { opacity: 0.35; cursor: progress; }

/* 6px on top: room for a focus ring the scroller would clip. */
.modal-body {
    padding: 6px 12px 18px;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 18px;
    flex: 1;
    min-height: 0;
    scrollbar-width: thin;
    scrollbar-color: var(--border-2) transparent;
}

.field {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

/* .atproto-login-error is the SDK LoginForm's error block, which ships
   Bootstrap classes we don't have. */
.modal-error,
.atproto-login-error {
    margin: 0;
    padding: 12px 14px;
    border-radius: var(--radius-sm);
    background: var(--danger-bg);
    color: var(--danger);
    font-size: 13px;
    font-weight: 500;
    line-height: 18px;
}

.modal-footer {
    display: flex;
    gap: 8px;
    align-items: center;
    padding: 0 12px 12px;
    flex-shrink: 0;
}

.modal-footer-spacer { flex: 1; }

/* ── Card modal: the inspector (docs/ui.md, "The card dialog") ── */

/* A panel of white groups: title, description, the properties, the thread. The
   same at every width; the board docks it, a phone makes it a sheet. */
.card-modal {
    width: min(560px, 100%);
}

/* — The bar: where the card lives, Share, Close — */

.card-bar .card-crumb {
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
    font-size: 13px;
    font-weight: 600;
    line-height: 20px;
    letter-spacing: 0;
    color: var(--fg-2);
}

.card-crumb-part {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.card-crumb-new { color: var(--fg); }

.card-crumb-sep { flex: none; }

/* The board's mark in its own colour, wherever the dialog opens. */
.card-crumb-mark {
    flex: none;
    display: grid;
    place-items: center;
    width: 16px;
    height: 16px;
    border-radius: 5px;
    background: linear-gradient(rgb(255 255 255 / 0.18), transparent), var(--brand);
    box-shadow: var(--shadow-sm);
    color: var(--brand-contrast);
    font-size: 10px;
    font-weight: 700;
    line-height: 1;
}

.card-bar .share-button {
    flex: none;
    width: 32px;
    height: 32px;
    padding: 0;
    justify-content: center;
    color: var(--fg);
}

.card-bar .share-button svg {
    width: 18px;
    height: 18px;
    stroke-width: 1.6;
}

/* The phone sheet's, below. */
.card-grabber,
.card-done { display: none; }

/* — Groups and their headings — */

.section-head {
    display: flex;
    align-items: center;
    gap: 6px;
    min-height: 28px;
    padding: 0 2px 6px 14px;
}

.section-label,
.section-count {
    font-size: 13px;
    line-height: 20px;
    color: var(--fg-2);
}

.section-label { font-weight: 600; }

.section-note {
    margin: 0;
    padding: 12px 14px;
    color: var(--fg-3);
    font-size: 14px;
}

/* An input styled as the heading it is, in a group of its own. Focus rings it
   in the accent, as every field here. */
.composer-title {
    width: 100%;
    margin: 0;
    border: none;
    border-radius: var(--radius-sm);
    padding: 13px 14px;
    background: var(--bg-group);
    box-shadow: var(--shadow-sm);
    font-family: var(--font-display);
    font-size: 20px;
    font-weight: 700;
    line-height: 26px;
    letter-spacing: -0.012em;
    color: var(--fg);
    transition: box-shadow var(--duration) var(--ease);
}

.composer-title::placeholder {
    color: var(--fg-3);
    opacity: 0.7;
}

.composer-title:focus,
.composer-body:focus,
.due-input:focus,
.move-select:focus,
.comment-form textarea:focus,
.comment-edit textarea:focus {
    outline: none;
    background-color: var(--bg-group);
    box-shadow: 0 0 0 2px var(--brand-ink), 0 0 0 5.5px var(--brand-glow);
}

/* A title you may read but not change: the same type, no field. */
.composer-title-static { overflow-wrap: anywhere; }

.composer-desc {
    display: flex;
    flex-direction: column;
}

/* — Write / Preview switch — */

.segmented {
    margin-left: auto;
    display: inline-flex;
    gap: 2px;
    padding: 2px;
    background: var(--fill-1);
    border-radius: var(--radius-pill);
}

.segmented-tab {
    border: none;
    background: transparent;
    border-radius: var(--radius-pill);
    padding: 0 14px;
    font: inherit;
    font-family: var(--font-body);
    font-size: 13.5px;
    font-weight: 600;
    color: var(--fg-2);
    cursor: pointer;
    transition: all var(--duration) var(--ease);
    height: 28px;
    line-height: 16px;
    letter-spacing: -0.003em;
}

.segmented-tab:hover:not(.segmented-tab-on) {
    color: var(--fg);
}

.segmented-tab-on {
    background: var(--bg-thumb);
    color: var(--fg);
    box-shadow: var(--shadow-sm);
}

/* Beside the view control, so it drops the auto margin the segmented control
   uses when alone on its row. */
.segmented-scale {
    margin-left: 0;
}

/* The draft's small size. */
.composer-desc .segmented-tab,
.segmented-scale .segmented-tab {
    height: 24px;
    padding: 0 10px;
    font-size: 12px;
    font-weight: 500;
    letter-spacing: 0;
}

/* Both tabs share one footprint, so switching never resizes the dialog. */
.composer-panel {
    display: flex;
    flex-direction: column;
}

.composer-body,
.composer-preview,
.composer-empty {
    flex: 1;
    min-height: 6.5rem;
    margin: 0;
    border-radius: var(--radius-sm);
    padding: 12px 14px;
    background: var(--bg-group);
    box-shadow: var(--shadow-sm);
    font-size: 14px;
    line-height: 20px;
    color: var(--fg);
}

/* Grows with what is typed, like the preview it becomes; where the browser
   cannot, it is resized by hand. */
.composer-body {
    width: 100%;
    max-height: 24rem;
    border: none;
    font-family: var(--font-body);
    resize: none;
    field-sizing: content;
    transition: box-shadow var(--duration) var(--ease);
}

@supports not (field-sizing: content) {
    .composer-body { resize: vertical; }
}

.composer-body::placeholder {
    color: var(--fg-3);
    opacity: 0.7;
}

.composer-preview { overflow-wrap: anywhere; }

.composer-empty { color: var(--fg-3); }

/* — The properties: one group, a row each, hairlines after the icon column — */

.card-props {
    display: flex;
    flex-direction: column;
    border-radius: var(--radius-sm);
    background: var(--bg-group);
    box-shadow: var(--shadow-sm);
}

.prop-row {
    position: relative;
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 12px 14px;
}

.prop-row + .prop-row::before {
    content: "";
    position: absolute;
    top: 0;
    left: 54px;
    right: 0;
    height: 1px;
    background: var(--border-2);
}

.prop-icon {
    flex: none;
    display: grid;
    place-items: center;
    width: 28px;
    height: 28px;
    border-radius: 8px;
    background: var(--brand-glow);
    color: var(--brand-ink);
}

.prop-main {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
    padding-top: 5px;
}

.prop-label {
    font-size: 12.5px;
    font-weight: 600;
    line-height: 16px;
    color: var(--fg-2);
}

/* Chips at the size of a line of text, not of a card's labels. */
.card-props .tag-picker,
.card-props .assignee-picker { gap: 6px; }

.card-props .tag-chip {
    gap: 6px;
    padding: 6px 11px 6px 9px;
    font-size: 13px;
    font-weight: 400;
    line-height: 18px;
}

.card-props .tag-chip:not(.tag-chip-selected):not(.tag-chip-static) {
    box-shadow: inset 0 0 0 1px var(--border-2);
}

/* The tick, then the colour, then the name. */
.card-props .tag-chip-selected::before {
    order: -2;
    margin: 0;
    width: 13px;
    height: 13px;
}

.card-props .assignee-chip {
    gap: 6px;
    padding: 3px;
    border: 0;
    background: var(--fill-1);
}

.card-props .assignee-chip:hover {
    background: var(--fill-2);
    box-shadow: none;
}

.card-props .assignee-chip .assignee-name { font-weight: 400; }

.card-props .assignee-chip .assignee-avatar {
    width: 22px;
    height: 22px;
    border: none;
    box-shadow: none;
    font-size: 9px;
}

.card-props .assignee-chip-static { padding-right: 9px; }

.card-props .assignee-chip-x {
    display: grid;
    place-items: center;
    width: 22px;
    height: 22px;
    color: var(--fg);
}

.card-props .assignee-add {
    align-self: center;
    gap: 4px;
    padding: 5px 10px 5px 8px;
    border: 0;
}

.card-props .assignee-add-plus {
    font-weight: 400;
}

/* ── Markdown ── */

.markdown {
    font-size: 14px;
    line-height: 20px;
    color: var(--fg);
}

.markdown p { margin: 0 0 0.5rem; }
.markdown p:last-child { margin-bottom: 0; }
.markdown ul, .markdown ol { padding-left: 1.25rem; margin: 0 0 0.5rem; }

.markdown code {
    font-family: var(--font-mono);
    background: var(--fill-1);
    padding: 0.15rem 0.45rem;
    border-radius: 6px;
    font-size: 0.82em;
    border: none;
}

.markdown pre {
    background: var(--fill-1);
    padding: 1rem;
    border-radius: var(--radius-xs);
    overflow-x: auto;
    border: none;
}

.markdown pre code {
    background: transparent;
    padding: 0;
}

.markdown a {
    color: var(--brand-ink);
    text-decoration: underline;
    text-decoration-color: color-mix(in srgb, var(--brand) 35%, transparent);
    text-underline-offset: 2px;
}

.markdown a:hover {
    text-decoration-color: var(--brand-ink);
}

/* An attached image, at most the text's width and a screen's height. */
.markdown img {
    max-width: 100%;
    max-height: 28rem;
    height: auto;
    border-radius: var(--radius-xs);
    vertical-align: middle;
}

/* Any other attached file: a paperclip before its name. */
.markdown a[href^="/attachment/"]::before {
    content: "";
    display: inline-block;
    width: 0.95em;
    height: 0.95em;
    margin-right: 0.3em;
    vertical-align: -0.12em;
    background: currentColor;
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m21.4 11.1-9.2 9.2a6 6 0 0 1-8.5-8.5l9.2-9.2a4 4 0 0 1 5.7 5.7l-9.2 9.2a2 2 0 0 1-2.8-2.8l8.5-8.5'/%3E%3C/svg%3E") center / contain no-repeat;
}

/* ── Attaching ── */

/* The share button's shape; a label, so the file input inside it opens the picker. */
.attach-button {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0.4rem;
    border-radius: 50%;
    color: var(--fg-3);
    cursor: pointer;
    transition: all var(--duration) var(--ease);
}

.attach-button:hover,
.attach-button:focus-within {
    background: var(--bg-1);
    color: var(--fg);
}

.attach-button:focus-within {
    box-shadow: 0 0 0 3px var(--brand-glow);
}

.attach-button-busy {
    cursor: progress;
    animation: attach-busy 1s var(--ease) infinite alternate;
}

@keyframes attach-busy {
    to { opacity: 0.35; }
}

/* Out of sight but still focusable, so the keyboard reaches the picker. */
.attach-input {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    overflow: hidden;
    pointer-events: none;
}

.section-head .attach-button {
    margin-left: auto;
}

.section-head .attach-button + .segmented {
    margin-left: 0;
}

/* ── Sharing ── */

/* No layout of its own; each surface places it. */
.share-button {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    background: none;
    border: none;
    color: var(--fg-3);
    cursor: pointer;
    padding: 0.5rem;
    border-radius: 50%;
    font: inherit;
    font-family: var(--font-body);
    transition: all var(--duration) var(--ease);
}

.share-button:hover:not(:disabled) {
    background: var(--fill-1);
    color: var(--fg);
}

.share-button:disabled {
    opacity: 0.55;
    cursor: default;
}

.share-button svg {
    flex-shrink: 0;
}

/* ── Link cards ── */

/* Deliberately the board's own card: same surface, radius and shadow. */
.link-card {
    display: flex;
    flex-direction: column;
    gap: 6px;
    background: var(--bg-1);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-card);
    padding: var(--card-inset);
    margin: 8px 0;
    text-decoration: none;
    color: var(--fg);
    transition: box-shadow var(--duration) var(--ease), transform var(--duration) var(--ease);
    /* An anchor in a flex column still has to be told not to stretch its text. */
    align-items: stretch;
}

.link-card:hover {
    box-shadow: var(--shadow-card-hover);
    transform: translateY(-1px);
    text-decoration: none;
}

.link-card-kind {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    font-weight: 500;
    color: var(--fg-2);
    min-width: 0;
    line-height: 16px;
}

.link-card-board,
.link-card-column {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.link-card-sep {
    flex-shrink: 0;
    opacity: 0.7;
}

.link-card-title {
    font-size: 14px;
    font-weight: 600;
    line-height: 19px;
    color: var(--fg);
    letter-spacing: -0.006em;
}

.link-card-excerpt {
    font-size: 13px;
    line-height: 18px;
    color: var(--fg-2);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.link-card-meta {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
}

.link-card-count {
    font-size: 12px;
    color: var(--fg-2);
    font-weight: 500;
    line-height: 16px;
}

/* A link we could not resolve is shown as the plain link. */
.link-card-plain {
    margin: 0.5rem 0;
    word-break: break-word;
}

.link-card-plain a {
    color: var(--brand-ink);
    text-decoration: underline;
    text-decoration-color: color-mix(in srgb, var(--brand) 35%, transparent);
    text-underline-offset: 2px;
}

/* ── SortableJS drag states (docs/frontend.md) ── */

/* The slot a card will land in: tinted, dashed, its words hidden. Full size at
   once, since SortableJS sizes its clone off it. It ties with .card:active, which
   a mouse's slot still is, and wins by coming after it: keep it below. */
.card.card-ghost {
    background: var(--brand-glow);
    box-shadow: none;
    outline: 1.5px dashed var(--brand-ink);
    outline-offset: -1.5px;
    scale: none;
    transition: none;
}

.card.card-ghost > * { visibility: hidden; }

/* Picked up. A mouse picks a card up as it presses, so there .card:active keeps
   it pressed in until it moves. */
.card-chosen {
    cursor: grabbing;
    box-shadow: var(--shadow-xl);
}

/* A finger has held for 200ms (sortable-interop.js): the card lifts, the only
   sign the press worked. `scale`, because :hover and :active own `transform`.
   Not on the ghost, which SortableJS leaves chosenClass on. */
@media (hover: none) and (prefers-reduced-motion: no-preference) {
    .card-chosen:not(.card-ghost) {
        scale: 1.04;
        transition: scale 320ms var(--ease-spring);
    }
}

/* In the air: SortableJS's clone, moved by a `transform` it writes on every
   pointer move. A transition lagged each write and the library compounds the
   lag, so none here; sortable-interop.js lifts and tilts it on `scale`, `rotate`
   and `translate`. will-change moved ~26-30% of a touch drag's paint time off
   the main thread, and the class only exists while a card is in the air. */
.card.card-dragging {
    box-shadow: var(--shadow-xl);
    transition: none;
    will-change: transform;
}

/* ── Tag chips ── */

.card-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
}

.tag-chip {
    --tag-color: #888;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 8px 3px 7px;
    border-radius: var(--radius-pill);
    border: none;
    background: light-dark(oklch(from var(--tag-color) 0.93 min(c, 0.06) h), oklch(from var(--tag-color) 0.36 min(c, 0.075) h));
    color: var(--fg);
    font-size: 12px;
    font-weight: 500;
    cursor: pointer;
    line-height: 16px;
    transition: all var(--duration) var(--ease);
}

.tag-chip::after {
    content: "";
    order: -1;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: light-dark(oklch(from var(--tag-color) 0.6 min(c, 0.16) h), oklch(from var(--tag-color) 0.72 min(c, 0.15) h));
}

.tag-chip-static { cursor: default; }

.tag-picker {
    display: flex;
    flex-wrap: wrap;
    gap: 0.45rem;
}

/* An unpicked choice is an outline of its colour; picked, it fills. */
.tag-chip:not(.tag-chip-selected):not(.tag-chip-static) {
    background: transparent;
    color: var(--fg-2);
    box-shadow: inset 0 0 0 1px var(--border-3);
}

/* The tick marks a picked tag, since fill alone reads as another colour. Only on
   choices: card chips are labels and get none. */
.tag-picker .tag-chip-selected::before,
.calendar-legend-chip[aria-pressed="true"]::before,
.assignee-option-on::after {
    content: "";
    display: inline-block;
    flex: 0 0 auto;
    width: 0.85em;
    height: 0.85em;
    background: currentColor;
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round' d='M3 8.5 6.5 12 13 4.5'/%3E%3C/svg%3E") no-repeat center / contain;
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round' d='M3 8.5 6.5 12 13 4.5'/%3E%3C/svg%3E") no-repeat center / contain;
}

.tag-picker .tag-chip-selected::before,
.calendar-legend-chip[aria-pressed="true"]::before {
    margin-right: 0.35em;
    vertical-align: -0.05em;
}

.assignee-option-on::after {
    background: var(--brand);
    width: 0.95rem;
    height: 0.95rem;
    margin-left: auto;
}

.tag-picker-empty {
    margin: 0;
    color: var(--fg-3);
    font-size: 13px;
}

/* ── A card's meta line ── */

/* Due, tags, the description's mark and the comments wrap on the left; the
   faces keep the right. */
.card-meta {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
}

.card-meta-chips {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    min-width: 0;
}

/* The member's colour, exactly; the chip is that hue re-lit. */
.card .tag-chip::after,
.menu-dot { background: var(--tag-color); }

.card-has-description,
.card-comment-count {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    color: var(--fg-2);
    font-size: 12px;
    font-weight: 500;
    line-height: 16px;
    white-space: nowrap;
}

/* currentColor masks: identical on every platform, and no markup per card. */
.card-has-description::before,
.card-comment-count::before {
    content: "";
    width: 14px;
    height: 14px;
    flex: none;
    background: currentColor;
    -webkit-mask: var(--glyph) no-repeat center / contain;
    mask: var(--glyph) no-repeat center / contain;
}

.card-has-description {
    --glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' d='M4 5.5h12M4 9h12M4 12.5h7.5'/%3E%3C/svg%3E");
}

.card-comment-count {
    --glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath fill='none' stroke='black' stroke-width='1.6' stroke-linejoin='round' d='M4 5.6a2.2 2.2 0 0 1 2.2-2.2h7.6A2.2 2.2 0 0 1 16 5.6v5.6a2.2 2.2 0 0 1-2.2 2.2H9.6L6 16.5v-3.1a2.2 2.2 0 0 1-2-2.2z'/%3E%3C/svg%3E");
}

/* ── Assignees ── */

/* Overlapping faces; each avatar's 2px ring, the surface's colour, keeps them
   legible. Faces caps the stack at three. */
.card-assignees {
    display: flex;
    flex: none;
    isolation: isolate;
}

.card-assignees .assignee-avatar:not(:first-child) {
    margin-left: -4px;
}

/* Earlier faces over later ones. */
.card-assignees .assignee-avatar:nth-child(1) { z-index: 4; }
.card-assignees .assignee-avatar:nth-child(2) { z-index: 3; }
.card-assignees .assignee-avatar:nth-child(3) { z-index: 2; }

.card .card-assignees .assignee-avatar {
    width: 22px;
    height: 22px;
    font-size: 11px;
    box-shadow: none;
}

.assignee-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: var(--brand-surface);
    color: var(--brand-ink);
    font-size: 11px;
    font-weight: 600;
    border: 2px solid var(--bg-1);
    box-shadow: 0 0 0 1px var(--border-1);
    letter-spacing: -0.03em;
}

.assignee-avatar-img {
    object-fit: cover;
    background: var(--fill-2);
    padding: 0;
}

/* Initials on a soft colour of the member's own (Faces.HueOf). */
.assignee-avatar[style*="--h"] {
    background: light-dark(oklch(0.88 0.06 var(--h)), oklch(0.42 0.06 var(--h)));
    color: light-dark(oklch(0.4 0.11 var(--h)), oklch(0.9 0.1 var(--h)));
}

/* After the .assignee-avatar base on purpose: same specificity, and the base's
   fill would win from below. Opaque, so the face it overlaps stays under it. */
.assignee-overflow {
    background: color-mix(in oklab, var(--fg) 10%, var(--bg-1));
    color: var(--fg-2);
    font-weight: 600;
}

.assignee-picker {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.45rem;
}

.assignee-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.3rem 0.6rem 0.3rem 0.3rem;
    border-radius: var(--radius-pill);
    border: 1px solid var(--border-2);
    background: var(--bg-0);
    color: var(--fg);
    font: inherit;
    font-family: var(--font-body);
    cursor: pointer;
    font-size: 13px;
    transition: all var(--duration) var(--ease);
    line-height: 18px;
}

.assignee-chip:hover {
    border-color: var(--brand-ink);
    background: var(--brand-glow);
    box-shadow: var(--shadow-xs);
}

.assignee-chip-x {
    color: var(--fg-3);
    font-size: 16px;
    line-height: 1;
    transition: color var(--duration) var(--ease);
}

.assignee-chip:hover .assignee-chip-x {
    color: var(--danger);
}

.assignee-chip-static {
    cursor: default;
    background: var(--fill-1);
}

.assignee-chip-static:hover {
    background: var(--fill-1);
    border-color: var(--border-2);
    box-shadow: none;
}

.assignee-chip .assignee-name {
    color: var(--fg);
    font-weight: 500;
}

/* Dashed: a place a chip could go. Stretched to the chips' height. */
.assignee-add {
    display: inline-flex;
    align-items: center;
    align-self: stretch;
    gap: 0.3rem;
    padding: 0.3rem 0.85rem;
    border-radius: var(--radius-pill);
    border: 1.5px dashed var(--brand-ink);
    background: transparent;
    color: var(--brand-ink);
    font: inherit;
    font-family: var(--font-body);
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    transition: background var(--duration) var(--ease);
    line-height: 18px;
}

/* A glyph in the markup rather than ::before: the canvas tracer (design/trace)
   reads text from elements. */
.assignee-add-plus {
    font-size: 16px;
    line-height: 1;
}

.assignee-add:hover,
.assignee-add[aria-expanded="true"] {
    background: var(--brand-glow);
    border-style: solid;
}

/* Anchored on the panel, not the button: the button moves along the row, and a
   menu following it would slide past the dialog's edge. */
.detail-panel-assignees {
    position: relative;
}

/* Invisible click-catcher: a click elsewhere shuts the menu rather than hitting
   what was under it. */
.assignee-menu-catcher {
    position: fixed;
    inset: 0;
    z-index: 20;
}

.assignee-menu {
    position: absolute;
    top: calc(100% - 0.5rem);
    left: 0.5rem;
    right: 0.5rem;
    z-index: 30;
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 6px;
    background: var(--bg-1);
    border-radius: 16px;
    box-shadow: var(--shadow-lg);
    animation: column-filter-in var(--duration) var(--ease) both;
}

.assignee-search {
    width: 100%;
    border: none;
    border-radius: var(--radius-btn);
    padding: 0 12px;
    font: inherit;
    font-family: var(--font-body);
    font-size: 14px;
    background: var(--fill-1);
    color: var(--fg);
    transition: all var(--duration) var(--ease);
    height: 36px;
    box-shadow: var(--shadow-recessed);
}

.assignee-search::placeholder { color: var(--fg-3); }

.assignee-search:focus {
    outline: none;
    background: var(--bg-1);
    box-shadow: inset 0 0 0 2px var(--brand-ink), 0 0 0 5.5px var(--brand-glow);
}

.assignee-options {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 1px;
}

.assignee-option {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 4px 10px;
    border: none;
    border-radius: var(--radius-xs);
    background: transparent;
    color: var(--fg);
    font: inherit;
    font-family: var(--font-body);
    text-align: left;
    cursor: pointer;
    transition: background var(--duration) var(--ease);
}

.assignee-option:hover { background: var(--fill-1); }

.assignee-option:focus-visible {
    outline: none;
    box-shadow: inset 0 0 0 2px var(--brand-ink);
}

.assignee-option-on,
.assignee-option-on:hover {
    background: var(--brand-glow);
}

.assignee-option-text {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-width: 0;
}

.assignee-option-name,
.assignee-option-handle {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.assignee-option-name {
    font-size: 13.5px;
    font-weight: 500;
    line-height: 18px;
}

.assignee-option-handle {
    font-size: 12px;
    color: var(--fg-2);
    line-height: 16px;
}

.assignee-menu-note {
    margin: 2px 10px;
    font-size: 12px;
    color: var(--fg-2);
    font-weight: 500;
    line-height: 16px;
}

/* ── Due dates on a card ── */

/* Words for today and tomorrow, a date otherwise. Orange only for soon, red only
   for overdue, a tick for done: each state has its own mark, so colour is never
   the only signal. Home's strip, link cards and search wear the base and overdue. */
.card-due {
    --glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4.3' width='14' height='12.7' rx='3'/%3E%3Cpath d='M3 8.6h14M7 2.8v3M13 2.8v3'/%3E%3C/g%3E%3C/svg%3E");
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 12px;
    font-weight: 500;
    line-height: 16px;
    color: var(--fg-2);
    background: var(--fill-1);
    padding: 3px 8px 3px 6px;
    border-radius: var(--radius-pill);
    white-space: nowrap;
}

.card-due::before {
    content: "";
    width: 13px;
    height: 13px;
    flex: none;
    background: currentColor;
    -webkit-mask: var(--glyph) no-repeat center / contain;
    mask: var(--glyph) no-repeat center / contain;
}

.card-due-soon {
    --glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='10' cy='10' r='6.6'/%3E%3Cpath d='M10 6.4V10l2.4 1.6'/%3E%3C/g%3E%3C/svg%3E");
    color: var(--warning);
    background: var(--warning-bg);
}

.card-due-overdue {
    --glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round'%3E%3Ccircle cx='10' cy='10' r='6.6'/%3E%3Cpath d='M10 6.4v4.2'/%3E%3Ccircle cx='10' cy='13.4' r='.6' fill='black'/%3E%3C/g%3E%3C/svg%3E");
    color: var(--danger);
    background: var(--danger-bg);
}

/* Done needs no box: the tick says it. */
.card-due-done {
    --glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round' d='M4.6 10.4 8.1 13.9 15.4 6.1'/%3E%3C/svg%3E");
    padding-inline: 0;
    background: none;
}

.card-due-done::before { background: var(--success); }

/* — The due-date field in the card dialog — */

.due-picker,
.move-picker {
    display: flex;
    align-self: stretch;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
}

/* Wells recessed into the group; the menus and pickers are the platform's. */
.due-input,
.move-select {
    min-width: 0;
    height: 32px;
    border: none;
    border-radius: var(--radius-btn);
    padding: 0 10px 0 12px;
    background: var(--fill-1);
    box-shadow: var(--shadow-recessed);
    font: inherit;
    font-family: var(--font-body);
    font-size: 13px;
    color: var(--fg);
    transition: box-shadow var(--duration) var(--ease), background var(--duration) var(--ease);
}

.due-input { width: 160px; }

/* "Saturday · in 4 days"; overdue is the one state that earns colour. */
.due-when {
    font-size: 12px;
    font-weight: 500;
    line-height: 16px;
    color: var(--fg-2);
}

.due-when-overdue { color: var(--danger); }

/* — Moving a card to another board — */

/* A well with an up-down mark; the list itself is the platform's. */
.move-select {
    flex: 0 1 200px;
    padding-right: 30px;
    font-size: 14px;
    appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath fill='none' stroke='%235d5a56' stroke-width='1.12' stroke-linecap='round' stroke-linejoin='round' d='M4.76 5.32 7 3.08l2.24 2.24M4.76 8.68 7 10.92l2.24-2.24'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 10px center;
    background-size: 14px;
    cursor: pointer;
}

:root[data-theme="dark"] .move-select {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath fill='none' stroke='%23b6b4b0' stroke-width='1.12' stroke-linecap='round' stroke-linejoin='round' d='M4.76 5.32 7 3.08l2.24 2.24M4.76 8.68 7 10.92l2.24-2.24'/%3E%3C/svg%3E");
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .move-select {
        background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath fill='none' stroke='%23b6b4b0' stroke-width='1.12' stroke-linecap='round' stroke-linejoin='round' d='M4.76 5.32 7 3.08l2.24 2.24M4.76 8.68 7 10.92l2.24-2.24'/%3E%3C/svg%3E");
    }
}

.move-note {
    margin: 0;
    font-size: 12px;
    line-height: 16px;
    color: var(--fg-2);
}

.move-note-drops {
    display: block;
    color: var(--danger);
}

/* ── Calendar (the board read by date) ── */

/* The head: the month as the page's large title over its line, then the view,
   Today, the arrows and ⋯ (Figma, Desktop · Calendar). On a board's calendar it
   is the board's title bar (.board-titlebar.calendar-head), which keeps its views
   and puts the exports in the board's menu. Narrow, the controls wrap under the
   title, so the bar's fixed height gives way; the page below is sized by flex. */
.calendar-head,
.calendar-page {
    width: 100%;
    /* Wider than the app's reading measure, since these views answer by how much
       you see at once; capped so ultrawide day cells stay squares. */
    max-width: 1800px;
    margin-inline: auto;
}

/* flex-end with the title's auto margin: the controls keep right when they wrap. */
.calendar-head,
.board-titlebar.calendar-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: 8px 10px;
    height: auto;
    min-height: var(--titlebar-h);
    padding: 18px 1.5rem 16px;
}

.calendar-title {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    margin-right: auto;
}

.calendar-heading,
.calendar-sub {
    margin: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.calendar-heading {
    font-family: var(--font-display);
    font-size: 30px;
    font-weight: 700;
    line-height: 36px;
    letter-spacing: -0.022em;
    color: var(--fg);
}

.calendar-sub {
    font-size: 13px;
    line-height: 18px;
    color: var(--fg-2);
}

/* A phone's line starts with the year its title leaves out. */
.calendar-sub-phone { display: none; }

.calendar-controls {
    display: flex;
    align-items: center;
    gap: 10px;
}

.calendar-view.segmented { margin-left: 0; }

.calendar-nav {
    display: flex;
    align-items: center;
    gap: 10px;
}

.calendar-today,
.calendar-step,
.calendar-more .board-menu-toggle {
    display: inline-grid;
    place-items: center;
    border: none;
    border-radius: var(--radius-pill);
    background: var(--fill-1);
    color: var(--fg);
    cursor: pointer;
    transition: background var(--duration) var(--ease);
}

.calendar-today {
    height: 34px;
    padding: 0 16px 0 14px;
    font: inherit;
    font-family: var(--font-body);
    font-size: 13.5px;
    font-weight: 600;
    letter-spacing: -0.003em;
}

.calendar-step,
.calendar-more .board-menu-toggle {
    width: 32px;
    height: 32px;
    padding: 0;
}

.calendar-today:hover,
.calendar-step:hover:not(:disabled),
.calendar-more .board-menu-toggle:hover,
.calendar-more .board-menu-toggle[aria-expanded="true"] { background: var(--fill-2); }

.calendar-step:disabled {
    opacity: 0.35;
    cursor: default;
}

.calendar-more { position: relative; }

.calendar-more .menu {
    top: calc(100% + 6px);
    right: 0;
}

/* The legend is the filter wider; the menu's copy is a phone's. */
.calendar-menu-filter { display: none; }

.menu .calendar-dot { margin: 0 2px; }

.calendar-page {
    display: flex;
    flex-direction: column;
    gap: 14px;
    padding: 0 1.5rem 18px;
    /* Fallback; the :has() rule below derives the height instead. */
    height: calc(100vh - var(--header-h) - var(--titlebar-h));
    height: calc(100dvh - var(--header-h) - var(--titlebar-h));
    overflow: hidden;
}

@media (min-width: 600px) and (max-width: 959px) {
    .calendar-head,
    .board-titlebar.calendar-head,
    .calendar-page { padding-inline: 1.15rem; }
}

/* Height measured, not subtracted: the subtraction fits with no slack, so a
   browser off by a pixel (Firefox RDM) hid the last week. Body is a flex column
   and the page takes the rest. :has() keeps it off every other page. */
html:has(.calendar-page) { height: 100%; }

body:has(.calendar-page) {
    height: 100%;
    display: flex;
    flex-direction: column;
    /* The other children are the header (fixed, or a bar of its own height) and
       the calendar's head; the page takes what they leave. */
    overflow: hidden;
}

body:has(.calendar-page) > .calendar-page {
    height: auto;
    flex: 1;
    min-height: 0;
    /* A flex item's min-width is its content: the timeline pushed the page past the
       viewport and body's overflow clipped the head. */
    min-width: 0;
}

body:has(.calendar-page) > .calendar-head { flex: none; }

/* min-height: 0 is load-bearing, or the grid grows the page. */
.calendar-body {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

/* The range, the legend and what is overdue share a row to give the grid the height. */
.calendar-meta {
    display: flex;
    align-items: center;
    gap: 10px;
    flex: none;
    min-height: 28px;
}

.calendar-meta:not(:has(*)) { display: none; }

.calendar-legend {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    min-width: 0;
}

/* A hairline between the range and the legend. */
.segmented-scale + .calendar-legend::before {
    content: "";
    width: 1px;
    height: 20px;
    background: var(--border-2);
}

/* Every entry's colour, from --cal-color (Calendar.razor's EntryStyle). */
.calendar-dot {
    flex: none;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--cal-color, var(--fg-3));
}

.calendar-legend-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 24px;
    padding: 0 10px 0 8px;
    border: none;
    border-radius: var(--radius-pill);
    background: var(--bg-group);
    box-shadow: var(--shadow-sm);
    color: var(--fg);
    font: inherit;
    font-family: var(--font-body);
    font-size: 12px;
    font-weight: 500;
    line-height: 16px;
    white-space: nowrap;
    cursor: pointer;
    transition: opacity var(--duration) var(--ease), background var(--duration) var(--ease);
}

/* While a filter is in force the boards or tags it leaves out recede. */
.calendar-legend-chip-off {
    background: var(--fill-1);
    box-shadow: none;
    opacity: 0.55;
}

.calendar-legend-chip-off:hover { opacity: 0.8; }

.calendar-overdue {
    margin: 0 0 0 auto;
    font-size: 12px;
    font-weight: 500;
    line-height: 16px;
    color: var(--danger);
    white-space: nowrap;
}

/* Both classes on purpose: .empty-state is declared later at equal specificity
   and would win min-height and padding. Here the flex parent sets the height. */
.empty-state.calendar-empty {
    flex: 1;
    min-height: 0;
    justify-content: center;
    /* Lifted off geometric centre, since the eye takes the heading for the middle.
       Taken off the bottom (half the difference is the lift; a matching top
       padding would cancel it) and capped in vh. */
    padding: 1rem 2rem min(6rem, 14vh);
}

/* — Month grid: white days in a recessed tray — */

/* A grid, not flex: six flex:1 rows round independently and could overflow by
   a pixel, cutting the last week. minmax(0, 1fr) so a full cell can't push its
   row. */
.calendar-grid {
    display: grid;
    grid-template-rows: 30px repeat(var(--weeks, 6), minmax(0, 1fr));
    gap: 4px;
    flex: 1;
    min-height: 0;
    padding: 0 8px 8px;
    border-radius: var(--radius);
    background: var(--bg-2);
    box-shadow: var(--shadow-recessed);
    overflow: hidden;
}

.calendar-weekdays,
.calendar-week {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    gap: 4px;
}

.calendar-week {
    /* Low on purpose: the grid clips, so a higher floor lost the last week (e.g. at
       175% zoom). The cell adapts; this keeps the day number legible. */
    min-height: 2rem;
}

.calendar-weekday {
    align-self: end;
    padding: 0 10px 6px;
    font-size: 12px;
    font-weight: 600;
    line-height: 16px;
    color: var(--fg-2);
}

/* A size container: how many chips a day shows is a container query below, so
   only whole chips show and the count stays true at every height. */
.calendar-day {
    container: cal-day / size;
    display: flex;
    flex-direction: column;
    gap: 3px;
    min-height: 0;
    padding: 6px;
    border-radius: 12px;
    background: var(--bg-1);
    /* Clips rather than stretching the row. */
    overflow: hidden;
}

/* Another month's days sit in the tray itself: dimmed, not dropped, since a grid
   with holes stops reading as weeks. */
.calendar-day-outside { background: none; }

.calendar-day-open { cursor: pointer; }

.calendar-day-open:hover { box-shadow: inset 0 0 0 1px var(--border-2); }

.calendar-day-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 4px;
    flex: none;
    height: 22px;
}

.calendar-daynum {
    display: inline-flex;
    align-items: center;
    height: 22px;
    padding: 0 6px;
    border-radius: var(--radius-pill);
    font-size: 12.5px;
    font-weight: 600;
    line-height: 16px;
    color: var(--fg);
    white-space: nowrap;
}

.calendar-day-outside .calendar-daynum {
    font-weight: 400;
    color: var(--fg-2);
}

.calendar-day-today .calendar-daynum {
    background: var(--brand);
    color: var(--brand-contrast);
}

/* Shown where a day is too short for "+N more" (the queries below). */
.calendar-day-count {
    display: none;
    font-size: 11px;
    font-weight: 600;
    line-height: 1;
    padding: 3px 6px;
    border-radius: var(--radius-pill);
    background: var(--fill-2);
    color: var(--fg-2);
}

.calendar-day-cards {
    display: flex;
    flex-direction: column;
    gap: 3px;
    min-height: 0;
}

/* — A card on its day: its board's (or tag's) dot and one line — */

.cal-card {
    display: flex;
    align-items: center;
    gap: 6px;
    flex: none;
    width: 100%;
    min-width: 0;
    height: 22px;
    padding: 0 7px;
    border: none;
    border-radius: 8px;
    background: var(--bg-1);
    box-shadow: var(--shadow-sm);
    color: var(--fg);
    font: inherit;
    font-family: var(--font-body);
    font-size: 11.5px;
    font-weight: 500;
    line-height: 16px;
    text-align: left;
    cursor: pointer;
    transition: box-shadow var(--duration) var(--ease), background var(--duration) var(--ease);
}

.cal-card:hover { box-shadow: var(--shadow-md); }

.cal-card .calendar-dot {
    width: 7px;
    height: 7px;
}

/* One line: a wrapping title would push the week's rows apart. */
.cal-card-title {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.cal-overdue .cal-card-title { color: var(--danger); }

/* In its board's deck: met, so quiet. */
.cal-card.cal-done {
    background: none;
    box-shadow: none;
    color: var(--fg-2);
}

.cal-card.cal-done:hover { background: var(--fill-1); }

/* Only where a chip has a second line to give; day-cell chips are one line. */
.cal-card-board {
    display: block;
    font-size: 11.5px;
    font-weight: 500;
    color: var(--fg-2);
    overflow: hidden;
    text-overflow: ellipsis;
}

.cal-more {
    flex: none;
    align-self: flex-start;
    height: 16px;
    padding: 0;
    border: none;
    background: none;
    color: var(--fg-2);
    font: inherit;
    font-family: var(--font-body);
    font-size: 11.5px;
    font-weight: 500;
    line-height: 16px;
    cursor: pointer;
}

.cal-more:hover { color: var(--brand-ink); }

/* Three chips, then "+N more" (DayChipCap in Calendar.razor). Every chip is in the
   markup, so counting them stays true. A shorter day shows as many whole chips as
   fit, and its total where that leaves any out. The queries measure the content
   box: a 22px head and a 3px gap, then 25px a chip (22 and a gap), so n chips need
   22 + 25n px and "+N more" 19 more. */
.calendar-day-cards > .cal-card:nth-child(n+4) { display: none; }

@container cal-day (height < 116px) {
    .cal-more { display: none; }
    .calendar-day-head:has(+ .calendar-day-cards > .cal-card:nth-child(4)) .calendar-day-count { display: inline-block; }
}

@container cal-day (height < 97px) {
    .calendar-day-cards > .cal-card:nth-child(n+3) { display: none; }
    .calendar-day-head:has(+ .calendar-day-cards > .cal-card:nth-child(3)) .calendar-day-count { display: inline-block; }
}

@container cal-day (height < 72px) {
    .calendar-day-cards > .cal-card:nth-child(n+2) { display: none; }
    .calendar-day-head:has(+ .calendar-day-cards > .cal-card:nth-child(2)) .calendar-day-count { display: inline-block; }
}

@container cal-day (height < 47px) {
    .calendar-day-cards { display: none; }
    .calendar-day-count { display: inline-block; }
}

/* A phone's month and agenda; see the phone block. */
.calendar-mini,
.calendar-agenda { display: none; }

/* — Timeline: one lane per board (a tag, on a board's own calendar), days across — */

/* A card of its own height, which scrolls when the lanes outgrow the page. */
.calendar-timeline-scroll {
    flex: 0 1 auto;
    min-height: 0;
    overflow: auto;
    border-radius: var(--radius-sm);
    background: var(--bg-group);
    box-shadow: var(--shadow-sm);
    scrollbar-width: thin;
    scrollbar-color: var(--border-2) transparent;
}

.calendar-timeline {
    --label-w: 176px;
    --col-w: 30px;
    position: relative;
    /* Sized to fit the whole range on a desktop; scrolls only when narrower. Never
       width: max-content: with 1fr tracks it sized every column to the widest chip. */
    min-width: calc(var(--label-w) + var(--cols) * var(--col-w));
}

/* A coarser axis gets wider columns. */
.calendar-timeline-quarter { --col-w: 4.5rem; }
.calendar-timeline-all { --col-w: 5rem; }

.timeline-head,
.timeline-row {
    display: grid;
    grid-template-columns: var(--label-w) minmax(0, 1fr);
}

/* One column grid for the axis, the lanes and the two overlays, so all align.
   minmax(0, 1fr): an auto track lets one long nowrap chip widen the day. */
.timeline-days,
.timeline-track,
.timeline-shade,
.timeline-now {
    display: grid;
    grid-template-columns: repeat(var(--cols), minmax(0, 1fr));
}

/* Positioned, so the axis and the lanes paint over the weekend bands. */
.timeline-head,
.timeline-lanes { position: relative; }

.timeline-head { border-bottom: 1px solid var(--border-2); }

/* Weekends shaded down the whole axis, and today as a line through the lanes;
   both are overlays, so the lanes need no cell per day. */
.timeline-shade,
.timeline-now {
    position: absolute;
    inset: 0 0 0 var(--label-w);
    pointer-events: none;
}

.timeline-shade > span { background: var(--fill-1); }

.timeline-now > span {
    justify-self: center;
    width: 2px;
    background: var(--brand);
}

/* Sticky, so the labels stay while the days scroll under them. */
.timeline-corner,
.timeline-lane-head {
    position: sticky;
    left: 0;
    z-index: 1;
    background: var(--bg-group);
}

.timeline-corner {
    display: flex;
    align-items: center;
    padding-left: 16px;
    font-size: 12px;
    font-weight: 500;
    line-height: 16px;
    color: var(--fg-2);
}

.timeline-day {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    min-width: 0;
    height: 48px;
    color: var(--fg);
    white-space: nowrap;
}

.timeline-day-past { color: var(--fg-3); }

.timeline-day-letter {
    font-size: 11px;
    font-weight: 600;
    line-height: 14px;
    color: var(--fg-3);
}

.timeline-day-num {
    padding: 1px 6px;
    border-radius: var(--radius-pill);
    font-size: 12px;
    font-weight: 500;
    line-height: 16px;
}

.timeline-day-today .timeline-day-num {
    background: var(--brand);
    color: var(--brand-contrast);
    font-weight: 600;
}

/* The month or year a run of columns belongs to; coarser scales only. */
.timeline-head-groups { border-bottom: none; }

.timeline-group {
    padding: 10px 0 0;
    text-align: center;
    font-size: 12px;
    font-weight: 600;
    color: var(--fg-2);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.timeline-row + .timeline-row { border-top: 1px solid var(--border-2); }

.timeline-lane-head {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    min-width: 0;
    padding: 10px 10px 12px 16px;
}

/* The board's first letter on its colour, lit from above, as in the sidebar. */
.timeline-mark {
    flex: none;
    display: grid;
    place-items: center;
    width: 16px;
    height: 16px;
    margin-top: 1px;
    border-radius: 5px;
    background: linear-gradient(rgb(255 255 255 / 0.18), rgb(255 255 255 / 0)), var(--brand);
    box-shadow: var(--shadow-sm);
    color: var(--brand-contrast);
    font-size: 9px;
    font-weight: 700;
    line-height: 1;
}

.timeline-lane-head .calendar-dot { margin: 5px 4px 0; }

.timeline-label {
    flex: 1;
    min-width: 0;
    font-size: 13px;
    font-weight: 500;
    line-height: 18px;
    color: var(--fg);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.timeline-count {
    flex: none;
    font-size: 12px;
    font-weight: 500;
    line-height: 18px;
    color: var(--fg-3);
}

.timeline-track {
    /* Bars carry an explicit grid-row per packed sub-row. */
    grid-auto-rows: 26px;
    row-gap: 4px;
    align-content: start;
    min-height: 42px;
    padding: 6px 0;
}

/* Reaches past its day so the title has room (see CalendarTimeline.Pack); the
   coloured edge pins it to its date, since a due date is a point, not a span.
   Inset 3px from its day; a grid item's margin shrinks it, so it never runs past
   the axis. */
.timeline-card {
    height: 26px;
    margin-left: 3px;
    gap: 6px;
    padding: 0 10px 0 6px;
    background: color-mix(in oklab, var(--cal-color) 22%, transparent);
    box-shadow: none;
    font-size: 12px;
    font-weight: 600;
}

.timeline-card::before {
    content: "";
    flex: none;
    width: 3px;
    height: 16px;
    border-radius: 2px;
    background: var(--cal-color);
}

.timeline-card:hover {
    background: color-mix(in oklab, var(--cal-color) 32%, transparent);
    box-shadow: none;
}

.timeline-card.cal-overdue::before { background: var(--danger); }

.timeline-card.cal-done {
    background: color-mix(in oklab, var(--cal-color) 22%, transparent);
    color: var(--fg);
    opacity: 0.5;
}

.timeline-card-title {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The column is the date here; the phone's vertical timeline shows it. */
.timeline-card-date { display: none; }

/* — One day in full, for the cards the cell could not fit — */

/* Flex centring on the overlay: slideUp animates `transform`, which would
   replace a centring translate on the panel. Below the card dialog's 1000; both
   exist for the frame of the swap. */
.calendar-overlay {
    position: fixed;
    inset: 0;
    background: var(--scrim);
    z-index: 900;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
    animation: fadeIn var(--duration) var(--ease-out);
}

/* Both panels are dialogs in the card dialog's language (.modal): the panel, a bar
   with the title and a 32px close, no hairlines. */
.calendar-day-sheet,
.calendar-subscribe-panel {
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 0 16px 18px;
    background: var(--bg-panel);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-xl);
    animation: slideUp var(--duration) var(--ease-out);
}

.calendar-day-sheet {
    width: min(380px, 100%);
    max-height: min(70vh, 32rem);
}

.calendar-day-sheet-head {
    display: flex;
    align-items: center;
    gap: 4px;
    flex: 0 0 auto;
    min-height: 48px;
    margin-right: -6px;
}

.calendar-day-sheet-head h3 {
    margin: 0 auto 0 0;
    font-size: 17px;
    font-weight: 600;
    line-height: 22px;
    letter-spacing: -0.008em;
    color: var(--fg);
}

/* The one chip list allowed to scroll: nothing in the sheet is hidden. */
.calendar-day-sheet-cards {
    display: flex;
    flex-direction: column;
    gap: 6px;
    overflow-y: auto;
    scrollbar-width: thin;
    scrollbar-color: var(--border-2) transparent;
}

/* A list, not a grid, so its chips may wrap. */
.calendar-day-sheet-cards .cal-card {
    height: auto;
    min-height: 34px;
    padding: 8px 10px;
    font-size: 13px;
    line-height: 18px;
}

.calendar-day-sheet-cards .cal-card-title { white-space: normal; }

/* — The subscription link — */

/* Centred by .calendar-overlay. */
.calendar-subscribe-panel {
    width: min(460px, 100%);
    max-height: calc(100dvh - 2rem);
    overflow-y: auto;
}

.calendar-subscribe-blurb {
    margin: 0;
    font-size: 13px;
    line-height: 18px;
    color: var(--fg-2);
}

.calendar-subscribe-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin: 0;
}

/* A .chip with its lock. */
.calendar-subscribe-tag {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 8px;
    border-radius: var(--radius-pill);
    background: var(--fill-1);
    color: var(--fg-2);
    font-size: 11px;
    font-weight: 600;
    line-height: 14px;
}

.calendar-subscribe-tag svg {
    width: 12px;
    height: 12px;
    flex: 0 0 auto;
}

.calendar-subscribe-loading {
    display: flex;
    justify-content: center;
    padding: 1.5rem 0;
}

/* A string to copy, not read; the ellipsis keeps the panel one height. A well,
   as every field. */
.calendar-subscribe-url {
    width: 100%;
    height: 36px;
    padding: 0 12px;
    border: none;
    border-radius: var(--radius-btn);
    background: var(--fill-1);
    box-shadow: var(--shadow-recessed);
    color: var(--fg-2);
    font-family: var(--font-mono);
    font-size: 12px;
    text-overflow: ellipsis;
    flex: none;
}

.calendar-subscribe-url:focus {
    outline: none;
    box-shadow: inset 0 0 0 2px var(--brand-ink), 0 0 0 5.5px var(--brand-glow);
}

.calendar-subscribe-actions {
    display: flex;
    gap: 8px;
}

/* Not .modal-error's red: nothing has gone wrong. */
.calendar-subscribe-warning {
    margin: 0;
    padding: 12px 14px;
    border-radius: var(--radius-sm);
    background: var(--brand-glow);
    color: var(--fg-2);
    font-size: 12px;
    font-weight: 500;
    line-height: 16px;
}

.calendar-subscribe-regen {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.calendar-subscribe-hint {
    font-size: 12px;
    font-weight: 500;
    line-height: 16px;
    color: var(--fg-3);
}

/* ── The calendar on a phone ──
   The draft's (Figma, Phone · Calendar): a large title with the year in its line,
   a compact month with a dot per card, and the agenda under it. The head keeps
   the arrows and Today level with the title, and the view and ⋯ (the filter and
   the exports) on a row of their own. */
@media (max-width: 599px) {
    .calendar-head,
    .board-titlebar.calendar-head {
        display: grid;
        grid-template-columns: minmax(0, 1fr) auto;
        grid-template-areas:
            "title nav"
            "view more";
        align-items: center;
        gap: 10px 8px;
        min-height: 0;
        padding: 0.625rem 1rem 0.5rem 1.25rem;
    }

    .calendar-controls { display: contents; }
    .calendar-title { grid-area: title; }
    .calendar-view { grid-area: view; justify-self: start; }

    .calendar-nav {
        grid-area: nav;
        align-self: start;
        gap: 8px;
    }

    .calendar-more,
    .calendar-head .board-titlebar-more {
        grid-area: more;
        justify-self: end;
        position: relative;
    }

    /* The tab bar is the way between pages here, as on a board. */
    .calendar-head .nav-views,
    .calendar-head .board-titlebar-faces { display: none; }

    .calendar-heading {
        font-size: 34px;
        line-height: 41px;
    }

    .calendar-heading-year,
    .calendar-sub-wide { display: none; }

    .calendar-sub-phone {
        display: block;
        font-weight: 500;
    }

    /* Material, like the board's own top: 40px, level with the title. */
    .calendar-today,
    .calendar-step,
    .calendar-head .board-menu-toggle {
        height: 40px;
        background: var(--material);
        box-shadow: var(--shadow-material);
        color: var(--fg);
    }

    .calendar-step,
    .calendar-head .board-menu-toggle { width: 40px; }

    .calendar-today { padding: 0 14px; }

    /* The filter is in the menu here and outlives it, so ⋯ carries it, as a column's does. */
    .calendar-head .board-menu-toggle-active { color: var(--brand-ink); }

    .calendar-head .menu {
        top: calc(100% + 4px);
        right: 0;
        width: min(280px, calc(100vw - 2rem));
        max-height: 60dvh;
        overflow-y: auto;
    }

    .calendar-head .menu .menu-row {
        min-height: 44px;
        font-size: 15px;
    }

    .calendar-menu-filter { display: block; }

    .calendar-page {
        padding: 0 0 0.5rem;
        gap: 0;
    }

    /* Only the timeline's range is left on the row; the legend is in ⋯. */
    .calendar-meta { padding: 0 1rem 0.5rem; }
    .calendar-legend,
    .calendar-overdue { display: none; }
    .calendar-meta:not(:has(.segmented-scale)) { display: none; }

    .calendar-grid { display: none; }

    .calendar-mini {
        display: block;
        flex: none;
        padding: 4px 16px 8px;
    }

    .calendar-mini-weekdays,
    .calendar-mini-week {
        display: grid;
        grid-template-columns: repeat(7, minmax(0, 1fr));
    }

    .calendar-mini-weekdays span {
        padding-bottom: 6px;
        text-align: center;
        font-size: 13px;
        font-weight: 500;
        line-height: 18px;
        color: var(--fg-3);
    }

    .calendar-mini-day {
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: 3px;
        min-width: 0;
        padding: 3px 0 5px;
        border: none;
        background: none;
        color: var(--fg);
        font: inherit;
        font-family: var(--font-body);
        cursor: pointer;
        -webkit-tap-highlight-color: transparent;
    }

    .calendar-mini-num {
        display: grid;
        place-items: center;
        width: 36px;
        height: 36px;
        border-radius: 50%;
        font-size: 17px;
        line-height: 22px;
    }

    .calendar-mini-weekend { color: var(--fg-2); }
    .calendar-mini-outside { color: var(--fg-3); }

    .calendar-mini-today .calendar-mini-num,
    .calendar-mini-selected .calendar-mini-num {
        font-size: 16px;
        font-weight: 600;
    }

    .calendar-mini-today .calendar-mini-num {
        background: var(--brand-glow);
        color: var(--brand-ink);
    }

    .calendar-mini-selected .calendar-mini-num {
        background: var(--brand);
        color: var(--brand-contrast);
    }

    .calendar-mini-dots {
        display: flex;
        justify-content: center;
        gap: 3px;
        height: 5px;
    }

    .calendar-mini-dots .calendar-dot {
        width: 5px;
        height: 5px;
    }

    /* Scrolls on its own so the month above stays put. */
    .calendar-agenda {
        display: flex;
        flex-direction: column;
        gap: 18px;
        flex: 1;
        min-height: 0;
        overflow-y: auto;
        padding: 14px 16px 16px;
        border-top: 1px solid var(--border-2);
        scrollbar-width: none;
    }

    .calendar-agenda::-webkit-scrollbar { display: none; }

    .calendar-agenda-head {
        display: flex;
        align-items: baseline;
        gap: 6px;
        margin: 0;
        padding: 0 4px 8px;
        font-size: 16px;
        font-weight: 600;
        line-height: 21px;
        letter-spacing: -0.01em;
        color: var(--fg-2);
    }

    .calendar-agenda-lead .calendar-agenda-head { color: var(--fg); }

    .calendar-agenda-when {
        font-size: 13px;
        font-weight: 500;
        line-height: 18px;
        letter-spacing: 0;
        color: var(--fg-2);
    }

    .calendar-agenda-list {
        display: flex;
        flex-direction: column;
        border-radius: var(--radius-sm);
        background: var(--bg-group);
        box-shadow: var(--shadow-sm);
        overflow: hidden;
    }

    /* Its own class rather than .cal-card; see Calendar.razor. */
    .agenda-card {
        display: flex;
        align-items: center;
        gap: 12px;
        width: 100%;
        padding: 12px 16px 12px 12px;
        border: none;
        background: none;
        color: var(--fg);
        font: inherit;
        font-family: var(--font-body);
        text-align: left;
        cursor: pointer;
    }

    .agenda-card + .agenda-card { border-top: 1px solid var(--border-2); }
    .agenda-card:active { background: var(--fill-1); }

    .agenda-card-bar {
        flex: none;
        width: 4px;
        height: 36px;
        border-radius: 2px;
        background: var(--cal-color, var(--fg-3));
    }

    .agenda-card-text {
        display: flex;
        flex-direction: column;
        gap: 1px;
        flex: 1;
        min-width: 0;
    }

    .agenda-card-title,
    .agenda-card-meta {
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .agenda-card-title {
        font-size: 17px;
        line-height: 22px;
    }

    .agenda-card-meta {
        font-size: 13px;
        font-weight: 500;
        line-height: 18px;
        color: var(--fg-2);
    }

    .agenda-card.cal-overdue .agenda-card-title { color: var(--danger); }
    .agenda-card.cal-done .agenda-card-title { color: var(--fg-2); }

    .agenda-card-chevron {
        flex: none;
        color: var(--fg-3);
    }

    .calendar-day-sheet {
        width: 100%;
        max-height: 80dvh;
    }

    /* ── The timeline runs down the page here ──
       Sideways, a month is three phone screens. Same markup: lanes stack, cards
       list in date order, and each bar carries its date. */
    .calendar-timeline-scroll {
        margin: 0 1rem;
        overflow-x: hidden;
    }

    .calendar-timeline { min-width: 0; }

    .timeline-head,
    .timeline-shade,
    .timeline-now { display: none; }

    .timeline-row {
        display: block;
        padding-bottom: 10px;
    }

    .timeline-lane-head {
        position: static;
        padding: 12px 14px 8px;
        background: none;
    }

    /* A lane is a list here. */
    .timeline-track {
        display: flex;
        flex-direction: column;
        gap: 4px;
        min-height: 0;
        padding: 0 10px;
    }

    /* Flex ignores the bars' inline grid placement, but the width has to be taken back. */
    .timeline-card {
        grid-column: auto !important;
        grid-row: auto !important;
        width: 100%;
        height: auto;
        min-height: 34px;
        margin: 0;
        padding: 6px 10px 6px 8px;
        font-size: 13px;
    }

    /* Fixed width so the dates form a column; wide enough for the longest
       ("Nov 2, 2027"), since a flex item's auto min-width would let one grow. */
    .timeline-card-date {
        display: block;
        flex: 0 0 5rem;
        min-width: 0;
        overflow: hidden;
        font-size: 11.5px;
        font-weight: 600;
        color: var(--fg-2);
        white-space: nowrap;
    }

    /* Stacked: side by side at this width they wrap mid-label. */
    .calendar-subscribe-actions { flex-direction: column; }
}

/* ── Comments ── */

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

.comments-earlier { margin-left: auto; }

/* The thread and the box to add to it, one group with hairlines between. */
.discussion-group {
    border-radius: var(--radius-sm);
    background: var(--bg-group);
    box-shadow: var(--shadow-sm);
}

.discussion-group > * + * { border-top: 1px solid var(--border-2); }

.comment {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 12px 14px;
}

.comment > .assignee-avatar {
    flex: none;
    border: none;
    box-shadow: none;
}

.comment-main {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.comment-header {
    display: flex;
    align-items: baseline;
    gap: 6px;
    min-width: 0;
}

.comment-author {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 13px;
    font-weight: 600;
    line-height: 20px;
    color: var(--fg);
}

.comment-time,
.comment-edited {
    flex: none;
    font-size: 12px;
    font-weight: 500;
    line-height: 16px;
    color: var(--fg-2);
}

.comment-edited { color: var(--fg-3); }

.comment-body {
    font-size: 14px;
    line-height: 20px;
    overflow-wrap: anywhere;
}

.comment-actions {
    display: flex;
    align-items: center;
    gap: 0.15rem;
    margin-left: auto;
    align-self: center;
}

/* Quiet until hovered or focused, but never display:none, which would take them
   off the keyboard and touch. */
.comment-action {
    border: none;
    background: transparent;
    color: var(--fg-3);
    font: inherit;
    font-family: var(--font-body);
    font-size: 12px;
    font-weight: 500;
    padding: 0.15rem 0.4rem;
    border-radius: var(--radius-btn);
    cursor: pointer;
    opacity: 0;
    transition: opacity var(--duration) var(--ease), color var(--duration) var(--ease);
}

.comment:hover .comment-action,
.comment-action:focus-visible { opacity: 1; }

.comment-action:hover { color: var(--fg); }
.comment-action-danger:hover { color: var(--danger); }

@media (hover: none) {
    /* No hover to reveal them with. */
    .comment-action { opacity: 1; }
}

.comment-edit {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-top: 4px;
}

/* Fields are wells, as the title's and the date's are. */
.comment-edit textarea,
.comment-form textarea {
    min-height: 62px;
    border: none;
    border-radius: var(--radius-btn);
    padding: 9px 12px;
    background: var(--fill-1);
    box-shadow: var(--shadow-recessed);
    font: inherit;
    font-family: var(--font-body);
    font-size: 14px;
    line-height: 20px;
    color: var(--fg);
    resize: vertical;
    transition: box-shadow var(--duration) var(--ease), background var(--duration) var(--ease);
}

.comment-form textarea::placeholder { color: var(--fg-3); }

.comment-edit-actions,
.comment-form-actions {
    display: flex;
    align-items: center;
    gap: 4px;
}

.comment-edit-actions .attach-button,
.comment-form-actions .attach-button { margin-right: auto; }

.comment-form {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 10px;
}

/* ── The card's foot: delete, and what saving is doing ── */

.card-delete {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    min-height: 46px;
    padding: 0 16px;
    border: none;
    border-radius: var(--radius-sm);
    background: var(--bg-group);
    box-shadow: var(--shadow-sm);
    font: inherit;
    font-family: var(--font-body);
    font-size: 14px;
    font-weight: 500;
    color: var(--danger);
    cursor: pointer;
    transition: background var(--duration) var(--ease);
}

.card-delete:hover:not(:disabled) { background: color-mix(in oklab, var(--danger) 7%, var(--bg-group)); }

.card-delete:disabled { opacity: 0.4; cursor: not-allowed; }

/* A dialog's footnote; the feedback dialog's too. */
.card-note,
.feedback-note {
    margin: 0;
    font-size: 12px;
    font-weight: 500;
    line-height: 16px;
    color: var(--fg-2);
}

.modal-saved:not(:empty)::before { content: " · "; }

/* ── The auth panel: sign-in, sign-up, requests, access denied ── */

/* One soft glow of the accent behind the panel: the draft's blurred ellipse,
   a little above the middle, drawn as a gradient rather than a blur filter. */
.login-page {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    min-height: 100dvh;
    padding: 2rem;
    background:
        radial-gradient(560px 420px at 50% calc(50% - 80px), var(--brand-glow) 50%,
                        color-mix(in oklab, var(--brand) 6%, transparent) 76%, transparent),
        var(--bg-0);
}

.login-card {
    display: flex;
    flex-direction: column;
    gap: 20px;
    width: min(420px, 100%);
    padding: 32px;
    border-radius: var(--radius-lg);
    background: var(--bg-panel);
    box-shadow: var(--shadow-lg);
    text-align: center;
}

.login-card-wide { width: min(460px, 100%); }

.login-card p { margin: 0; }

/* The mark (or a glyph saying what happened), the page's title, what it is for. */
.login-head {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
}

.login-head h1 {
    margin: 0;
    font-size: 22px;
    line-height: 28px;
    font-weight: 700;
    letter-spacing: -0.012em;
    color: var(--fg);
}

.login-head p {
    font-size: 13px;
    line-height: 18px;
    color: var(--fg-2);
}

.login-glyph {
    display: grid;
    place-items: center;
    width: 56px;
    height: 56px;
    border-radius: 50%;
    background: var(--brand-glow);
    color: var(--brand-ink);
}

.login-glyph svg {
    width: 28px;
    height: 28px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.login-card form {
    display: flex;
    flex-direction: column;
    gap: 16px;
    text-align: left;
}

.login-card .field,
.atproto-login-field {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.login-card form label {
    font-size: 12px;
    line-height: 16px;
    font-weight: 500;
    color: var(--fg-2);
}

/* A well recessed into the panel; focus lifts it to the surface and rings it. */
.login-card form input:not([type="checkbox"]):not([type="radio"]),
.login-card form textarea {
    width: 100%;
    min-height: 36px;
    border: 2px solid transparent;
    border-radius: var(--radius-btn);
    padding: 6px 10px;
    font: inherit;
    font-size: 14px;
    line-height: 20px;
    background: var(--fill-1);
    box-shadow: var(--shadow-recessed);
    color: var(--fg);
    /* Named properties, not `all`: password managers write inline styles here
       (Proton Pass pads for its icon), and `all` animated every write. */
    transition: border-color var(--duration) var(--ease),
                box-shadow var(--duration) var(--ease),
                background var(--duration) var(--ease);
}

.login-card form input:not([type="checkbox"]):not([type="radio"]):focus,
.login-card form textarea:focus {
    outline: none;
    border-color: var(--brand-ink);
    box-shadow: 0 0 0 5.5px var(--brand-glow);
    background: var(--bg-1);
}

.login-card form input::placeholder,
.login-card form textarea::placeholder {
    color: var(--fg-3);
}

/* The placeholder cycles examples (js/handle-placeholder.js) through a ghost
   over the field, since only the domain rolls. The real placeholder stays
   underneath, transparent, for assistive tech. */
.handle-ghost-field {
    position: relative;
}

.login-card form input.has-handle-ghost::placeholder {
    color: transparent;
}

.handle-ghost {
    position: absolute;
    display: flex;
    align-items: center;
    overflow: hidden;
    pointer-events: none;
    white-space: nowrap;
    color: var(--fg-3);
}

.handle-ghost.is-hidden {
    display: none;
}

.handle-ghost-reel {
    position: relative;
}

.handle-ghost-domain {
    display: inline-block;
    /* The incoming domain waits out the fade. 420ms must match SWAP_MS in
       js/handle-placeholder.js. */
    transition: transform 420ms var(--ease), opacity 280ms var(--ease) 140ms;
}

.handle-ghost-domain.is-entering {
    transform: translateY(0.75em);
    opacity: 0;
}

.handle-ghost-domain.is-leaving {
    position: absolute;
    left: 0;
    top: 0;
    transform: translateY(-0.75em);
    opacity: 0;
    transition: transform 420ms var(--ease), opacity 200ms var(--ease);
}

@media (prefers-reduced-motion: reduce) {
    .handle-ghost-domain {
        transition: none;
    }
}

/* Grows with what is typed, where the browser can, instead of a grip. */
.login-card form textarea {
    resize: none;
    min-height: 72px;
    max-height: 12rem;
    field-sizing: content;
}

.login-card form small {
    font-size: 12px;
    line-height: 16px;
    font-weight: 500;
    color: var(--fg-3);
}

.login-card form input[type="checkbox"] {
    margin-right: 0.3rem;
}

/* ── Home ── */

/* The canvas's Home (v2, Desktop · Home and Phone · Home): a large title, the
   "for you" cards, then the boards, as decks on a desk and one inset list on a
   phone. A deck decrypts its board's top three cards and nothing else. */
.home {
    padding: 0 1.25rem 2.5rem;
}

/* The phone block restyles it as the large title beside the account. */
.home-head {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 18px 1.25rem 16px;
}

.home-head-title {
    margin: 0;
    font-size: 30px;
    line-height: 36px;
    font-weight: 700;
    letter-spacing: -0.022em;
    color: var(--fg);
}

.home-head-date {
    margin: 2px 0 0;
    font-size: 13px;
    line-height: 18px;
    color: var(--fg-2);
}

.home-new { margin-left: auto; }

/* Home's glyphs (Home.Glyph): the canvas's 20-unit set, sized where they sit. */
.home-glyph {
    flex: none;
    width: 16px;
    height: 16px;
}

/* Under the title, an empty page is the empty state alone. */
.home-head ~ .board-loading,
.home-head ~ .empty-state { min-height: calc(100vh - var(--header-h) - 90px); }

.home-section-head {
    display: flex;
    align-items: baseline;
    padding: 4px 2px 12px;
}

.home-title {
    margin: 0;
    font-size: 22px;
    line-height: 28px;
    font-weight: 700;
    letter-spacing: -0.012em;
}

.home-count {
    margin-left: 3px;
    font-size: 17px;
    line-height: 22px;
    font-weight: 600;
    color: var(--fg-2);
}

/* A phone's: a pointer reaches a tile's controls by hovering it. */
.home-edit { display: none; }

/* auto-fill, not auto-fit: auto-fit stretches two boards into banners. */
.home-tiles {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(238px, 1fr));
    gap: 14px;
}

.board-tile {
    position: relative;
    display: flex;
    flex-direction: column;
    min-height: 216px;
    padding: 16px;
    border-radius: var(--radius);
    background: var(--bg-1);
    box-shadow: var(--shadow-sm);
    transition: box-shadow var(--duration) var(--ease), transform var(--duration) var(--ease), scale 160ms var(--ease-out), color var(--duration) var(--ease);
}

/* A board is a deck of its top cards on its own colour (the Stacks sketch). The
   colour is the re-lit --brand, so white words on it pass AA from top to foot;
   the board's mark would only repeat it. */
@media (min-width: 600px) {
    .board-tile[data-board-key] {
        padding: 0 16px 14px;
        background: linear-gradient(172deg, light-dark(color-mix(in oklab, var(--brand), white 10%), var(--brand)) 0%, var(--brand) 42%,
            light-dark(color-mix(in oklab, var(--brand), black 30%), color-mix(in oklab, var(--brand), black 38%)) 100%);
        box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.24), 0 0 0 0.5px color-mix(in oklab, var(--brand), black 35%),
            0 12px 26px -12px color-mix(in oklab, var(--brand) 70%, transparent), var(--shadow-xs);
        color: var(--brand-contrast);
    }

    .board-tile[data-board-key] .board-tile-link > .board-icon { display: none; }

    .board-tile[data-board-key] .board-tile-name { padding-right: 60px; }

    .board-tile[data-board-key] .board-tile-count,
    .board-tile[data-board-key] .board-tile-foot { color: rgb(255 255 255 / 0.82); }

    .board-tile[data-board-key] .assignee-avatar { border-color: color-mix(in oklab, var(--brand), black 26%); }

    .board-tile[data-board-key]:active { scale: 0.975; }
}

@media (hover: hover) {
    .board-tile:not(.board-tile-new, .board-tile-form):hover {
        box-shadow: var(--shadow-md);
        transform: translateY(-2px);
    }
}

@media (min-width: 600px) and (hover: hover) {
    .board-tile[data-board-key]:hover {
        box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.24), 0 0 0 0.5px color-mix(in oklab, var(--brand), black 35%),
            0 18px 32px -14px color-mix(in oklab, var(--brand) 80%, transparent), var(--shadow-sm);
        transform: translateY(-3px);
    }
}

/* The deck: up to three cards, the board's top one in front, each a little askew.
   nth-last-child, so the top card always lies lowest however many there are. */
.board-tile-deck {
    position: relative;
    flex: none;
    height: 124px;
    margin: 0 -16px 2px;
}

.board-tile-mini {
    position: absolute;
    left: 12%;
    width: 76%;
    height: 54px;
    padding: 8px 11px;
    border-radius: 12px;
    /* A wash of its first tag at the top, as a card's edge catches its colour. */
    background: linear-gradient(color-mix(in oklab, var(--tag-color, var(--bg-1)) 12%, var(--bg-1)), var(--bg-1) 28px);
    box-shadow: inset 0 0.5px 0 var(--sh-highlight), 0 0 0 0.5px rgb(0 0 0 / 0.1), 0 2px 4px rgb(0 0 0 / 0.1), 0 8px 16px -8px rgb(0 0 0 / 0.3);
    color: var(--fg);
    transition: rotate 520ms var(--ease-spring), translate 520ms var(--ease-spring);
}

.board-tile-mini:nth-last-child(1) { top: 62px; rotate: -0.6deg; }
.board-tile-mini:nth-last-child(2) { top: 38px; rotate: 2.4deg; translate: 7px 0; }
.board-tile-mini:nth-last-child(3) { top: 14px; rotate: -3deg; translate: -6px 0; }

/* An empty board is an empty place where its top card would lie. */
.board-tile-deck:not(:has(.board-tile-mini))::after {
    content: "";
    position: absolute;
    top: 62px;
    left: 12%;
    width: 76%;
    height: 54px;
    border: 1.5px dashed rgb(255 255 255 / 0.4);
    border-radius: 12px;
}

/* Under a pointer, or the keyboard, the deck fans out on a spring. */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
    .board-tile:hover .board-tile-mini:nth-last-child(1),
    .board-tile:has(.board-tile-link:focus-visible) .board-tile-mini:nth-last-child(1) { rotate: -1.2deg; translate: 0 5px; }

    .board-tile:hover .board-tile-mini:nth-last-child(2),
    .board-tile:has(.board-tile-link:focus-visible) .board-tile-mini:nth-last-child(2) { rotate: 5.5deg; translate: 18px -1px; }

    .board-tile:hover .board-tile-mini:nth-last-child(3),
    .board-tile:has(.board-tile-link:focus-visible) .board-tile-mini:nth-last-child(3) { rotate: -7deg; translate: -18px -3px; }
}

.board-tile-mini-title {
    display: block;
    font-size: 12.5px;
    line-height: 16px;
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.board-tile-mini-meta {
    display: flex;
    align-items: center;
    gap: 5px;
    margin-top: 6px;
}

.board-tile-mini .card-due {
    gap: 3px;
    padding: 1px 6px 1px 4px;
    font-size: 11px;
    line-height: 14px;
}

.board-tile-mini .card-due::before {
    width: 11px;
    height: 11px;
}

.board-tile-mini-tag {
    flex: none;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--tag-color);
}

/* The link covers the tile from underneath and the controls sit above it: an
   anchor can't contain the settings button. */
.board-tile-link {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-width: 0;
    color: inherit;
    text-decoration: none;
}

.board-tile-link::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
}

/* A board's mark: its first letter on its own colour, re-lit (--brand), raised. */
.board-icon {
    flex: none;
    display: grid;
    place-items: center;
    width: 38px;
    height: 38px;
    border-radius: 11px;
    background: linear-gradient(rgb(255 255 255 / 0.18), rgb(255 255 255 / 0)), var(--brand);
    box-shadow: var(--shadow-sm);
    color: var(--brand-contrast);
    font-size: 19px;
    font-weight: 700;
    line-height: 1;
}

.board-tile-link > .board-icon { margin-bottom: 15px; }

.board-tile-text {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    margin-top: auto;
}

.board-tile-name {
    font-size: 17px;
    line-height: 22px;
    font-weight: 600;
    letter-spacing: -0.008em;
    /* Cut, not wrapped, so one long name can't set its row's height. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.board-tile-count {
    font-size: 13px;
    line-height: 18px;
    color: var(--fg-2);
}

/* Above the link's cover, so a face's name still shows on hover. */
.board-tile-foot {
    position: relative;
    z-index: 1;
    display: flex;
    align-items: center;
    gap: 6px;
    min-height: 22px;
    margin-top: 8px;
    font-size: 12px;
    line-height: 16px;
    font-weight: 500;
    color: var(--fg-2);
}

.board-tile-foot .home-glyph {
    width: 15px;
    height: 15px;
}

.board-tile-foot .card-assignees { margin-left: auto; }

.board-tile-chevron { display: none; }

/* One positioned row at the end of the name's line, the pin last, so the cog coming
   and going never moves it. The lines below the name are one line each, so the
   offset is their sum: foot 22, gap 8, counts 18, gap 2, less half the overhang. */
.board-tile-controls {
    position: absolute;
    bottom: 61px;
    right: 10px;
    display: flex;
    flex-direction: row-reverse;
    gap: 2px;
}

/* Shown on hover and for the keyboard; a pinned tile's pin stays as its mark. */
.board-tile-pin,
.board-tile-settings {
    display: grid;
    place-items: center;
    width: 28px;
    height: 28px;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: none;
    color: var(--fg-2);
    cursor: pointer;
    opacity: 0;
    transition: opacity var(--duration) var(--ease), background var(--duration) var(--ease), color var(--duration) var(--ease);
}

.board-tile:hover .board-tile-pin,
.board-tile:hover .board-tile-settings,
.board-tile-pin:focus-visible,
.board-tile-settings:focus-visible,
.board-tile-pin-on {
    opacity: 1;
}

.board-tile-pin:hover,
.board-tile-settings:hover {
    background: var(--fill-1);
    color: var(--brand-ink);
}

@media (min-width: 600px) {
    .board-tile-pin,
    .board-tile-settings { color: rgb(255 255 255 / 0.85); }

    .board-tile-pin:hover,
    .board-tile-settings:hover {
        background: rgb(255 255 255 / 0.18);
        color: var(--brand-contrast);
    }
}

/* Without hover, both are always there. */
@media (hover: none) {
    .board-tile-pin,
    .board-tile-settings { opacity: 1; }
}

/* The place for a new board is recessed: it holds, and acts only when pressed. */
.board-tile-new {
    flex-direction: row;
    align-items: center;
    justify-content: center;
    gap: 8px;
    border: none;
    background: var(--bg-2);
    box-shadow: var(--shadow-recessed);
    color: var(--fg-2);
    font: inherit;
    font-size: 14px;
    line-height: 20px;
    font-weight: 600;
    cursor: pointer;
}

.board-tile-new:hover { color: var(--brand-ink); }

.board-tile-plus { display: flex; }

.board-tile-plus .home-glyph {
    width: 22px;
    height: 22px;
}

.board-tile-plus .home-glyph-plus { display: none; }

/* The settings pages' field and buttons, so a name is typed alike everywhere. */
.board-tile-form {
    gap: 10px;
    justify-content: center;
}

/* Not the settings row's stretch: the form is a column. */
.board-tile-form > .board-tile-input { flex: none; }

.board-tile-hint {
    margin: 0;
    font-size: 12px;
    line-height: 16px;
    color: var(--fg-2);
}

.board-tile-actions {
    display: flex;
    gap: 8px;
}

/* The canvas's faces (Faces): 22px, three then +N, initials on a soft colour of
   the member's own. The 2px ring of the surface's colour is outside the 22px, so
   it takes no room: the margins give it back. */
.home .card-assignees {
    display: flex;
    flex: none;
    margin-block: -2px;
    margin-right: -2px;
    isolation: isolate;
}

.home .card-assignees .assignee-avatar {
    width: 26px;
    height: 26px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: -0.03em;
    box-shadow: none;
}

.home .card-assignees .assignee-avatar:not(:first-child) { margin-left: -8px; }

.home .card-assignees .assignee-avatar:nth-child(1) { z-index: 4; }
.home .card-assignees .assignee-avatar:nth-child(2) { z-index: 3; }
.home .card-assignees .assignee-avatar:nth-child(3) { z-index: 2; }

.home .assignee-avatar[style*="--h"] {
    background: light-dark(oklch(0.88 0.06 var(--h)), oklch(0.42 0.06 var(--h)));
    color: light-dark(oklch(0.4 0.11 var(--h)), oklch(0.9 0.1 var(--h)));
}

.home .assignee-overflow {
    background: color-mix(in oklab, var(--fg) 10%, var(--bg-1));
    color: var(--fg-2);
}

.home-empty-new {
    margin-top: 1.5rem;
}

.home-empty-note {
    margin: 1.75rem 0 0;
    font-size: 13px;
    line-height: 18px;
    color: var(--fg-3);
}

/* Beside the sidebar: its gap on the left, the page's edge on the right; titles
   sit 2px in from the tiles. */
@media (min-width: 960px) {
    .home { padding-inline: 14px 22px; }

    .home-head { padding-inline: 16px 24px; }
}

/* On a phone the boards are one inset grouped list (Phone · Home): a row each,
   mark, name over counts, the pin and a chevron, and New board as the last row.
   The bar's inset is already on the body (body:has(.board-header)). */
@media (max-width: 599px) {
    .home { padding: 0 16px 1.5rem; }

    .home-new { display: none; }

    /* A group's label: no counts, and the boards' carries Edit. */
    .home-section-head {
        align-items: center;
        padding: 20px 4px 8px;
    }

    .home-title {
        font-size: 16px;
        line-height: 21px;
        font-weight: 600;
        letter-spacing: -0.01em;
    }

    .home-count { display: none; }

    /* Thumb-sized through its padding, the label where the canvas has it. */
    .home-edit {
        display: block;
        margin: -10px -10px -10px auto;
        padding: 10px;
        border: none;
        background: none;
        font: inherit;
        font-size: 13px;
        line-height: 18px;
        font-weight: 500;
        color: var(--brand-ink);
        cursor: pointer;
    }

    .home-tiles {
        display: block;
        border-radius: var(--radius-sm);
        background: var(--bg-group);
        box-shadow: var(--shadow-sm);
    }

    .board-tile {
        min-height: 0;
        padding: 8px 14px;
        border-radius: 0;
        background: none;
        box-shadow: none;
        transform: none;
    }

    /* Hairlines between rows, inset to where the words start. */
    .board-tile + .board-tile::before {
        content: "";
        position: absolute;
        top: 0;
        left: 58px;
        right: 0;
        border-top: 1px solid var(--border-1);
    }

    .board-tile-link {
        flex-direction: row;
        align-items: center;
        gap: 12px;
    }

    .board-tile-link > .board-icon {
        width: 32px;
        height: 32px;
        margin: 0;
        border-radius: 10px;
        font-size: 16px;
    }

    .board-tile-text {
        flex: 1;
        gap: 0;
        margin-top: 0;
    }

    .board-tile-name {
        font-size: 16.5px;
        line-height: 21px;
        letter-spacing: -0.01em;
    }

    .board-tile-count { font-weight: 500; }

    .board-tile-deck,
    .board-tile-foot { display: none; }

    .board-tile-chevron {
        display: block;
        width: 18px;
        height: 18px;
        color: var(--fg-2);
    }

    /* At rest a pinned row wears its pin as a mark before the chevron; Edit turns
       pin and cog into buttons where the chevron was. */
    .board-tile-controls {
        top: 50%;
        bottom: auto;
        right: 44px;
        translate: 0 -50%;
    }

    .board-tile-pin .home-glyph {
        width: 15px;
        height: 15px;
    }

    .home-tiles:not(.home-editing) .board-tile-settings,
    .home-tiles:not(.home-editing) .board-tile-pin:not(.board-tile-pin-on) { display: none; }

    .home-tiles:not(.home-editing) .board-tile-pin-on {
        width: 15px;
        height: 15px;
        pointer-events: none;
    }

    .home-editing .board-tile-chevron { visibility: hidden; }

    .home-editing .board-tile-controls {
        right: 8px;
        gap: 6px;
    }

    .home-editing .board-tile-pin,
    .home-editing .board-tile-settings {
        width: 40px;
        height: 40px;
        background: var(--fill-1);
    }

    .home-editing .board-tile-pin-on {
        background: var(--brand-surface);
        color: var(--brand-ink);
    }

    /* Clear of the buttons, past the chevron's place they take. */
    .home-editing .board-tile-text { padding-right: 10px; }

    .home-editing .board-tile:has(.board-tile-settings) .board-tile-text { padding-right: 56px; }

    .board-tile-new {
        width: 100%;
        justify-content: flex-start;
        gap: 12px;
        min-height: 44px;
        color: var(--brand-ink);
        font-size: 17px;
        line-height: 22px;
    }

    .board-tile-new:hover { color: var(--brand-ink); }

    .board-tile-plus .home-glyph-plus-circle { display: none; }

    .board-tile-plus .home-glyph-plus { display: block; }

    .board-tile-form { padding: 12px 14px; }

    .board-tile-input { font-size: 16px; }
}

/* ── Home: recently deleted ── */

/* A grouped list in the page's column, not the tile grid: these aren't boards
   to open. */
.home-deleted-title {
    margin: 0;
    padding: 0 14px 8px;
    font-size: 12px;
    line-height: 16px;
    font-weight: 500;
    color: var(--fg-2);
}

.home-deleted-list {
    max-width: 560px;
    margin: 0;
    padding: 0;
    list-style: none;
    border-radius: var(--radius-sm);
    background: var(--bg-group);
    box-shadow: var(--shadow-sm);
}

.home-deleted-row {
    position: relative;
    display: flex;
    align-items: center;
    gap: 12px;
    min-height: 56px;
    padding: 10px 14px;
}

.home-deleted-row + .home-deleted-row::before {
    content: "";
    position: absolute;
    top: 0;
    left: 58px;
    right: 0;
    border-top: 1px solid var(--border-1);
}

/* Faded: a board on its way out. */
.home-deleted-row .board-icon {
    width: 32px;
    height: 32px;
    border-radius: 10px;
    font-size: 16px;
    opacity: 0.5;
}

.home-deleted-text {
    display: flex;
    flex-direction: column;
    gap: 1px;
    flex: 1 1 auto;
    min-width: 0;
}

.home-deleted-name {
    font-size: 14px;
    line-height: 19px;
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.home-deleted-until {
    font-size: 12px;
    line-height: 16px;
    font-weight: 500;
    color: var(--fg-2);
}

.home-deleted-restore { flex: none; }

/* On a phone its label is the page's other group labels. */
@media (max-width: 599px) {
    .home-deleted-title {
        padding: 0 4px 8px;
        font-size: 16px;
        line-height: 21px;
        font-weight: 600;
        letter-spacing: -0.01em;
        color: var(--fg);
    }

    .home-deleted-list { max-width: none; }
}

/* ── Home: the "for you" cards ── */

/* Capped at six: the one place on the page that decrypts a card's words (two
   passes, ForYouService). */
.home-foryou {
    margin-bottom: 22px;
    container-type: inline-size;
}

/* Three across in two rows at most on a desk, two across where the room is less
   (the page's, beside the sidebar or not): six cards of two lines each fill their
   place instead of floating in it. */
.foryou-strip {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 12px;
}

@container (max-width: 899px) {
    .foryou-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* A card's two lines: what and when, then where and who, the right-hand column
   on the lines' own grid so the chip and the faces sit level with them. */
.foryou-card {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-rows: auto auto;
    column-gap: 10px;
    row-gap: 6px;
    align-items: center;
    min-width: 0;
    padding: 10px 12px 10px 14px;
    border-radius: var(--radius-sm);
    background: var(--bg-1);
    box-shadow: var(--shadow-sm);
    color: inherit;
    text-decoration: none;
    transition: box-shadow var(--duration) var(--ease), transform var(--duration) var(--ease);
}

@media (hover: hover) {
    .foryou-card:hover {
        box-shadow: var(--shadow-md);
        transform: translateY(-2px);
    }
}

.foryou-title {
    font-size: 14px;
    line-height: 19px;
    font-weight: 600;
    letter-spacing: -0.006em;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.foryou-from {
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
    font-size: 12px;
    line-height: 16px;
    font-weight: 500;
    color: var(--fg-2);
    white-space: nowrap;
}

.foryou-from .board-icon {
    width: 16px;
    height: 16px;
    border-radius: 5px;
    font-size: 9px;
}

.foryou-board {
    flex: none;
    max-width: 60%;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* As search says where a hit lives. */
.foryou-column {
    margin-left: -2px;
    overflow: hidden;
    text-overflow: ellipsis;
}

.foryou-column::before { content: "› "; }

.foryou-meta {
    grid-column: 2;
    grid-row: 1 / 3;
    display: grid;
    grid-template-rows: subgrid;
    justify-items: end;
    row-gap: 6px;
}

.foryou-meta .card-due { grid-row: 1; }

.foryou-meta .card-assignees { grid-row: 2; }

/* On a phone one row runs edge to edge under the page's inset, and scrolls; snap
   points land a thumb on a card. Padded on every side for the lift and shadow,
   since overflow-x clips overflow-y too; the negative margins give it back. */
@media (max-width: 599px) {
    .home-foryou { margin-bottom: 0; }

    .foryou-strip {
        grid-template-columns: none;
        grid-auto-flow: column;
        grid-auto-columns: 264px;
        gap: 10px;
        overflow-x: auto;
        scroll-snap-type: x proximity;
        padding: 1rem 1rem 1.75rem;
        margin: -1rem -1rem -1.75rem;
        scroll-padding-inline: 1rem;
        /* Shown only while wanted; `thin` keeps the room, so it never moves the row. */
        scrollbar-width: thin;
        scrollbar-color: transparent transparent;
    }

    .foryou-strip:hover,
    .foryou-strip:focus-within { scrollbar-color: var(--border-3) transparent; }

    .foryou-card { scroll-snap-align: start; }
}

/* ── Pages of grouped lists: board settings, members, the profile ── */

/* The v2 canvas's inset grouped lists (docs/ui.md, Settings, members and your
   profile): a large title over groups, each a small label, a rounded box of rows
   split by hairlines, and a footnote. One system for every page that sets things;
   a phone reshapes the same markup at the end of this section. */

.page {
    display: flex;
    flex-direction: column;
    gap: 24px;
    max-width: calc(720px + 2 * 24px);
    margin: 0 auto;
    padding: 48px 24px;
}

.page-head {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
}

.page-crumb {
    align-self: flex-start;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 13px;
    line-height: 18px;
    color: var(--fg-2);
    text-decoration: none;
}

.page-crumb:hover { color: var(--fg); }

.page-title {
    margin: 0;
    font-family: var(--font-display);
    font-size: 30px;
    line-height: 36px;
    font-weight: 700;
    letter-spacing: -0.022em;
    color: var(--fg);
    overflow-wrap: anywhere;
}

.page-sub {
    margin: 0;
    font-size: 13px;
    line-height: 18px;
    color: var(--fg-2);
}

/* A board's mark: its first letter on its colour, lit from above. */
.board-mark {
    flex: none;
    display: grid;
    place-items: center;
    width: 32px;
    height: 32px;
    border-radius: 10px;
    background: linear-gradient(rgb(255 255 255 / 0.18), transparent), var(--brand);
    color: var(--brand-contrast);
    font-size: 16px;
    font-weight: 700;
    line-height: 1;
    box-shadow: var(--shadow-sm);
}

.board-mark-xs {
    width: 16px;
    height: 16px;
    border-radius: 5px;
    font-size: 9px;
}

/* The settings tabs: a segmented control, the chosen one a raised thumb. */
.settings-tabs {
    align-self: flex-start;
    display: inline-flex;
    flex-wrap: wrap;
    padding: 2px;
    border-radius: var(--radius-pill);
    background: var(--fill-1);
}

.settings-tab {
    display: inline-flex;
    align-items: center;
    height: 28px;
    padding: 0 14px;
    border-radius: var(--radius-pill);
    font-size: 13.5px;
    line-height: 16px;
    font-weight: 600;
    letter-spacing: -0.003em;
    color: var(--fg-2);
    text-decoration: none;
    transition: color var(--duration) var(--ease), background var(--duration) var(--ease);
}

.settings-tab:hover:not(.settings-tab-active) { color: var(--fg); }

.settings-tab:focus-visible {
    outline: 2px solid var(--brand-ink);
    outline-offset: 2px;
}

.settings-tab-active {
    background: var(--bg-thumb);
    color: var(--fg);
    box-shadow: var(--shadow-sm);
}

/* — The group: label, box of rows, footnote — */

.group {
    display: flex;
    flex-direction: column;
    min-width: 0;
    margin: 0;
}

.group-label {
    display: flex;
    align-items: center;
    gap: 6px;
    margin: 0;
    padding: 0 14px 8px;
    font-size: 12px;
    line-height: 16px;
    font-weight: 500;
    color: var(--fg-2);
}

.group-link {
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    color: var(--brand-ink);
    text-decoration: none;
    cursor: pointer;
}

.group-link:hover { text-decoration: underline; }

.group-box {
    margin: 0;
    border-radius: var(--radius-sm);
    background: var(--bg-group);
    box-shadow: var(--shadow-sm);
}

/* Corners for a row that lights on hover; the box cannot clip, or a row's menu would be cut. */
.group-box > :first-child { border-top-left-radius: inherit; border-top-right-radius: inherit; }
.group-box > :last-child { border-bottom-left-radius: inherit; border-bottom-right-radius: inherit; }

.group-row {
    position: relative;
    display: flex;
    align-items: center;
    gap: 12px;
    min-height: 56px;
    margin: 0;
    padding: 10px 14px;
    font-size: 14px;
    line-height: 20px;
    color: var(--fg);
    text-decoration: none;
}

label.group-row,
a.group-row { cursor: pointer; }

button.group-row {
    width: 100%;
    border: 0;
    background: none;
    font: inherit;
    text-align: start;
    cursor: pointer;
}

.group-row + .group-row { border-top: 1px solid var(--border-2); }

a.group-row:hover,
button.group-row:hover:not(:disabled) { background: var(--fill-1); }

.group-action .group-title { color: var(--brand-ink); }

.group-row-off {
    opacity: 0.55;
    cursor: not-allowed;
}

.group-note {
    margin: 0;
    padding: 8px 14px 0;
    font-size: 12px;
    line-height: 16px;
    font-weight: 500;
    color: var(--fg-2);
}

.group-text {
    display: flex;
    flex-direction: column;
    gap: 1px;
    flex: 1 1 auto;
    min-width: 0;
}

.group-title {
    font-size: 14px;
    line-height: 19px;
    font-weight: 600;
    letter-spacing: -0.006em;
    color: var(--fg);
    overflow-wrap: anywhere;
}

.group-sub {
    font-size: 12px;
    line-height: 16px;
    font-weight: 500;
    color: var(--fg-2);
    overflow-wrap: anywhere;
}

.group-danger { color: var(--danger); }

.group-error { color: var(--danger); }

.group-empty { color: var(--fg-3); }

.group-count,
.group-meta,
.group-value {
    flex: none;
    margin-left: auto;
    font-size: 12px;
    line-height: 16px;
    font-weight: 500;
    color: var(--fg-2);
    white-space: nowrap;
}

.group-icon {
    flex: none;
    color: var(--fg-2);
}

.group-chevron {
    flex: none;
    color: var(--fg-3);
}

/* A round mark before a row's words: tinted, or red for what deletes. */
.group-glyph {
    flex: none;
    display: grid;
    place-items: center;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: var(--brand-glow);
    color: var(--fg);
}

.group-glyph-danger {
    width: 36px;
    height: 36px;
    background: var(--danger-bg);
    color: var(--danger);
}

/* The phone's way into the tabs, and its add row's mark: phone only (below). */
.settings-nav,
.group-add-icon,
.only-phone { display: none; }

/* — Controls inside a row — */

/* A well recessed into the box; an inline edit is the same field, bare until used.
   Focus lifts either onto the surface and rings it in the accent. */
.group-field,
.group-edit {
    flex: 1 1 auto;
    min-width: 0;
    height: 36px;
    padding: 0 12px;
    border: 0;
    border-radius: var(--radius-btn);
    background: var(--fill-1);
    box-shadow: var(--shadow-recessed);
    font: inherit;
    font-size: 14px;
    line-height: 20px;
    color: var(--fg);
    transition: background var(--duration) var(--ease), box-shadow var(--duration) var(--ease);
}

.group-edit {
    flex: 0 1 240px;
    background: transparent;
    box-shadow: none;
}

.group-edit:hover { background: var(--fill-1); }

.group-field::placeholder,
.group-edit::placeholder {
    color: var(--fg-3);
    opacity: 1;
}

.group-field:focus,
.group-edit:focus {
    outline: none;
    background: var(--bg-1);
    box-shadow: inset 0 0 0 2px var(--brand-ink), 0 0 0 5.5px var(--brand-glow);
}

.group-search {
    padding-left: 36px;
    -webkit-appearance: none;
    appearance: none;
}

.group-search::-webkit-search-cancel-button { display: none; }

.group-search-icon {
    position: absolute;
    left: 26px;
    color: var(--fg-3);
    pointer-events: none;
}

.group-row input[type="checkbox"]:not(.switch),
.group-row input[type="radio"] {
    width: 18px;
    height: 18px;
    margin: 0;
}

/* A checkbox with role="switch": the knob travels on the spring. */
input.switch {
    -webkit-appearance: none;
    appearance: none;
    position: relative;
    flex: none;
    width: 44px;
    height: 27px;
    margin: 0 0 0 auto;
    border-radius: var(--radius-pill);
    background: var(--fill-3);
    cursor: pointer;
    transition: background var(--duration) var(--ease);
}

input.switch::before {
    content: "";
    position: absolute;
    top: 2px;
    left: 2px;
    width: 23px;
    height: 23px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 3px rgb(0 0 0 / 0.2);
    transition: translate 320ms var(--ease-spring);
}

@media (prefers-reduced-motion: reduce) {
    input.switch::before { transition: none; }
}

input.switch:checked { background: var(--brand); }

input.switch:checked::before { translate: 17px 0; }

input.switch:disabled {
    opacity: 0.45;
    cursor: default;
}

input.switch:focus-visible {
    outline: 2px solid var(--brand-ink);
    outline-offset: 2px;
}

/* A colour, as a dot: the swatch fills the circle. */
input.swatch {
    -webkit-appearance: none;
    appearance: none;
    flex: none;
    width: 24px;
    height: 24px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: none;
    cursor: pointer;
}

input.swatch::-webkit-color-swatch-wrapper { padding: 0; }

input.swatch::-webkit-color-swatch {
    border: 0;
    border-radius: 50%;
    box-shadow: inset 0 0 0 1px var(--border-1);
}

input.swatch::-moz-color-swatch {
    border: 0;
    border-radius: 50%;
}

input.swatch:focus-visible {
    outline: 2px solid var(--brand-ink);
    outline-offset: 2px;
}

.settings-add input.swatch {
    width: 28px;
    height: 28px;
}

.chip {
    flex: none;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 8px;
    border-radius: var(--radius-pill);
    font-size: 11px;
    line-height: 14px;
    font-weight: 600;
    letter-spacing: 0;
    white-space: nowrap;
    vertical-align: 1px;
}

.chip-accent { background: var(--brand-glow); color: var(--brand-ink); }
.chip-muted { background: var(--fill-1); color: var(--fg-2); }
.chip-danger { background: var(--danger-bg); color: var(--danger); }

.icon-btn {
    flex: none;
    display: grid;
    place-items: center;
    width: 32px;
    height: 32px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: none;
    color: var(--fg);
    cursor: pointer;
    transition: background var(--duration) var(--ease);
}

.icon-btn:hover:not(:disabled) { background: var(--fill-1); }

.icon-btn:disabled {
    opacity: 0.3;
    cursor: default;
}

.icon-btn:focus-visible {
    outline: 2px solid var(--brand-ink);
    outline-offset: 1px;
}

/* A member's face: their picture, or initials on a soft colour from their id
   (MemberFace). The ring is the surface beneath, so overlaps stay legible. */
.face {
    flex: none;
    display: inline-grid;
    place-items: center;
    width: 28px;
    height: 28px;
    border: 2px solid var(--bg-group);
    border-radius: 50%;
    background: light-dark(oklch(0.88 0.06 var(--face-h, 280)), oklch(0.42 0.06 var(--face-h, 280)));
    color: light-dark(oklch(0.4 0.11 var(--face-h, 280)), oklch(0.88 0.11 var(--face-h, 280)));
    font-size: 11px;
    line-height: 1;
    font-weight: 600;
    letter-spacing: -0.03em;
    object-fit: cover;
}

.face-l {
    width: 36px;
    height: 36px;
    font-size: 13px;
}

/* A ⋯ menu on a row: the canvas's menu, over a catcher that closes it. */
/* Also held empty on a row without one, so every row's date lines up. */
.row-menu-anchor {
    position: relative;
    flex: none;
    width: 32px;
}

.row-menu-catcher {
    position: fixed;
    inset: 0;
    z-index: 40;
}

.row-menu {
    position: absolute;
    top: calc(100% + 4px);
    right: 0;
    z-index: 41;
    display: flex;
    flex-direction: column;
    min-width: 180px;
    padding: 6px;
    border-radius: 16px;
    background: var(--bg-1);
    box-shadow: var(--shadow-lg);
}

.row-menu-item {
    display: flex;
    align-items: center;
    height: 32px;
    padding: 0 10px;
    border: 0;
    border-radius: var(--radius-xs);
    background: none;
    font: inherit;
    font-size: 13.5px;
    font-weight: 500;
    color: var(--fg);
    text-align: start;
    white-space: nowrap;
    cursor: pointer;
}

.row-menu-item:hover:not(:disabled),
.row-menu-item:focus-visible {
    outline: none;
    background: var(--brand);
    color: var(--brand-contrast);
}

.row-menu-danger { color: var(--danger); }

.row-menu-danger:hover:not(:disabled),
.row-menu-danger:focus-visible {
    background: var(--danger);
    color: var(--danger-contrast);
}

/* — Settings: columns and tags — */

.settings-pos {
    flex: none;
    width: 14px;
    font-size: 12px;
    line-height: 16px;
    font-weight: 500;
    text-align: center;
    color: var(--fg-3);
}

/* Drawn by a mask, so a phone can swap the dots for its three lines. */
.admin-sortable-handle {
    flex: none;
    width: 16px;
    height: 16px;
    background: var(--fg-3);
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cg fill='black'%3E%3Ccircle cx='6' cy='4' r='1.5'/%3E%3Ccircle cx='11' cy='4' r='1.5'/%3E%3Ccircle cx='6' cy='9' r='1.5'/%3E%3Ccircle cx='11' cy='9' r='1.5'/%3E%3Ccircle cx='6' cy='14' r='1.5'/%3E%3Ccircle cx='11' cy='14' r='1.5'/%3E%3C/g%3E%3C/svg%3E") center / contain no-repeat;
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cg fill='black'%3E%3Ccircle cx='6' cy='4' r='1.5'/%3E%3Ccircle cx='11' cy='4' r='1.5'/%3E%3Ccircle cx='6' cy='9' r='1.5'/%3E%3Ccircle cx='11' cy='9' r='1.5'/%3E%3Ccircle cx='6' cy='14' r='1.5'/%3E%3Ccircle cx='11' cy='14' r='1.5'/%3E%3C/g%3E%3C/svg%3E") center / contain no-repeat;
    cursor: grab;
    touch-action: none;
}

/* The place a row will land, and the row in the air (column-sort-interop.js). */
.admin-sortable-ghost {
    background: var(--brand-glow);
}

.admin-sortable-ghost > * { visibility: hidden; }

.admin-sortable-drag {
    border-radius: var(--radius-sm);
    background: var(--bg-group);
    box-shadow: var(--shadow-xl);
    rotate: 0.6deg;
    opacity: 1 !important;
}

.settings-deck { padding-left: 6px; }

.settings-tag .tag-chip { margin-right: auto; }

/* — Access — */

.access-member-pick {
    display: flex;
    align-items: center;
    gap: 12px;
    flex: 1 1 auto;
    min-width: 0;
    cursor: pointer;
}

.access-member-admin {
    display: flex;
    align-items: center;
    gap: 12px;
    flex: none;
    margin-left: auto;
    font-size: 13px;
    line-height: 18px;
    color: var(--fg-2);
    cursor: pointer;
}

/* Open to everyone, the ticks are kept but decide nothing; who runs it still does. */
.access-members-moot .access-member-pick { opacity: 0.55; }

/* — Members — */

.member-row-banned .group-title { color: var(--fg-3); }

.member-banned { margin-left: 6px; }

.member-preview-note { white-space: normal; text-align: end; }

.member-ban-row .group-field { flex: 1 1 auto; }

.request-card {
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
    padding: 12px 14px;
}

.request-head {
    display: flex;
    align-items: center;
    gap: 12px;
    min-width: 0;
}

.request-head .group-text,
.invite-row .group-text { flex: 0 1 auto; }

.request-date { color: var(--fg-3); }

.request-body {
    display: flex;
    align-items: center;
    gap: 8px;
    padding-left: 40px;
}

.request-message {
    flex: 1 1 auto;
    min-width: 0;
    margin: 0;
    font-size: 14px;
    line-height: 20px;
    color: var(--fg-2);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.request-actions {
    display: flex;
    gap: 8px;
    flex: none;
    margin-left: auto;
}

.invite-row .invite-code {
    font-size: 14px;
    line-height: 19px;
    font-weight: 600;
    letter-spacing: 0.06em;
}

.invite-new .invite-code { color: var(--brand-ink); }

.invite-copy { margin-left: auto; }

/* — The board record (a super-admin's) — */

.board-record-facts dt {
    flex: 0 0 10rem;
    color: var(--fg-2);
}

.board-record-facts dd {
    margin: 0;
    min-width: 0;
    overflow-wrap: anywhere;
}

.board-record-admins {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem 0.75rem;
}

/* — Profile — */

.profile-account {
    gap: 14px;
    padding: 14px;
}

.profile-name {
    font-size: 17px;
    line-height: 22px;
    font-weight: 600;
    letter-spacing: -0.008em;
}

.profile-handle {
    font-size: 13px;
    line-height: 18px;
    color: var(--fg-2);
}

.profile-did {
    font-size: 11px;
    line-height: 14px;
    font-weight: 600;
    color: var(--fg-3);
    overflow-wrap: anywhere;
}

.profile-label {
    flex: none;
    width: 140px;
}

.profile-picture .group-meta { margin-left: 0; }

/* The file input lies over its button, transparent: the picker stays the browser's. */
.profile-choose { position: relative; overflow: hidden; }

.profile-file {
    position: absolute;
    inset: 0;
    opacity: 0;
    cursor: pointer;
}

.profile-file::file-selector-button { cursor: pointer; }

/* — A sub-page's bar on a phone (below) — */

.settings-phone-bar { display: none; }

/* — On a phone: the inset grouped list, drilled into — */

@media (max-width: 599px) {
    .page {
        gap: 0;
        padding: 0 16px 24px;
    }

    .page-head { padding: 10px 4px 0; }

    .page-title {
        font-size: 34px;
        line-height: 41px;
    }

    /* The board's own top, or a sub-page's bar, says what the page is. */
    .settings-page .page-head,
    .settings-tabs { display: none; }

    .only-wide { display: none; }
    .only-phone { display: revert; }

    .group-label {
        padding: 18px 16px 7px;
        font-size: 13px;
        line-height: 18px;
        letter-spacing: 0.03em;
        text-transform: uppercase;
    }

    .group-box { box-shadow: none; }

    .group-row {
        min-height: 48px;
        padding: 11px 16px;
        font-size: 17px;
        line-height: 22px;
    }

    .group-title {
        font-size: 17px;
        line-height: 22px;
        font-weight: 400;
        letter-spacing: 0;
    }

    .group-sub {
        font-size: 13px;
        line-height: 18px;
    }

    .group-note {
        padding: 7px 16px 0;
        font-size: 13px;
        line-height: 18px;
    }

    .group-count,
    .group-value {
        font-size: 17px;
        line-height: 22px;
        font-weight: 400;
    }

    .group-field,
    .group-edit { font-size: 17px; }

    .group-search-icon { left: 28px; }

    /* As wide as its words, so a Deck chip follows the name. */
    .group-edit {
        flex: 0 1 auto;
        field-sizing: content;
        min-width: 3ch;
        height: 26px;
        padding: 0;
        border-radius: 6px;
    }

    .group-edit:hover { background: none; }

    .group-edit:focus { padding: 0 6px; }

    /* General: name and pin share a group, the tabs are rows, Delete a red row. */
    .settings-name,
    .settings-pin,
    .settings-add,
    .settings-list { display: contents; }

    .settings-name > .group-label,
    .settings-pin > .group-label,
    .settings-pin .group-sub,
    .settings-delete-group > .group-label,
    .settings-delete-group .group-glyph,
    .settings-delete-group .group-sub { display: none; }

    .settings-name > .group-box {
        order: 1;
        margin-top: 18px;
        border-bottom-left-radius: 0;
        border-bottom-right-radius: 0;
    }

    .settings-name .group-field {
        height: 26px;
        padding: 0;
        background: none;
        box-shadow: none;
    }

    .settings-pin > .group-box {
        order: 2;
        border-top: 1px solid var(--border-2);
        border-top-left-radius: 0;
        border-top-right-radius: 0;
    }

    .settings-name > .group-note { order: 3; }

    .settings-nav {
        display: flex;
        order: 4;
    }

    .settings-nav .group-icon { color: var(--fg-2); }

    .settings-delete-group {
        order: 5;
        padding-top: 18px;
    }

    .settings-delete-group .group-row { justify-content: center; }

    .settings-delete-group .group-text { flex: none; }

    /* The whole row is the button, under its words. */
    .settings-delete {
        position: absolute;
        inset: 0;
        width: 100%;
        height: 100%;
        opacity: 0;
    }

    /* Columns and tags: the add row closes the list, a red minus deletes, and the
       drag handle is three lines at the end. */
    .settings-add > .group-label { display: none; }

    .settings-list > .group-label { order: 1; }

    .settings-list > .group-box {
        order: 2;
        border-bottom-left-radius: 0;
        border-bottom-right-radius: 0;
    }

    .settings-add > .group-box {
        order: 3;
        border-top: 1px solid var(--border-2);
        border-top-left-radius: 0;
        border-top-right-radius: 0;
    }

    .settings-list > .group-note { order: 4; }

    .group-add-icon {
        display: block;
        color: var(--brand-ink);
    }

    .settings-add .group-field {
        height: 26px;
        padding: 0;
        background: none;
        box-shadow: none;
    }

    .settings-add .group-field::placeholder { color: var(--brand-ink); }

    .settings-add .group-row:has(.group-field:placeholder-shown) .btn-add { display: none; }

    /* The small size (.btn-sm): a row's inline action. */
    .settings-add .btn-add {
        height: 28px;
        padding: 0 12px;
        font-size: 12px;
        font-weight: 500;
        letter-spacing: 0;
    }

    .settings-add .btn-add svg,
    .settings-pos,
    .settings-tag .tag-chip { display: none; }

    .admin-sortable-handle {
        order: 10;
        width: 18px;
        height: 14px;
        -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18 14'%3E%3Cg fill='black'%3E%3Crect x='1' y='1' width='16' height='2' rx='1'/%3E%3Crect x='1' y='6' width='16' height='2' rx='1'/%3E%3Crect x='1' y='11' width='16' height='2' rx='1'/%3E%3C/g%3E%3C/svg%3E");
        mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18 14'%3E%3Cg fill='black'%3E%3Crect x='1' y='1' width='16' height='2' rx='1'/%3E%3Crect x='1' y='6' width='16' height='2' rx='1'/%3E%3Crect x='1' y='11' width='16' height='2' rx='1'/%3E%3C/g%3E%3C/svg%3E");
    }

    .row-delete {
        order: -1;
        width: 22px;
        height: 22px;
        background: var(--danger);
    }

    .row-delete::before {
        content: "";
        width: 10px;
        height: 2px;
        border-radius: 1px;
        background: var(--bg-group);
    }

    /* A thumb-sized target round the 22px mark. */
    .row-delete::after {
        content: "";
        position: absolute;
        inset: -11px;
    }

    .row-delete { position: relative; }

    .row-delete svg { display: none; }

    .row-delete:disabled {
        background: var(--fill-2);
        opacity: 1;
    }

    .settings-deck {
        font-size: 13px;
        line-height: 18px;
        font-weight: 500;
    }

    /* Access: a tick at the end of the row, and Admin a tag to tap. */
    .access-visibility .group-sub { display: none; }

    .access-member-pick { display: contents; }

    .access-member input[type="checkbox"]:not(.switch),
    .access-visibility input[type="radio"] {
        -webkit-appearance: none;
        appearance: none;
        order: 3;
        width: 20px;
        height: 20px;
        margin-left: auto;
        background: transparent;
    }

    .access-member input[type="checkbox"]:not(.switch) { margin-left: 0; }

    .access-member input[type="checkbox"]:checked:not(.switch),
    .access-visibility input[type="radio"]:checked {
        background: var(--brand-ink);
        -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M4.6 10.4l3.5 3.5 7.3-7.8' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
        mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M4.6 10.4l3.5 3.5 7.3-7.8' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
    }

    .access-member-admin { order: 2; }

    .access-member-admin:has(.switch:disabled) { display: none; }

    /* Dashed while it is only on offer, filled once they run the board. */
    .access-member-admin > span {
        padding: 1px 7px;
        border: 1px dashed var(--border-3);
        border-radius: var(--radius-pill);
        color: var(--fg-3);
    }

    .access-member-admin:has(.switch:checked) > span {
        border-color: transparent;
        background: var(--fill-1);
        color: var(--fg-2);
    }

    .access-member,
    .member-row { padding-block: 9px; }

    .access-member-admin:has(.switch:focus-visible) > span {
        outline: 2px solid var(--brand-ink);
        outline-offset: 2px;
    }

    .access-member-admin input.switch {
        position: absolute;
        width: 1px;
        height: 1px;
        opacity: 0;
    }

    /* Members and the profile: rows that would not fit on one line wrap. */
    .member-row .group-meta { display: none; }

    .request-body {
        flex-wrap: wrap;
        padding-left: 0;
    }

    .group-row:has(> .profile-label) { flex-wrap: wrap; }

    .profile-label { width: 100%; }

    .profile-picture .profile-label { width: auto; }

    .profile-picture .group-meta { flex-basis: 100%; }

    .member-ban-row { flex-wrap: wrap; }

    .member-ban-row .group-field { flex-basis: 100%; }

    /* A sub-page's bar: the way back, its name in the middle, and Share. */
    .settings-phone-bar {
        position: sticky;
        top: 0;
        z-index: 20;
        display: flex;
        align-items: center;
        gap: 8px;
        min-height: 56px;
        padding: max(6px, env(safe-area-inset-top)) 16px 6px;
        background: var(--material);
        -webkit-backdrop-filter: blur(12px);
        backdrop-filter: blur(12px);
        border-bottom: 0.5px solid var(--border-2);
    }

    .settings-phone-back {
        display: flex;
        align-items: center;
        gap: 2px;
        height: 44px;
        margin-left: -7px;
        padding-right: 8px;
        font-size: 17px;
        line-height: 22px;
        font-weight: 500;
        color: var(--brand-ink);
        text-decoration: none;
    }

    .settings-phone-title {
        position: absolute;
        left: 50%;
        translate: -50% 0;
        margin: 0;
        font-size: 17px;
        line-height: 21px;
        font-weight: 600;
        letter-spacing: -0.01em;
        color: var(--fg);
        white-space: nowrap;
    }

    .settings-phone-bar .settings-phone-action {
        display: grid;
        place-items: center;
        width: 44px;
        height: 44px;
        margin-left: auto;
        padding: 0;
        color: var(--fg);
    }

    .settings-phone-bar ~ .page .group:first-of-type > .group-label,
    .settings-phone-bar ~ .page .settings-list > .group-label { padding-top: 18px; }
}

/* ── Scrollbars ── */

.column-cards::-webkit-scrollbar {
    width: 5px;
}

.column-cards::-webkit-scrollbar-track {
    background: transparent;
}

.column-cards::-webkit-scrollbar-thumb {
    background: var(--border-2);
    border-radius: 3px;
}

.column-cards::-webkit-scrollbar-thumb:hover {
    background: var(--border-3);
}

/* ── Reconnect modal ── */

/* The only styles for it. Don't add a ReconnectModal.razor.css: its scoped
   [b-…] selectors outrank these, as the template's once did. */

/* Nothing here may decide whether the dialog is on screen: that is showModal(),
   which ReconnectModal.razor.js holds back so a quick reconnect shows nothing. A
   display rule on a state class painted it outside the top layer. The state
   classes only choose which line an open dialog shows. */

/* The draft's Connection: a pill at the top centre, over a dimmed page that
   stays readable. A modal <dialog> places itself with margins; never a
   transform, which the entrance animates. */
#components-reconnect-modal {
    margin: max(28px, env(safe-area-inset-top)) auto auto;
    max-width: calc(100vw - 2rem);
    padding: 0;
    border: none;
    border-radius: 22px;
    background: var(--bg-1);
    color: var(--fg);
    box-shadow: var(--shadow-lg);
    font-family: var(--font-body);
}

#components-reconnect-modal[open] {
    animation: reconnect-in 300ms var(--ease-out);
}

@keyframes reconnect-in {
    from { opacity: 0; translate: 0 -12px; }
}

/* The browser focuses the dialog itself; keep the focus, drop the ring. The
   buttons keep theirs. */
#components-reconnect-modal:focus,
#components-reconnect-modal:focus-visible {
    outline: none;
}

/* --scrim, literal: ::backdrop inheriting custom properties is too recent.
   No blur, so the board reads through it. */
#components-reconnect-modal::backdrop {
    background: rgb(42 39 35 / 0.3);
    animation: fadeIn var(--duration) var(--ease-out);
}

:root[data-theme="dark"] #components-reconnect-modal::backdrop {
    background: rgb(0 0 0 / 0.45);
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) #components-reconnect-modal::backdrop {
        background: rgb(0 0 0 / 0.45);
    }
}

.components-reconnect-container {
    display: flex;
    align-items: center;
    gap: 12px;
    min-height: 44px;
    padding: 6px 18px 6px 14px;
}

/* A button ends the pill, closer to its edge. */
.components-reconnect-failed .components-reconnect-container,
.components-reconnect-paused .components-reconnect-container,
.components-reconnect-resume-failed .components-reconnect-container {
    padding-right: 6px;
}

/* Every part starts hidden and the state class picks them; the id outranks
   .btn-secondary. */
#components-reconnect-modal .components-reconnect-container > * {
    display: none;
}

#components-reconnect-modal.components-reconnect-show .components-reconnect-first-attempt-visible,
#components-reconnect-modal.components-reconnect-retrying .components-reconnect-repeated-attempt-visible,
#components-reconnect-modal.components-reconnect-failed .components-reconnect-failed-visible,
#components-reconnect-modal.components-reconnect-paused .components-pause-visible,
#components-reconnect-modal.components-reconnect-resume-failed .components-resume-failed-visible,
#components-reconnect-modal.components-reconnect-show .reconnect-icon-wait,
#components-reconnect-modal.components-reconnect-retrying .reconnect-icon-wait,
#components-reconnect-modal.components-reconnect-paused .reconnect-icon-wait,
#components-reconnect-modal.components-reconnect-failed .reconnect-icon-failed,
#components-reconnect-modal.components-reconnect-resume-failed .reconnect-icon-failed {
    display: block;
}

#components-reconnect-modal.components-reconnect-failed button.components-reconnect-failed-visible,
#components-reconnect-modal.components-reconnect-paused button.components-pause-visible,
#components-reconnect-modal.components-reconnect-resume-failed button.components-resume-failed-visible {
    display: inline-flex;
}

.reconnect-icon {
    flex: none;
    width: 17px;
    height: 17px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.36;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.reconnect-icon-wait { color: var(--brand-ink); }

.reconnect-icon-failed { color: var(--danger); }

.reconnect-icon-failed circle { fill: currentColor; }

/* Turning while a rejoin is under way. */
.components-reconnect-show .reconnect-icon-wait,
.components-reconnect-retrying .reconnect-icon-wait {
    animation: reconnect-turn 1.2s linear infinite;
}

@keyframes reconnect-turn {
    to { rotate: 1turn; }
}

#components-reconnect-modal p {
    margin: 0;
    font-size: 13px;
    line-height: 18px;
    font-weight: 500;
}

@media (prefers-reduced-motion: reduce) {
    #components-reconnect-modal[open],
    #components-reconnect-modal::backdrop,
    .reconnect-icon-wait {
        animation: none !important;
    }
}

/* ── A board's title bar ── */

/* From 600px up; a phone has .board-phone-bar instead. Its height is a token, so
   the board and the calendar below it can subtract it. The large title over its
   line, then the people, the views, the page's one primary and the menu. */
.board-titlebar { display: none; }

@media (min-width: 600px) {
    body:has(.board-titlebar) { --titlebar-h: 92px; }

    .board-titlebar {
        display: flex;
        align-items: center;
        gap: 12px;
        height: var(--titlebar-h);
        padding: 18px 1.5rem 16px;
    }

    .board-titlebar-text {
        display: flex;
        flex-direction: column;
        gap: 2px;
        min-width: 0;
        margin-right: auto;
    }

    .board-titlebar-name,
    .board-titlebar-sub {
        margin: 0;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    .board-titlebar-name {
        font-family: var(--font-display);
        font-size: 30px;
        font-weight: 700;
        line-height: 36px;
        letter-spacing: -0.022em;
        color: var(--fg);
    }

    .board-titlebar-sub {
        font-size: 13px;
        line-height: 18px;
        color: var(--fg-2);
    }

    /* Ringed in the ground they stand on. */
    .board-titlebar-faces .assignee-avatar {
        border-color: var(--bg-0);
        box-shadow: none;
    }

    .board-titlebar-faces .assignee-avatar:not(:first-child) { margin-left: -6px; }

    .board-titlebar-more { position: relative; }

    .board-menu-toggle {
        width: 32px;
        height: 32px;
        background: var(--fill-1);
        color: var(--fg);
    }

    .board-menu-toggle svg {
        width: 18px;
        height: 18px;
    }

    .board-menu-toggle:hover,
    .board-menu-toggle[aria-expanded="true"] { background: var(--fill-2); }

    .board-titlebar-more .menu {
        top: calc(100% + 6px);
        right: 0;
    }
}

/* A recessed track; the chosen view is a raised thumb. */
.nav-views {
    display: inline-flex;
    flex: none;
    align-items: center;
    padding: 2px;
    background: var(--fill-1);
    border-radius: var(--radius-pill);
    box-shadow: inset 0 0 0 0.5px var(--border-2);
}

/* ── Menus: a board's, a column's, the phone's ── */

/* Under the button that opened it, which is lifted over the catcher. */
.menu-backdrop {
    position: fixed;
    inset: 0;
    z-index: 190;
}

[aria-expanded="true"]:has(+ .menu-backdrop) {
    position: relative;
    z-index: 200;
}

.menu {
    position: absolute;
    z-index: 200;
    display: flex;
    flex-direction: column;
    width: 248px;
    padding: 6px;
    border-radius: 16px;
    background: var(--bg-1);
    box-shadow: var(--shadow-lg);
    text-align: left;
    animation: column-filter-in var(--duration) var(--ease) both;
}

.menu:focus { outline: none; }

/* Shared with the card dialog's assignee menu. */
@keyframes column-filter-in {
    from { opacity: 0; transform: translateY(-6px); }
    to   { opacity: 1; transform: none; }
}

/* Two classes, to outrank .share-button's round icon button. */
.menu .menu-row {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    min-height: 32px;
    padding: 0 10px;
    border: none;
    border-radius: var(--radius-xs);
    background: none;
    color: var(--fg);
    font-family: var(--font-body);
    font-size: 13.5px;
    font-weight: 500;
    line-height: 20px;
    text-align: left;
    text-decoration: none;
    cursor: pointer;
}

.menu .menu-row svg { flex: none; }

.menu-row-label,
.menu .share-button-label {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Hover fills with the accent. */
@media (hover: hover) {
    .menu .menu-row:hover {
        background: var(--brand);
        color: var(--brand-contrast);
    }
}

.menu-label {
    margin: 0;
    padding: 7px 10px 4px;
    color: var(--fg-2);
    font-size: 12px;
    font-weight: 600;
    line-height: 16px;
}

.menu-sep {
    flex: none;
    height: 1px;
    margin: 5px 10px;
    background: var(--border-2);
}

.menu-dot {
    flex: none;
    width: 8px;
    height: 8px;
    border-radius: 50%;
}

/* ── The phone's tabs ── */

/* Shaped per width below (the phone's bar, the top bar's links, the sidebar's
   rows). Hidden in CSS rather than left out of the render: the width arrives a
   round trip late, and a bar that arrives late rearranges under a thumb. */
.nav-tabs { display: none; }

.nav-side-boards { display: none; }

.nav-tab-label { display: none; }

/* ── Header lead: the brand, then which board ── */

/* min-width: 0 is load-bearing: without it a long board name pushes the account
   and menu off the row. Above 960px the search block overrides it. */
.nav-lead {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    min-width: 0;
    flex: 1 1 auto;
}

.nav-lead-sep {
    width: 1px;
    height: 22px;
    background: var(--border-2);
    flex: 0 0 auto;
}

/* ── Board switcher ── */

/* Anchor for the absolutely positioned flyout. */
.nav-boards {
    position: relative;
    display: flex;
    min-width: 0;
}

/* Board names are user-supplied and can be sentences, so it truncates. */
.nav-board-title,
.nav-boards-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    min-width: 0;
    max-width: 24rem;
    font-size: 17px;
    font-weight: 600;
    letter-spacing: -0.008em;
    color: var(--fg);
    line-height: 22px;
}

.nav-board-title {
    padding: 0.3rem 0.2rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.nav-boards-toggle {
    padding: 0 8px 0 12px;
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--radius-pill);
    cursor: pointer;
    transition: all var(--duration) var(--ease);
    height: 34px;
}

.nav-boards-toggle:hover {
    background: var(--fill-1);
    border-color: var(--border-1);
}

/* Over the click-catcher (z-index -1 in the header's stacking context), so the
   button that opened the flyout can close it. */
.nav-boards-toggle[aria-expanded="true"] {
    background: var(--brand-surface);
    border-color: transparent;
    color: var(--brand-ink);
    position: relative;
    z-index: 1;
}

.nav-boards-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.nav-boards-toggle > svg {
    flex: 0 0 auto;
    opacity: 0.6;
}

/* ── The header's flyout ── */

.nav-boards-menu {
    position: absolute;
    top: calc(100% + 0.4rem);
    z-index: 200;
    min-width: 248px;
    max-height: 60vh;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 0;
    padding: 6px;
    background: var(--bg-1);
    border-radius: 16px;
    box-shadow: var(--shadow-lg);
    animation: flyoutIn 160ms var(--ease-out);
    left: 0;
}

@keyframes flyoutIn {
    from { opacity: 0; transform: translateY(-6px); }
    to   { opacity: 1; transform: translateY(0); }
}

.nav-flyout-head {
    margin: 0;
    font-size: 12px;
    font-weight: 600;
    color: var(--fg-2);
    padding: 7px 10px 4px;
    line-height: 16px;
}

.nav-board-option {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 0 10px;
    border-radius: var(--radius-xs);
    color: var(--fg);
    text-decoration: none;
    font-size: 13.5px;
    font-weight: 500;
    transition: all var(--duration) var(--ease);
    min-height: 32px;
    line-height: 20px;
}

.nav-board-option:hover {
    background: var(--brand-glow);
    color: var(--brand-ink);
    text-decoration: none;
}

.nav-board-option-name {
    flex: 1 1 auto;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.nav-board-option-active {
    background: var(--brand-surface);
    color: var(--brand-ink);
    font-weight: 600;
}

/* A tick as well as the fill, so the current board doesn't rest on a tint. */
.nav-board-option-tick {
    flex: 0 0 auto;
    opacity: 0;
}

.nav-board-option-active .nav-board-option-tick { opacity: 1; }

/* The general calendar picks all the rows under it, so a hairline starts the
   list proper. */
.nav-board-option-all {
    border-bottom: 1px solid var(--border-2);
    border-radius: var(--radius-xs) var(--radius-xs) 0 0;
    margin-bottom: 5px;
    padding-bottom: 0;
}

/* Below a laptop, drop the wordmark (the mark stays) and the name (the avatar
   stands for it). Not in the phone/tablet blocks: this width spans both. */
@media (max-width: 959px) {
    .board-header h1 { display: none; }

    .board-user-who { display: none; }
}

/* ── The account menu ── */

.nav-menu-toggle {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    position: relative;
    padding: 2px 8px 2px 2px;
    background: var(--fill-1);
    border: 1px solid var(--border-1);
    border-radius: var(--radius-pill);
    color: var(--fg-2);
    font: inherit;
    font-family: var(--font-body);
    font-size: 14px;
    font-weight: 500;
    cursor: pointer;
    flex-shrink: 0;
    transition: all var(--duration) var(--ease);
}

.nav-menu-toggle:hover {
    background: var(--bg-3);
    border-color: var(--border-2);
    color: var(--fg);
}

/* Over the click-catcher, as the switcher's. */
.nav-menu-toggle[aria-expanded="true"] {
    background: var(--bg-3);
    border-color: var(--border-2);
    position: relative;
    z-index: 1;
}

.nav-menu-toggle-caret {
    flex: 0 0 auto;
    opacity: 0.6;
    transition: transform var(--duration) var(--ease);
}

.nav-menu-toggle[aria-expanded="true"] .nav-menu-toggle-caret {
    transform: rotate(180deg);
}

.nav-avatar {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    object-fit: cover;
    background: var(--brand-surface);
    border: none;
    flex-shrink: 0;
    box-shadow: 0 0 0 1px var(--border-2);
}

.nav-avatar-fallback {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--brand-ink);
    font-size: 11px;
    font-weight: 700;
    font-family: var(--font-display);
}

.nav-avatar-lg {
    width: 38px;
    height: 38px;
}

.nav-avatar-lg.nav-avatar-fallback {
    font-size: 13px;
}

.board-user-handle {
    color: var(--fg);
    font-weight: 500;
    font-size: 14px;
    line-height: 1;
}

/* One dot for anything new in the menu (install offer, unread release), inside
   the pill's curve: at (0,0) it would sit outside the radius. */
.nav-menu-dot {
    position: absolute;
    top: 2px;
    right: 2px;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--brand);
    /* Punches a ring out of the button so the dot reads at any header colour. */
    box-shadow: 0 0 0 2px var(--bg-1);
}

/* ── The search box ── */

/* Two triggers: this magnifier below 960px (a pill in the top bar, round beside
   the phone's tab bar), the sidebar's field above it. Both open the palette. */
.nav-search {
    display: flex;
    align-items: center;
}

/* The same box as .nav-menu-toggle beside it: a bare glyph didn't read as pressable. */
.nav-search-compact {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 34px;
    padding: 0;
    border: 1px solid var(--border-1);
    border-radius: var(--radius-pill);
    background: var(--bg-2);
    color: var(--fg-2);
    cursor: pointer;
    transition: all var(--duration) var(--ease);
}

/* Larger when alone in its button; the one in a field stays 16px. */
.nav-search-compact svg { width: 20px; height: 20px; }

.nav-search-compact:hover { background: var(--bg-3); color: var(--fg); }

.nav-search-field { display: none; }

.nav-search-mark {
    display: flex;
    flex: 0 0 auto;
    color: var(--fg-3);
}

.nav-search-mark svg { width: 16px; height: 16px; }

/* The palette: over the dimmed page, centred on it beside the sidebar and a little
   below its top, so the list grows downwards. Below 600px it is the whole screen. */
/* The header is the palette's stacking context, so it rises while the palette is
   open: Ctrl-K reaches it from over a card, and a docked card stays on screen. */
.board-header:has(.nav-search-palette) { z-index: 1100; }

.nav-search-scrim {
    position: fixed;
    inset: 0;
    z-index: 290;
    background: var(--scrim);
    animation: fadeIn var(--duration) var(--ease-out);
}

.nav-search-palette {
    position: fixed;
    inset: 96px 0 auto var(--sidebar-w);
    z-index: 300;
    width: min(640px, calc(100vw - var(--sidebar-w) - 2rem));
    max-height: calc(100dvh - 96px - 1rem);
    margin-inline: auto;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    background: var(--bg-panel);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-xl);
    outline: none;
    animation: flyoutIn 160ms var(--ease-out);
    /* How strongly the lit chip and the active row take their accent. */
    --search-tint: 22%;
}

.nav-search-bar {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 14px 14px 0;
}

/* Focused for as long as it exists, so it wears the focused field's ring. */
.nav-search-query {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 8px;
    height: 44px;
    padding: 0 12px;
    background: var(--bg-1);
    border: 2px solid var(--brand-ink);
    border-radius: var(--radius-btn);
}

.nav-search-input {
    flex: 1 1 auto;
    min-width: 0;
    height: 100%;
    border: 0;
    background: none;
    color: var(--fg);
    font: inherit;
    font-size: 14px;
    outline: none;
    padding: 0;
}

.nav-search-input::placeholder { color: var(--fg-3); }

.nav-search-clear {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    padding: 0;
    border: 0;
    border-radius: var(--radius-pill);
    background: var(--fill-2);
    color: var(--fg-2);
    cursor: pointer;
}

.nav-search-clear:hover { color: var(--fg); }

/* Only the phone's, which covers the trigger, needs a way out in words. */
.nav-search-close { display: none; }

.nav-search-try {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 10px 14px;
    font-size: 12px;
    font-weight: 500;
    line-height: 16px;
    color: var(--fg-2);
}

.nav-search-chip {
    padding: 3px 8px;
    border: 0;
    border-radius: var(--radius-pill);
    background: var(--fill-1);
    color: var(--fg-2);
    font: inherit;
    white-space: nowrap;
    cursor: pointer;
    transition: background var(--duration) var(--ease);
}

.nav-search-chip:hover { background: var(--fill-2); }

/* The page's accent, as the active row's is its board's. */
.nav-search-chip-lit,
.nav-search-chip-lit:hover {
    background: color-mix(in oklab, var(--brand) var(--search-tint), transparent);
    color: var(--brand-ink);
}

/* Scrolls on its own between the query and the keys; heads and rows 2px apart. */
.nav-search-panel {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 4px 8px 8px;
}

.nav-search-panel,
.nav-search-group,
.nav-search-group-hits {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.nav-search-panel:empty { display: none; }

.nav-search-empty {
    margin: 0;
    padding: 14px 6px 6px;
    font-size: 14px;
    color: var(--fg-2);
}

.nav-search-group-head {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0;
    padding: 10px 6px 4px;
    font-size: 14px;
    font-weight: 600;
    line-height: 20px;
    color: var(--fg-2);
}

.nav-search-group-count {
    font-size: 12px;
    font-weight: 500;
}

/* A board's mark, as in the sidebar: its initial on its re-lit colour. */
.nav-search-board-mark {
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    width: 16px;
    height: 16px;
    border-radius: 5px;
    background: linear-gradient(rgb(255 255 255 / 0.18), rgb(255 255 255 / 0)), var(--brand);
    box-shadow: var(--shadow-sm);
    color: var(--brand-contrast);
    font-size: 9px;
    font-weight: 700;
    line-height: 1;
}

.nav-search-hit {
    display: flex;
    align-items: center;
    gap: 12px;
    min-height: 52px;
    padding: 8px 12px;
    border-radius: var(--radius-xs);
    color: var(--fg);
    text-decoration: none;
}

.nav-search-hit:hover { background: var(--fill-1); text-decoration: none; }

/* Mixed here, where --brand is the row's board's (the group's inline colour). */
.nav-search-hit-active,
.nav-search-hit-active:hover { background: color-mix(in oklab, var(--brand) var(--search-tint), transparent); }

.nav-search-hit .nav-search-board-mark {
    width: 28px;
    height: 28px;
    border-radius: var(--radius-xs);
    font-size: 16px;
}

.nav-search-hit-text {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
}

.nav-search-hit-title,
.nav-search-hit-place {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.nav-search-hit-title {
    font-size: 14px;
    font-weight: 600;
    line-height: 20px;
}

.nav-search-hit-place {
    font-size: 12px;
    font-weight: 500;
    line-height: 16px;
    color: var(--fg-2);
}

.nav-search-hit .card-due { flex: 0 0 auto; }

.nav-search-keys {
    display: flex;
    gap: 16px;
    margin: 0;
    padding: 10px 16px 12px;
    border-top: 1px solid var(--border-2);
    font-size: 12px;
    font-weight: 500;
    line-height: 16px;
    color: var(--fg-2);
}

/* ── The menu ── */

/* The draft's Account menu: a raised tile of 32px rows in groups with a hairline
   between, a row lit in the accent under the pointer. On a phone, a sheet. */
.nav-menu {
    position: absolute;
    top: calc(100% + 0.5rem);
    right: 1rem;
    z-index: 200;
    display: flex;
    flex-direction: column;
    width: 248px;
    padding: 6px;
    border-radius: 16px;
    background: var(--bg-1);
    box-shadow: var(--shadow-lg);
    transform: translateY(-8px);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity var(--duration) var(--ease),
                transform var(--duration) var(--ease),
                visibility 0s var(--duration);
}

/* Invisible click-catcher at negative z inside the sticky header's stacking
   context (z 100): below the header's own children, so nav links stay one click,
   but above all page content. The menu is z 200. */
.nav-menu-backdrop {
    position: fixed;
    inset: 0;
    z-index: -1;
    background: transparent;
}

.nav-menu-open {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
    pointer-events: auto;
    transition: opacity var(--duration) var(--ease),
                transform var(--duration) var(--ease),
                visibility 0s 0s;
}

/* Only the phone's sheet shows one. */
.nav-menu-sheet-handle { display: none; }

.nav-menu-account {
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
    padding: 6px 10px 8px;
}

.nav-menu-account-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.nav-menu-account-name,
.nav-menu-account-handle {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Name over username; a username alone takes the name's place. */
.nav-menu-account-name,
.nav-menu-account-text > :only-child {
    font-size: 14px;
    font-weight: 600;
    line-height: 20px;
    color: var(--fg);
}

.nav-menu-account-handle {
    font-size: 12px;
    line-height: 16px;
    color: var(--fg-2);
}

.nav-menu-group {
    display: flex;
    flex-direction: column;
}

/* The draft's separator: an 11px gap with a hairline inset to the rows' text. */
.nav-menu-account + .nav-menu-group,
.nav-menu-group + .nav-menu-group {
    position: relative;
    margin-top: 11px;
}

.nav-menu-account + .nav-menu-group::before,
.nav-menu-group + .nav-menu-group::before {
    content: "";
    position: absolute;
    top: -6px;
    left: 10px;
    right: 10px;
    height: 1px;
    background: var(--border-2);
}

/* Profile to Install is one block; the phone's sheet splits it. */
.nav-menu-group + .nav-menu-prefs { margin-top: 0; }

.nav-menu-group + .nav-menu-prefs::before { display: none; }

/* ── Menu rows ── */

.nav-menu .nav-menu-row {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    min-height: 32px;
    padding: 0 10px;
    border: none;
    border-radius: var(--radius-xs);
    background: none;
    color: var(--fg);
    font: inherit;
    font-family: var(--font-body);
    font-size: 13.5px;
    font-weight: 500;
    line-height: 20px;
    text-align: left;
    text-decoration: none;
    cursor: pointer;
    transition: background var(--duration) var(--ease), color var(--duration) var(--ease);
}

.nav-menu a.nav-menu-row:hover,
.nav-menu button.nav-menu-row:hover:not(:disabled) {
    background: var(--brand);
    color: var(--brand-contrast);
}

.nav-menu .nav-menu-row:disabled {
    opacity: 0.55;
    cursor: default;
}

/* One icon column, sized here rather than by the markup, so every word starts
   on one left edge. */
.nav-menu .nav-menu-row > svg:first-child {
    flex: none;
    width: 17px;
    height: 17px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.nav-menu-row-label {
    flex: 1 1 auto;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.nav-menu-row-active {
    background: var(--brand-glow);
    color: var(--brand-ink);
}

/* The phone's rows say they lead on; the flyout's do not. */
.nav-menu-chevron,
.nav-pref-updown { display: none; }

/* The sparkle pictures its row, so it keeps its colour until the row lights. */
.nav-menu .nav-menu-whatsnew > svg:first-child {
    fill: currentColor;
    stroke: none;
    color: var(--brand-ink);
}

.nav-menu .nav-menu-whatsnew:hover > svg:first-child { color: inherit; }

/* "New", here and on the release in the dialog: a .chip, filled. */
.nav-menu-badge,
.changelog-badge {
    flex: none;
    padding: 2px 8px;
    border-radius: var(--radius-pill);
    background: var(--brand);
    color: var(--brand-contrast);
    font-size: 11px;
    font-weight: 600;
    line-height: 14px;
}

/* The Members row's count of requests waiting. */
.nav-menu-count {
    flex: none;
    padding: 2px 7px;
    border: 0.5px solid var(--border-2);
    border-radius: var(--radius-pill);
    background: var(--bg-1);
    color: var(--fg-2);
    font-size: 12px;
    font-weight: 600;
    line-height: 14px;
    font-variant-numeric: tabular-nums;
}

.nav-install-hint {
    margin: 0;
    padding: 2px 10px 6px 37px;
    font-size: 12px;
    line-height: 16px;
    color: var(--fg-2);
}

/* ── Prefs (theme + language) ── */

.nav-menu .nav-pref-row { cursor: default; }

/* The theme's segments sit closer to the edge than a row's words. */
.nav-menu .nav-pref-row:first-child {
    min-height: 40px;
    padding: 4px 4px 4px 10px;
}

.nav-pref-options {
    display: flex;
    flex: none;
    padding: 2px;
    border-radius: var(--radius-pill);
    background: var(--fill-1);
}

.nav-pref-options button {
    height: 24px;
    padding: 0 8px;
    border: none;
    border-radius: var(--radius-pill);
    background: transparent;
    color: var(--fg-2);
    font: inherit;
    font-family: var(--font-body);
    font-size: 12px;
    font-weight: 500;
    line-height: 16px;
    white-space: nowrap;
    cursor: pointer;
    transition: color var(--duration) var(--ease);
}

.nav-pref-options button:hover { color: var(--fg); }

/* The chosen theme reads :root (no data-theme is auto), so the tap needs no
   round trip. */
:root:not([data-theme]) .nav-pref-options button[data-theme-set="auto"],
:root[data-theme="light"] .nav-pref-options button[data-theme-set="light"],
:root[data-theme="dark"] .nav-pref-options button[data-theme-set="dark"] {
    background: var(--bg-thumb);
    color: var(--fg);
    box-shadow: var(--shadow-sm);
}

/* The language is a native picker showing its value, on the row's far side. */
.nav-pref-select {
    flex: none;
    margin-right: 6px;
    padding: 0;
    border: none;
    background: transparent;
    color: var(--fg-2);
    font: inherit;
    font-size: 13px;
    font-weight: 400;
    text-align: right;
    cursor: pointer;
    appearance: none;
}

.nav-pref-select:focus-visible { outline: 2px solid var(--brand-ink); outline-offset: 2px; border-radius: 4px; }

/* ── The foot ── */

/* The one place naming the running version: a footnote, for bug reports. */
.nav-menu-version {
    margin: 0;
    padding: 2px 10px 4px 37px;
    font-size: 11px;
    font-weight: 500;
    line-height: 14px;
    color: var(--fg-3);
    font-variant-numeric: tabular-nums;
}

/* ── What's new: the dialog ── */

.changelog-modal {
    width: min(560px, 100%);
}

.changelog-modal .modal-header h2 {
    display: flex;
    align-items: center;
    gap: 8px;
}

.changelog-sparkle {
    fill: var(--brand);
    flex-shrink: 0;
}

.changelog-intro {
    margin: 0;
    padding: 0 14px;
    font-size: 13px;
    line-height: 18px;
    color: var(--fg-2);
}

/* One group on the panel, the rail down its left. */
.changelog-timeline {
    display: flex;
    flex-direction: column;
    position: relative;
    padding: 14px 14px 14px 32px;
    border-radius: var(--radius-sm);
    background: var(--bg-group);
    box-shadow: var(--shadow-sm);
}

/* On the container, so the line runs behind the marks and stops at the last. */
.changelog-timeline::before {
    content: "";
    position: absolute;
    left: 17px;
    top: 24px;
    bottom: 24px;
    width: 2px;
    border-radius: 1px;
    background: var(--border-2);
}

.changelog-release {
    display: flex;
    flex-direction: column;
    gap: 6px;
    position: relative;
    padding: 2px 0 18px;
}

.changelog-release:last-child { padding-bottom: 2px; }

.changelog-dot {
    position: absolute;
    left: -18px;
    top: 8px;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--border-3);
    box-shadow: 0 0 0 3px var(--bg-group);
}

/* The top of the story is filled; the unreleased block is hollow instead, said
   here rather than left to rule order. */
.changelog-release-top:not(.changelog-release-unreleased) .changelog-dot { background: var(--brand); }

/* Only on the dev stack: a heading, not a date, with a hollow mark. */
.changelog-release-unreleased .changelog-dot {
    background: var(--bg-group);
    border: 2px solid var(--brand);
}

.changelog-release-new {
    background: var(--brand-surface);
    border-radius: var(--radius-xs);
    padding: 10px 12px 12px 14px;
    margin: -2px -6px 6px -14px;
}

.changelog-release-new .changelog-dot {
    left: -4px;
    top: 16px;
    box-shadow: 0 0 0 3px var(--brand-surface);
}

.changelog-release-head {
    display: flex;
    align-items: baseline;
    gap: 8px;
    flex-wrap: wrap;
}

.changelog-release-date {
    margin: 0;
    font-size: 14px;
    font-weight: 600;
    line-height: 19px;
    letter-spacing: -0.006em;
    color: var(--fg);
}

.changelog-release-unreleased .changelog-release-date,
.changelog-release:not(.changelog-release-top) .changelog-release-date { color: var(--fg-2); }

.changelog-version {
    margin-left: auto;
    font-size: 12px;
    font-weight: 500;
    line-height: 16px;
    color: var(--fg-3);
    font-variant-numeric: tabular-nums;
}

.changelog-list {
    margin: 0;
    padding-left: 18px;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.changelog-list li {
    font-size: 14px;
    line-height: 20px;
    color: var(--fg-2);
}

.changelog-list li::marker {
    color: var(--brand-ink);
}

/* Its words on the group's. */
.changelog-more {
    align-self: flex-start;
    margin-left: 2px;
}

/* ── Report a problem: the dialog ── */
/* Narrower: a 720px dialog around one textarea reads as a form missing fields.
   The box is the card description's field, a white group on the panel. */

.feedback-modal {
    width: min(480px, 100%);
}

.feedback-text { min-height: 8rem; }

.feedback-note { padding: 0 14px; }

.feedback-thanks {
    margin: 0;
    padding: 0 14px;
    font-size: 14px;
    line-height: 20px;
}

/* ── Loading spinner ── */

.spinner {
    position: relative;
    width: 2.6rem;
    height: 1.5rem;
    --windbar: var(--brand);
}

.spinner::before,
.spinner::after {
    content: "";
    position: absolute;
    left: 0;
    height: 4px;
    border-radius: 999px;
    background: var(--windbar);
    animation: wind-drift 1.1s ease-in-out infinite;
}

.spinner::before {
    top: 0;
    width: 100%;
}

.spinner::after {
    top: 10px;
    width: 70%;
    left: 8%;
    opacity: 0.7;
    animation-delay: 0.18s;
}

@keyframes wind-drift {
    0%, 100% { transform: translateX(0); opacity: 0.35; }
    50%      { transform: translateX(-7px); opacity: 1; }
}

.board-loading {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1.25rem;
    min-height: calc(100vh - var(--header-h) - var(--titlebar-h));
    color: var(--fg-3);
    font-size: 14px;
}

/* ── Status: empty states and outcome pages ── */

/* The draft's Status: a recessed glyph, what happened, one line on what to do,
   one action. Centred in what the page leaves. */
.empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 6px;
    min-height: calc(100vh - var(--header-h) - var(--titlebar-h));
    padding: 2rem 1.5rem;
    text-align: center;
}

/* A page with no header around it (signed out, an error) has the whole screen. */
body:not(:has(.board-header)) .empty-state {
    min-height: 100vh;
    min-height: 100dvh;
}

.empty-state-icon {
    display: grid;
    place-items: center;
    padding: 16px;
    margin-bottom: 8px;
    border-radius: var(--radius);
    background: var(--bg-2);
    box-shadow: var(--shadow-recessed);
    color: var(--fg-2);
}

.empty-state-icon svg {
    width: 32px;
    height: 32px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.empty-state-title {
    margin: 0;
    font-size: 22px;
    line-height: 28px;
    font-weight: 700;
    letter-spacing: -0.012em;
    color: var(--fg);
}

.empty-state-hint {
    margin: 0;
    max-width: 320px;
    font-size: 14px;
    line-height: 20px;
    color: var(--fg-2);
}

.empty-state-hint a {
    color: var(--brand-ink);
    font-weight: 500;
    text-decoration: none;
}

/* The one action, after a little more room. */
.empty-state .empty-state-action { margin-top: 10px; }

/* ── A deleted board ── */

/* Its name and a banner where its cards were; the cards stay hidden. */
.board-deleted {
    display: flex;
    flex-direction: column;
    gap: 18px;
    padding: 18px 24px;
}

.board-deleted-name {
    margin: 0;
    font-size: 30px;
    line-height: 36px;
    font-weight: 700;
    letter-spacing: -0.022em;
    color: var(--fg);
    overflow-wrap: anywhere;
}

.board-deleted-banner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 14px;
    padding: 14px 16px;
    border-radius: var(--radius-sm);
    background: var(--bg-group);
    box-shadow: var(--shadow-sm);
}

.board-deleted-glyph {
    display: grid;
    place-items: center;
    flex: none;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    background: var(--danger-bg);
    color: var(--danger);
}

.board-deleted-glyph svg {
    width: 20px;
    height: 20px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.board-deleted-text {
    display: flex;
    flex-direction: column;
    gap: 1px;
    flex: 1 1 12rem;
    font-size: 12px;
    line-height: 16px;
    font-weight: 500;
    color: var(--fg-2);
}

.board-deleted-text strong {
    font-size: 14px;
    line-height: 19px;
    font-weight: 600;
    letter-spacing: -0.006em;
    color: var(--fg);
}

/* ── The board while it loads ── */

/* The draft's Loading: the trays, with cards drawn as lines, so the board
   arriving changes as little as it can. */
.board-skeleton {
    display: flex;
    flex-direction: column;
    height: calc(100vh - var(--header-h));
    height: calc(100dvh - var(--header-h));
}

.skeleton-head {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 10px;
    height: 92px;
    flex: none;
    padding: 0 24px;
}

.skeleton-head span,
.skeleton-line {
    display: block;
    height: 8px;
    border-radius: 4px;
    background: var(--fill-2);
}

.skeleton-head span:first-child { width: 220px; height: 26px; border-radius: 8px; }

.skeleton-head span:last-child { width: 120px; }

.skeleton-columns {
    display: flex;
    gap: 14px;
    flex: 1;
    min-height: 0;
    padding: 0 22px 18px 24px;
    overflow: hidden;
}

.skeleton-column {
    display: flex;
    flex-direction: column;
    gap: 8px;
    flex: 1 1 0;
    max-width: 380px;
    padding: 8px;
    border-radius: var(--radius);
    background: var(--bg-2);
    box-shadow: var(--shadow-recessed);
}

.skeleton-column > .skeleton-line {
    width: 25%;
    margin: 14px 8px 16px;
}

.skeleton-card {
    display: flex;
    flex-direction: column;
    gap: 10px;
    height: 73px;
    padding: 18px 14px;
    border-radius: var(--radius-sm);
    background: color-mix(in oklab, var(--bg-1) 60%, transparent);
    box-shadow: var(--shadow-xs);
}

.skeleton-card .skeleton-line:first-child { width: 70%; }

.skeleton-card .skeleton-line:last-child { width: 42%; }

/* ── Toast notifications ── */

.toast-container {
    position: fixed;
    bottom: 1.75rem;
    /* Centred on the page, beside the sidebar. */
    left: calc(50% + var(--sidebar-w) / 2);
    transform: translateX(-50%);
    z-index: 2000;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    align-items: center;
    pointer-events: none;
}

/* The draft's (Components · Feedback & menus): 44px, its Undo a 30px pill. */
.toast {
    display: inline-flex;
    align-items: center;
    gap: 12px;
    min-height: 44px;
    padding: 0 18px 0 16px;
    background: var(--toast-bg);
    color: var(--toast-fg);
    border-radius: var(--radius-pill);
    font-size: 13.5px;
    font-weight: 500;
    line-height: 18px;
    box-shadow: var(--shadow-lg);
    border: none;
    -webkit-backdrop-filter: blur(20px);
    backdrop-filter: blur(20px);
    animation: toastIn 380ms cubic-bezier(0.34, 1.56, 0.64, 1);
    /* Click-through, bar its button: it covered the "+ Add card" below it. */
    pointer-events: none;
    white-space: nowrap;
}

.toast:has(.toast-action) {
    padding-right: 7px;
}

.toast-action {
    pointer-events: auto;
    margin-left: auto;
    height: 30px;
    padding: 0 14px;
    border: none;
    border-radius: var(--radius-pill);
    background: rgb(243 242 239 / 0.1);
    color: var(--toast-accent);
    font: inherit;
    font-size: 12.5px;
    font-weight: 600;
    line-height: 16px;
    cursor: pointer;
    transition: background var(--duration) var(--ease);
}

.toast-action:hover {
    background: rgb(243 242 239 / 0.18);
}

.toast-action:focus-visible {
    outline: 2px solid var(--toast-accent);
    outline-offset: 2px;
}

.toast-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    flex: 0 0 8px;
    box-shadow: 0 0 0 3px rgba(52, 211, 153, 0.16);
}

.toast-success .toast-dot {
    background: var(--success);
    box-shadow: 0 0 0 3px rgba(54, 211, 153, 0.18);
}

.toast-danger .toast-dot {
    background: #f87171;
    box-shadow: 0 0 0 3px rgba(248, 113, 113, 0.18);
}

.toast-exit {
    animation: toastOut 200ms var(--ease) forwards;
}

@keyframes toastIn {
    from { opacity: 0; transform: translateY(16px) scale(0.92); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
}

@keyframes toastOut {
    from { opacity: 1; transform: translateY(0) scale(1); }
    to   { opacity: 0; transform: translateY(-10px) scale(0.92); }
}

/* ── Drag state: no scroll-snap during a drag ── */

.board-dragging {
    scroll-snap-type: none !important;
}

/* The clone lets the pointer through, so the whole board says a mouse is
   grabbing. Not on touch: changing an inherited property this widely sets off
   the `transition: all` of every card, which a phone mid-drag cannot spare. */
@media (hover: hover) {
    .board-dragging,
    .board-dragging * {
        cursor: grabbing;
    }
}

/* ── The column switcher and the held card's hint (phone only) ── */

.board-pagination,
.board-drag-hint {
    display: none;
}

/* ── Phone (max-width: 599px) ── */

@media (max-width: 599px) {
    /* ── The nav floats: a tab bar, a search button, and the account on Home ──
       The header is the floating row, inset from the edges; the material is on
       its parts, not on it. Out of flow: pages that size themselves subtract
       --phone-nav-h, and body pads for the rest. */
    .board-header {
        position: fixed;
        inset: auto 1rem var(--phone-nav-bottom) 1rem;
        height: 62px;
        padding: 0;
        gap: 10px;
        box-shadow: none;
    }

    .board-header::before { display: none; }

    /* The wordmark duplicates Home and the switcher made the bar differ per page;
       a board names itself in .board-phone-bar, and Home is the way to another. */
    .nav-lead { display: none; }

    .nav-tabs,
    .nav-search-compact,
    .nav-search-compact:hover,
    .nav-search-compact[aria-expanded="true"],
    .nav-menu-toggle,
    .nav-menu-toggle:hover,
    .nav-menu-toggle[aria-expanded="true"] {
        border: 0;
        background: var(--material);
        -webkit-backdrop-filter: blur(28px) saturate(1.4);
        backdrop-filter: blur(28px) saturate(1.4);
        box-shadow: var(--shadow-material);
    }

    /* Home and Calendar in a pill, beside the search button. */
    .nav-tabs {
        display: flex;
        flex: 1 1 0;
        align-self: stretch;
        padding: 4px;
        border-radius: var(--radius-pill);
    }

    /* A mark over a word; the current one is a raised thumb (Theming in
       docs/frontend.md). 1 1 0, so a longer word doesn't get a wider target. */
    .nav-tab {
        flex: 1 1 0;
        min-width: 0;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 3px;
        border-radius: var(--radius-pill);
        color: var(--fg);
        text-decoration: none;
        font-size: 11px;
        font-weight: 600;
        line-height: 13px;
        transition: background var(--duration) var(--ease), color var(--duration) var(--ease);
    }

    .nav-tab svg {
        width: 20px;
        height: 20px;
        flex: 0 0 auto;
    }

    .nav-tab-label {
        display: block;
        max-width: 100%;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .nav-tab-active {
        background: var(--bg-thumb);
        box-shadow: var(--shadow-sm);
        color: var(--brand-ink);
    }

    /* Round, beside the pill; named for assistive tech in SearchBox. */
    .nav-search-compact,
    .nav-search-compact:hover,
    .nav-search-compact[aria-expanded="true"] {
        width: 62px;
        height: 62px;
        border-radius: 50%;
        color: var(--fg);
    }

    /* The account is on Home only, a face in a disc of material level with the
       large title (.home-head), opening the account menu as a sheet. Fixed, so
       it stays while Home scrolls, and so a box painted after the pill: the
       sheet's dim, painted with it, covers the bar, and the face stays above
       the dim as the way to close. */
    .board-user { display: none; }

    .board-header-home .board-user {
        display: block;
        position: fixed;
        /* Centred on the title's 41px line. */
        top: calc(0.75rem - 1.5px);
        right: 1rem;
    }

    .nav-menu-toggle,
    .nav-menu-toggle:hover,
    .nav-menu-toggle[aria-expanded="true"] {
        display: grid;
        place-items: center;
        width: 44px;
        height: 44px;
        padding: 0;
        border-radius: 50%;
    }

    .nav-menu-toggle-caret { display: none; }

    /* Scoped to the trigger, or it would also resize .nav-avatar-lg in the sheet. */
    .nav-menu-toggle .nav-avatar {
        width: 36px;
        height: 36px;
    }

    .nav-menu-toggle .nav-avatar-fallback { font-size: 13px; }

    /* On the disc's curve, as on the desktop pill. */
    .nav-menu-dot {
        top: 3px;
        right: 3px;
    }

    /* Only where the tab bar exists; sign-in, sign-up and /request have no header. */
    body:has(.board-header) { padding-bottom: var(--phone-nav-h); }

    /* The whole screen: the field at the top beside Cancel, the chips under it and
       the results below, so typing never moves the field. It stops on the keyboard
       (search-interop.js measures it), so the last row can be scrolled into sight. */
    .nav-search-scrim { display: none; }

    .nav-search-palette {
        inset: 0 0 var(--keyboard-inset, 0px) 0;
        width: auto;
        max-height: none;
        margin: 0;
        padding-top: env(safe-area-inset-top, 0px);
        background: var(--bg-0);
        border-radius: 0;
        box-shadow: none;
        animation: fadeIn var(--duration) var(--ease-out);
        --search-tint: 13%;
    }

    .nav-search-bar { padding: 8px 16px; }

    .nav-search-query { height: 36px; }

    /* Not the draft's 14px: under 16px, iOS zooms the page into a focused field. */
    .nav-search-input { font-size: 16px; }

    .nav-search-close {
        display: block;
        flex: 0 0 auto;
        padding: 0;
        border: 0;
        background: none;
        color: var(--brand-ink);
        font: inherit;
        font-size: 17px;
        line-height: 22px;
        cursor: pointer;
    }

    /* One row, scrolled sideways if it must; the chips say what they are. */
    .nav-search-try {
        gap: 8px;
        padding: 0 16px 8px;
        overflow-x: auto;
        scrollbar-width: none;
        font-size: 13px;
        line-height: 18px;
    }

    .nav-search-try-label,
    .nav-search-group-count,
    .nav-search-hit .nav-search-board-mark,
    .nav-search-hit-board,
    .nav-search-keys { display: none; }

    .nav-search-chip {
        flex: 0 0 auto;
        padding: 6px 12px;
    }

    .nav-search-panel { padding: 4px 16px 16px; }

    .nav-search-panel,
    .nav-search-group,
    .nav-search-group-hits { gap: 0; }

    .nav-search-group-head {
        gap: 6px;
        padding: 12px 4px 6px;
        font-size: 13px;
        font-weight: 500;
        line-height: 18px;
    }

    /* Each board's rows are one group, the board named above it. */
    .nav-search-group-hits {
        overflow: hidden;
        background: var(--bg-group);
        border-radius: var(--radius-sm);
    }

    .nav-search-hit {
        gap: 10px;
        min-height: 0;
        padding: 11px 14px;
        border-radius: 0;
    }

    .nav-search-hit + .nav-search-hit { border-top: 1px solid var(--border-2); }

    .nav-search-hit-text { gap: 1px; }

    .nav-search-hit-title {
        font-size: 17px;
        font-weight: 400;
        line-height: 22px;
    }

    .nav-search-hit-place,
    .nav-search-hit .card-due {
        font-size: 13px;
        line-height: 18px;
    }

    .nav-search-hit .card-due::before {
        width: 15px;
        height: 15px;
    }

    /* A sheet from the bottom edge, over the tab bar: the draft's Account sheet,
       white groups of rows on the panel. It slides on the transform
       .nav-menu-open already animates, since the menu is always in the DOM and an
       animation would play on load. */
    .nav-menu {
        position: fixed;
        inset: auto 0 0 0;
        top: auto;
        width: auto;
        max-height: 85dvh;
        overflow-y: auto;
        gap: 14px;
        padding: 6px 17px max(24px, env(safe-area-inset-bottom, 0px));
        border-radius: 38px 38px 0 0;
        background: var(--bg-panel);
        /* Upwards only — see --shadow-sheet. */
        box-shadow: var(--shadow-sheet);
        transform: translateY(100%);
    }

    .nav-menu-open { transform: none; }

    .nav-menu-sheet-handle {
        display: block;
        flex: none;
        width: 36px;
        height: 5px;
        margin: 0 auto;
        border-radius: 3px;
        background: var(--fill-3);
    }

    /* Visible here, and over the tab bar too, which steps aside under a sheet.
       The account's paints with .board-user, above the pill and under the face
       that opened it. */
    .nav-menu-backdrop {
        inset: 0;
        background: var(--scrim);
        animation: fadeIn var(--duration) var(--ease-out);
    }

    /* The account is the sheet's heading. */
    .nav-menu-account {
        gap: 12px;
        padding: 6px 4px 2px;
    }

    .nav-menu-account .nav-avatar-lg {
        width: 36px;
        height: 36px;
        box-shadow: 0 0 0 2px var(--bg-1);
    }

    .nav-menu-account-name,
    .nav-menu-account-text > :only-child {
        font-size: 16px;
        line-height: 21px;
        letter-spacing: -0.01em;
    }

    .nav-menu-account-handle {
        font-size: 13px;
        font-weight: 500;
        line-height: 18px;
    }

    /* Each group a white card; the gap is the separator. */
    .nav-menu-group,
    .nav-menu-group + .nav-menu-prefs {
        margin: 0;
        border-radius: var(--radius-sm);
        background: var(--bg-group);
        overflow: hidden;
    }

    .nav-menu-account + .nav-menu-group,
    .nav-menu-group + .nav-menu-group { margin-top: 0; }

    .nav-menu-account + .nav-menu-group::before,
    .nav-menu-group + .nav-menu-group::before { display: none; }

    /* Thumb-sized rows in the phone's body type, a hairline between. */
    .nav-menu .nav-menu-row {
        min-height: 46px;
        gap: 12px;
        padding: 12px 16px;
        border-radius: 0;
        font-size: 17px;
        font-weight: 400;
        line-height: 22px;
    }

    .nav-menu .nav-menu-row + .nav-menu-row { border-top: 1px solid var(--border-2); }

    .nav-menu .nav-menu-row > svg:first-child {
        width: 20px;
        height: 20px;
    }

    /* Touch lights a row while pressed, not in the accent. */
    .nav-menu a.nav-menu-row:hover,
    .nav-menu button.nav-menu-row:hover:not(:disabled) {
        background: none;
        color: var(--fg);
    }

    .nav-menu a.nav-menu-row:active,
    .nav-menu button.nav-menu-row:active { background: var(--fill-1); }

    .nav-menu .nav-menu-whatsnew:hover > svg:first-child { color: var(--brand-ink); }

    .nav-menu-chevron,
    .nav-pref-updown {
        display: block;
        flex: none;
        width: 20px;
        height: 20px;
        fill: none;
        stroke: currentColor;
        stroke-width: 1.6;
        stroke-linecap: round;
        stroke-linejoin: round;
        color: var(--fg-3);
    }

    .nav-menu .nav-pref-row:first-child { padding: 10px 12px 10px 16px; }

    .nav-pref-options button { padding: 0 10px; }

    .nav-pref-select {
        margin-right: 0;
        font-size: 17px;
    }

    .nav-menu .nav-menu-signout,
    .nav-menu .nav-menu-signout:hover { color: var(--danger); }

    .nav-install-hint { padding: 0 16px 12px 48px; }

    .nav-menu-version {
        margin-top: -4px;
        padding: 0;
        text-align: center;
    }

    /* ── Board: one column a page, its cards on the page itself ──
       No tray and no column header: the switcher above names the column, and the
       board's menu filters it. Cards sit 16px in from the edges. */
    .board {
        --column-rail: 16px;
        --card-inset: 14px;
        padding: 0;
        gap: 0;
        scroll-snap-type: x mandatory;
        -webkit-overflow-scrolling: touch;
        /* Whatever the board's own top, its switcher and the tab bar leave. */
        height: calc(100vh - var(--phone-nav-h) - var(--phone-board-title-h) - var(--phone-pager-h));
        height: calc(100dvh - var(--phone-nav-h) - var(--phone-board-title-h) - var(--phone-pager-h));
    }

    /* The board's own top (BoardPhoneBar), since the tab bar names no board: a row
       of floating buttons over a large title, the same pieces as Home's
       (.home-head). The row and the title are fixed heights, summed in
       --phone-board-title-h; the title is one line. */
    .board-phone-bar {
        display: block;
        padding: 0 1.25rem;
    }

    .board-phone-nav {
        position: relative;
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 0.5rem;
        height: 56px;
        margin: 0 -0.25rem;
    }

    /* Material without the blur: nothing scrolls under these. */
    .board-phone-back,
    .board-phone-action {
        height: 44px;
        border: 0;
        border-radius: var(--radius-pill);
        background: var(--material);
        box-shadow: var(--shadow-material);
        text-decoration: none;
    }

    .board-phone-back {
        display: flex;
        align-items: center;
        gap: 2px;
        min-width: 0;
        padding: 0 1rem 0 9px;
        color: var(--brand-ink);
        font-size: 17px;
        font-weight: 500;
    }

    .board-phone-back svg { flex: none; }

    .board-phone-back span {
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .board-phone-action {
        display: grid;
        place-items: center;
        flex: none;
        width: 44px;
        padding: 0;
        color: var(--fg);
        cursor: pointer;
    }

    .board-phone-action:active,
    .board-phone-action-active { color: var(--brand-ink); }

    /* Hangs from the menu button, thumb-sized rows. */
    .board-phone-menu {
        top: calc(100% + 4px);
        right: 0.25rem;
        width: min(280px, calc(100vw - 2rem));
    }

    /* The phone's body type, as the account sheet's rows. */
    .board-phone-menu .menu-row {
        min-height: 44px;
        font-size: 17px;
        line-height: 22px;
    }

    /* The large titles: a board's and Home's. */
    .board-phone-title,
    .home-head-title {
        margin: 2px 0 0;
        font-family: var(--font-display);
        font-size: 34px;
        font-weight: 700;
        line-height: 41px;
        letter-spacing: -0.022em;
        color: var(--fg);
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    .board-phone-sub,
    .home-head-date {
        margin: 1px 0 0;
        font-size: 13px;
        font-weight: 500;
        line-height: 18px;
        color: var(--fg-2);
    }


    /* Room on the right for the account (.board-header-home .board-user); the
       title's own 2px make the top 0.75rem, the face's line. */
    .home-head {
        display: block;
        padding: 0.625rem 4.5rem 0 1.25rem;
    }

    .column {
        width: 100vw;
        min-width: 100vw;
        flex: 0 0 auto;
        padding: 0;
        border-radius: 0;
        background: none;
        box-shadow: none;
        scroll-snap-align: start;
    }

    /* Only the deck's Show all / Show less stays, above it on the right. */
    .column-header {
        height: auto;
        justify-content: flex-end;
        padding: 0 var(--column-rail);
    }

    .column-header > :not(.column-deck-toggle) { display: none; }

    .column-cards {
        padding: 12px var(--column-rail) 16px;
        --card-gap: 10px;
    }

    .card { gap: 10px; }

    .card-title {
        font-size: 16px;
        line-height: 21px;
        letter-spacing: -0.01em;
    }

    .card .tag-chip {
        padding: 5px 10px 5px 8px;
        font-size: 13px;
        line-height: 18px;
    }

    .card-due {
        font-size: 13px;
        line-height: 18px;
    }

    .card-due::before {
        width: 15px;
        height: 15px;
    }

    /* While a card is held, the others step back and a line says where it can go:
       on a segment above, since the other columns are off screen. */
    .board-dragging .card:not(.card-ghost, .card-dragging) { opacity: 0.55; }

    .board-drag-hint {
        display: block;
        position: fixed;
        left: 50%;
        bottom: calc(var(--phone-nav-h) + 14px - 0.75rem);
        z-index: 150;
        margin: 0;
        padding: 8px 14px;
        border-radius: var(--radius-pill);
        background: var(--toast-bg);
        box-shadow: var(--shadow-xl);
        color: var(--toast-fg);
        font-size: 13px;
        font-weight: 500;
        line-height: 18px;
        white-space: nowrap;
        pointer-events: none;
        opacity: 0;
        transform: translate(-50%, 6px);
        transition: opacity var(--duration) var(--ease), transform var(--duration) var(--ease);
    }

    .board.board-dragging + .board-drag-hint {
        opacity: 1;
        transform: translate(-50%, 0);
    }

    /* Sized against the desktop header; here body already pads for the bar, and a
       large title above takes its height off (Home's is 4.5rem). */
    .board-loading,
    .empty-state { min-height: calc(100dvh - var(--phone-nav-h)); }

    .board-phone-bar ~ .empty-state { min-height: calc(100dvh - var(--phone-nav-h) - var(--phone-board-title-h)); }

    .home-head ~ .board-loading,
    .home-head ~ .empty-state { min-height: calc(100dvh - var(--phone-nav-h) - 4.5rem); }

    /* The phone's board while it loads: a large title and one column. */
    .board-skeleton { height: calc(100dvh - var(--phone-nav-h)); }

    .skeleton-head {
        height: var(--phone-board-title-h);
        justify-content: flex-end;
        padding: 0 1.25rem 14px;
    }

    .skeleton-columns { padding: 0 0.5rem 0.75rem; }

    .skeleton-column { max-width: none; }

    .skeleton-column + .skeleton-column { display: none; }

    .board-deleted { padding: 16px 1.25rem; }

    .board-deleted-name {
        font-size: 34px;
        line-height: 41px;
        letter-spacing: -0.022em;
    }

    /* ── The column switcher: a segmented control over the board ──
       A segment per column, name and count. The thumb is the track's ::before,
       placed by board-pagination.js from the board's scroll, so a swipe slides
       it; with more columns than fit, the track scrolls. */
    body:has(.board-pagination) { --phone-pager-h: calc(45px + 4px); }

    .board-pagination {
        display: flex;
        position: relative;
        height: 45px;
        margin: 4px 16px 0;
        padding: 2px;
        border-radius: var(--radius-pill);
        background: var(--fill-1);
        box-shadow: inset 0 0 0 0.5px var(--border-2);
        overflow-x: auto;
        scrollbar-width: none;
    }

    .board-pagination::-webkit-scrollbar { display: none; }

    .board-pagination::before {
        content: "";
        position: absolute;
        top: 2px;
        bottom: 2px;
        left: 0;
        width: var(--thumb-w, 0px);
        transform: translateX(var(--thumb-x, 0px));
        border-radius: var(--radius-pill);
        background: var(--bg-thumb);
        box-shadow: var(--shadow-sm);
    }

    /* Content-wide, sharing out what is left; never squeezed, so many columns
       scroll rather than shrink. A long name ends in an ellipsis. */
    .board-segment {
        position: relative;
        flex: 1 0 auto;
        max-width: 12rem;
        display: flex;
        align-items: center;
        justify-content: center;
        gap: 6px;
        padding: 0 12px;
        border: 0;
        border-radius: var(--radius-pill);
        background: none;
        color: var(--fg-2);
        font: inherit;
        font-size: 16px;
        letter-spacing: -0.01em;
        white-space: nowrap;
        cursor: pointer;
        transition: color var(--duration) var(--ease), background var(--duration) var(--ease);
    }

    .board-segment-name {
        min-width: 0;
        overflow: hidden;
        text-overflow: ellipsis;
        font-weight: 600;
    }

    .board-segment-count {
        font-weight: 500;
        color: var(--fg-2);
    }

    .board-segment-active { color: var(--fg); }

    /* While a card is dragged, every segment is a drop target for its column. */
    body:has(.board-dragging) .board-segment {
        outline: 1.5px dashed var(--border-3);
        outline-offset: -3px;
    }

    /* SortableJS puts the .card-ghost into whichever target is under the drag. */
    .board-segment:has(.card-ghost) {
        background: var(--brand-glow);
        color: var(--brand-ink);
        outline-color: var(--brand-ink);
    }

    /* board-pagination.js's onAdd moves a dropped card back before Blazor
       re-renders; this hides one if any path strands it here. */
    .board-segment .card {
        display: none;
    }

    /* ── Modal: full screen slide-up ── */

    .modal-backdrop {
        padding: 0;
        align-items: flex-end;
    }

    .modal-backdrop::before {
        /* No blur on a phone: backdrop-filter re-filters the whole viewport during the
           slide and dropped frames on weak GPUs. The scrim darkens a little more. */
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
        background: rgba(25, 21, 19, 0.58);
    }

    .modal {
        width: 100%;
        /* Not 100vh, which excludes the browser toolbar and pushed the sheet's header
           above the top edge. */
        max-height: 100%;
        border-radius: var(--radius) var(--radius) 0 0;
        animation: slideUpMobile 300ms var(--ease-out);
        /* Promoted before the first frame, so the slide is on the compositor from the start. */
        will-change: transform;
    }

    .modal-body { padding: 6px 17px 18px; }

    .modal-header { padding-inline: 20px 9px; }

    /* ── The card sheet: two heights, medium and large (docs/ui.md) ──
       Its size is fixed from the first frame, the large height, so content arriving
       mid-slide cannot change what slideUpMobile travels. Medium is the same sheet
       moved down on `translate`, its foot below the screen; js/card-dialog.js drags
       it and picks the height, and only `translate` ever moves. */
    /* The board shows above a medium sheet, dimmed as the draft has it. */
    .modal-backdrop:has(.card-modal)::before { background: var(--scrim); }

    .card-modal {
        --sheet-top: calc(env(safe-area-inset-top, 0px) + 0.75rem);
        position: relative;
        height: calc(100% - var(--sheet-top));
        border-radius: 38px 38px 0 0;
        box-shadow: var(--shadow-sheet);
        transition: translate 420ms var(--ease);
        will-change: transform, translate;
    }

    /* Its top at 35.5% of the screen (MEDIUM in card-dialog.js), in its own height's
       terms: H = screen - top, so 0.355 * screen - top = 35.5% of H - 0.645 * top.
       A drag anywhere moves it, so the body does not scroll here; scrolling opens it. */
    .card-modal[data-detent="medium"] {
        translate: 0 calc(35.5% - 0.645 * var(--sheet-top));
        touch-action: none;
    }

    .card-modal[data-detent="medium"] .modal-body { overflow: hidden; }

    .card-modal[data-detent="gone"] { translate: 0 100%; }

    .card-modal[data-dragging] { transition: none; }

    .card-bar {
        position: relative;
        min-height: 0;
        padding: 15px 9px 0 20px;
        touch-action: none;
    }

    .card-grabber {
        display: block;
        position: absolute;
        top: 6px;
        left: calc(50% - 18px);
        width: 36px;
        height: 5px;
        border-radius: 3px;
        background: color-mix(in oklab, var(--fg) 16%, transparent);
    }

    .card-bar .card-crumb { font-size: 14px; }

    .card-bar .modal-close { display: none; }

    .card-bar .share-button {
        width: 44px;
        height: 44px;
    }

    .card-done {
        display: inline-flex;
        align-items: center;
        min-height: 44px;
        padding: 0 8px;
        border: none;
        background: none;
        font: inherit;
        font-family: var(--font-body);
        font-size: 16px;
        font-weight: 600;
        color: var(--brand-ink);
        cursor: pointer;
    }

    .card-done:disabled { opacity: 0.4; }

    /* The properties first, where a medium sheet shows them. */
    .card-modal .modal-body {
        gap: 14px;
        padding: 6px 17px calc(var(--sheet-composer-h, 0px) + 18px);
    }

    .card-props { order: 1; }
    .composer-desc { order: 2; }
    .discussion { order: 3; }
    .card-delete { order: 4; }
    .card-note { order: 5; }

    /* The comment box is pinned to the sheet's foot, above the keyboard: out of
       the thread's flow (.card-modal is its containing block, not the scroller). */
    .card-modal:has(.comment-form) { --sheet-composer-h: 128px; }

    .card-modal .comment-form {
        position: absolute;
        z-index: 5;
        left: 0;
        right: 0;
        bottom: var(--keyboard-inset, 0px);
        padding: 12px 17px calc(12px + env(safe-area-inset-bottom, 0px));
        border-top: none;
        background: var(--bg-panel);
        box-shadow: 0 -0.5px 0 var(--border-2);
    }

    /* With no comments, nothing is left of the thread on the page. */
    .discussion:not(:has(.comment, .section-note)) .section-head { display: none; }

    .discussion:not(:has(.comment, .section-note)) .discussion-group {
        background: none;
        box-shadow: none;
    }

    /* 16px keeps iOS from zooming on focus. */
    .composer-title { font-size: 20px; }

    .composer-body,
    .comment-form textarea,
    .comment-edit textarea,
    .move-select { font-size: 16px; }

    .comment-form textarea { resize: none; }

    .card-props .tag-chip:not(.tag-chip-static) {
        min-height: 34px;
        padding: 6px 12px 6px 10px;
        font-size: 14px;
    }

    .card-props .assignee-chip {
        min-height: 32px;
        font-size: 14px;
    }

    .card-props .assignee-add { min-height: 32px; }

    .move-select { min-height: 36px; }

    .modal-footer {
        padding: 0 17px calc(12px + env(safe-area-inset-bottom, 0px));
        flex-wrap: wrap;
    }

    /* Large, as on every phone sheet (.btn-lg). */
    .modal-footer > :is(.btn-primary, .btn-secondary) {
        height: 44px;
        padding: 0 22px;
        font-size: 16px;
        line-height: 21px;
        letter-spacing: -0.01em;
    }

    .modal-close {
        width: 44px;
        height: 44px;
    }

    /* ── Touch targets ── */

    /* A small button keeps its size and takes a thumb-sized target, as .row-delete does. */
    .btn-sm::after {
        content: "";
        position: absolute;
        inset: -8px 0;
    }

    .column-add { min-height: 48px; }

    /* .card is not padded here: the whole tile is the target, and its padding
       lives in the board block above. */

    .assignee-add,
    .assignee-option {
        min-height: 44px;
    }

    /* 16px, or iOS zooms on focus. */
    .assignee-search {
        min-height: 44px;
        font-size: 16px;
    }

    .attach-button {
        min-width: 44px;
        min-height: 44px;
    }

    /* ── Toast ── */

    .toast-container {
        bottom: calc(1rem + var(--phone-nav-h));
        left: 1rem;
        right: 1rem;
        transform: none;
    }

    .toast {
        width: 100%;
        text-align: center;
    }

    /* ── The auth panel ── */

    .login-page {
        padding: 1rem;
    }

    .login-card {
        padding: 24px 20px;
    }

    /* 16px, or iOS zooms in on focus; 44px, the touch minimum. */
    .login-card form input:not([type="checkbox"]):not([type="radio"]),
    .login-card form textarea {
        min-height: 44px;
        font-size: 16px;
    }
}

@keyframes slideUpMobile {
    from { transform: translateY(100%); }
    to   { transform: translateY(0); }
}

/* The account's sheet appears without the slide (it still fades). The switcher's
   thumb follows the board's scroll, which board-pagination.js makes instant. */
@media (max-width: 599px) and (prefers-reduced-motion: reduce) {
    .nav-menu { transform: none; }

    .nav-tab,
    .board-segment { transition: none; }

    /* The card sheet changes height at once; a drag still follows the finger. */
    .card-modal { transition: none; }
}

/* ── Tablet (600px – 959px) ── */

@media (min-width: 600px) and (max-width: 959px) {
    .board { padding: 0 1.15rem 1.15rem; }

    .board-deleted { padding-inline: 1.15rem; }

    .board-titlebar { padding-inline: 1.15rem; }

    /* No room for the people beside the views and the primary. */
    .board-titlebar-faces { display: none; }

    .nav-tabs {
        display: flex;
        gap: 0.15rem;
    }

    .nav-tab {
        padding: 0 14px;
        border-radius: var(--radius-pill);
        color: var(--fg-2);
        font-size: 13.5px;
        font-weight: 500;
        text-decoration: none;
        display: flex;
        align-items: center;
        height: 34px;
        line-height: 20px;
    }

    .nav-tab svg { display: none; }

    .nav-tab .nav-tab-label { display: inline; }

    .nav-tab:hover { color: var(--fg); }

    .nav-tab-here {
        background: var(--brand-surface);
        color: var(--brand-ink);
        font-weight: 600;
    }

    .column {
        width: calc(50vw - 1.75rem);
        min-width: calc(50vw - 1.75rem);
    }
}

/* ── Desktop (960px and up) ── */

@media (min-width: 960px) {
    :root {
        --header-h: 0px;
        --sidebar-w: 244px;
    }

    /* The header becomes a sheet of material floating on the left edge: the
       wordmark, the search, the destinations, the boards, and the account at the
       foot. The page sits beside it. */
    body:has(.board-header) { padding-left: var(--sidebar-w); }

    .board-header {
        position: fixed;
        inset: 8px auto 8px 8px;
        width: calc(var(--sidebar-w) - 16px);
        height: auto;
        flex-direction: column;
        align-items: stretch;
        justify-content: flex-start;
        gap: 0.2rem;
        padding: 0.85rem 0.55rem 0.55rem;
        border-radius: var(--radius-lg);
        box-shadow: var(--shadow-material);
    }

    .board-header::before { border-radius: inherit; }

    .nav-lead {
        flex: 0 0 auto;
        padding: 0 0.45rem 0.55rem;
    }

    .nav-lead-sep,
    .nav-boards,
    .nav-board-title { display: none; }

    /* The search above the destinations; the markup keeps the phone bar's order. */
    .nav-search {
        order: 1;
        flex: 0 0 auto;
        margin: 0 0 0.45rem;
    }

    .nav-tabs { order: 2; }

    .nav-side-boards { order: 3; }

    .board-user { order: 4; }

    /* A well like any field, but a button: the query lives in the palette it opens. */
    .nav-search-compact { display: none; }

    .nav-search-field {
        display: flex;
        align-items: center;
        gap: 8px;
        width: 100%;
        height: 34px;
        padding: 0 12px;
        border: 0;
        border-radius: var(--radius-btn);
        background: var(--fill-1);
        box-shadow: var(--shadow-recessed);
        color: var(--fg-3);
        font: inherit;
        font-size: 14px;
        cursor: text;
        transition: background var(--duration) var(--ease);
    }

    .nav-search-field:hover { background: var(--fill-2); }

    .nav-search-field-text {
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .nav-tabs,
    .nav-side-boards {
        display: flex;
        flex-direction: column;
        gap: 2px;
    }

    /* A row: 34px, its mark, its word, and a board's count at the end. */
    .nav-tab,
    .nav-side-board,
    .nav-side-new {
        display: flex;
        align-items: center;
        gap: 10px;
        min-height: 34px;
        padding: 0 10px;
        border-radius: var(--radius-xs);
        color: var(--fg);
        font-size: 13.5px;
        font-weight: 500;
        line-height: 20px;
        text-decoration: none;
        transition: background var(--duration) var(--ease);
    }

    .nav-tab svg {
        width: 18px;
        height: 18px;
        color: var(--brand-ink);
    }

    .nav-tab .nav-tab-label { display: inline; }

    .nav-tab:hover,
    .nav-side-board:hover,
    .nav-side-new:hover { background: var(--fill-1); }

    /* Lit with the tint of the board on screen; a board row wears its own colour
       (the tokens are re-derived on the row from its inline --board-color). */
    .nav-tab-here,
    .nav-side-board-active,
    .nav-tab-here:hover,
    .nav-side-board-active:hover {
        background: color-mix(in oklab, var(--brand) 22%, transparent);
        font-weight: 600;
    }

    /* The list scrolls; the account stays at the foot. */
    .nav-side-boards {
        flex: 0 1 auto;
        min-height: 0;
        overflow-y: auto;
        scrollbar-width: thin;
    }

    .nav-side-head {
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 18px 2px 6px 10px;
    }

    .nav-side-head p {
        margin: 0;
        font-size: 12px;
        font-weight: 500;
        line-height: 16px;
        color: var(--fg-2);
    }

    .nav-side-add {
        display: grid;
        place-items: center;
        width: 24px;
        height: 24px;
        border-radius: var(--radius-pill);
        color: var(--fg);
    }

    .nav-side-add:hover { background: var(--fill-1); }

    /* The board's first letter on its colour, lit from above. */
    .nav-side-board-mark {
        flex: 0 0 auto;
        display: grid;
        place-items: center;
        width: 20px;
        height: 20px;
        border-radius: 6px;
        background: linear-gradient(rgb(255 255 255 / 0.18), rgb(255 255 255 / 0)), var(--brand);
        box-shadow: var(--shadow-sm);
        color: var(--brand-contrast);
        font-size: 11px;
        font-weight: 700;
        line-height: 11px;
    }

    .nav-side-board-name {
        flex: 1;
        min-width: 0;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .nav-side-pin {
        flex: none;
        color: var(--fg-2);
    }

    .nav-side-count {
        flex: none;
        color: var(--fg-2);
        font-size: 12px;
        line-height: 16px;
        font-variant-numeric: tabular-nums;
    }

    .nav-side-board-active .nav-side-pin,
    .nav-side-board-active .nav-side-count { color: var(--fg); }

    .nav-side-new { color: var(--fg-2); }

    .nav-side-new svg { flex: none; }

    .board-user {
        flex: 0 0 auto;
        margin-top: auto;
        padding-top: 0.45rem;
    }

    /* The account: face, name over username, and the menu's chevron. */
    .nav-menu-toggle {
        width: 100%;
        height: 48px;
        gap: 10px;
        padding: 8px;
        border: none;
        border-radius: var(--radius-sm);
        background: transparent;
        color: var(--fg-2);
    }

    .nav-menu-toggle:hover,
    .nav-menu-toggle[aria-expanded="true"] { background: var(--fill-1); }

    .nav-menu-toggle .nav-avatar {
        width: 30px;
        height: 30px;
    }

    .board-user-who {
        display: flex;
        flex: 1 1 auto;
        flex-direction: column;
        min-width: 0;
        text-align: left;
    }

    .board-user-name,
    .board-user-handle {
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .board-user-name {
        color: var(--fg);
        font-size: 14px;
        font-weight: 600;
        line-height: 20px;
    }

    .board-user-handle {
        color: var(--fg-2);
        font-size: 12px;
        line-height: 16px;
    }

    .board-user-who:not(:has(.board-user-name)) .board-user-handle {
        color: var(--fg);
        font-size: 14px;
        font-weight: 600;
        line-height: 20px;
    }

    .nav-menu-toggle-caret { opacity: 1; }

    .nav-menu-toggle[aria-expanded="true"] .nav-menu-toggle-caret { transform: none; }

    /* On the face, not the far end of the row. */
    .nav-menu-dot {
        left: 30px;
        right: auto;
        top: 4px;
    }

    /* Opens upwards from the account, over the page. */
    .nav-menu {
        top: auto;
        bottom: calc(100% + 0.5rem);
        left: 0;
        right: auto;
        transform: translateY(8px);
    }

    .nav-menu-open { transform: none; }

    /* The sidebar's foot it opens from already says who this is. */
    .nav-menu-account { display: none; }

    .nav-menu-account + .nav-menu-group { margin-top: 0; }

    .nav-menu-account + .nav-menu-group::before { display: none; }

    /* 22px from the sidebar's edge and the window's, as the canvas has it; the
       title's own 2px in. */
    .board-titlebar { padding-inline: 16px 24px; }

    .board,
    .board-deleted { padding-inline: 14px 22px; }

    /* A deleted board's name sits where its title bar had it. */
    .board-deleted-name { padding-left: 2px; }

    /* Columns share the spare width. The floor keeps a crowded board scrolling; the
       ceiling stops near a 70-character description line. */
    .column {
        flex: 1 1 320px;
        width: auto;
        min-width: 320px;
        max-width: 520px;
    }
}

/* ── The inspector (1100px and up) ── */

/* A board's card dialog docks on the right as an inspector: a panel floating 8px
   off the window's edges, no scrim, the board and its title bar making room and
   staying live, and clicking another card opens it in the same panel (CardModal
   re-initialises per card; the click blurs the field first, which saves it). The
   calendar keeps the centred dialog, phones the sheet. Below 1100px a panel
   would leave the board one column. */
@media (min-width: 1100px) {
    :root { --inspector-w: 380px; }

    .modal-backdrop-dockable {
        inset: 8px 8px 8px auto;
        width: var(--inspector-w);
        padding: 0;
        align-items: stretch;
    }

    .modal-backdrop-dockable::before { display: none; }

    /* Focused for Escape, not a control: docked, its ring would frame the panel. */
    .modal-backdrop-dockable:focus { outline: none; }

    .modal-backdrop-dockable .modal {
        width: 100%;
        max-height: none;
        border-radius: var(--radius-lg);
        box-shadow: var(--shadow-lg);
        animation: none;
    }

    /* The panel's width, its 8px off the edge and a 22px gap before it. */
    .board:has(~ .modal-backdrop-dockable),
    body:has(.modal-backdrop-dockable) .board-titlebar {
        padding-right: calc(var(--inspector-w) + 30px);
    }

    /* The panel has the card's people; the views, the primary and the title need the room. */
    body:has(.modal-backdrop-dockable) .board-titlebar-faces { display: none; }

    /* The columns narrow to share what the panel leaves (the draft's 247px at 1440),
       down to a floor past which the board scrolls sideways. */
    .board:has(~ .modal-backdrop-dockable) > .column { min-width: 240px; }

    /* The card beside it, ringed; the ring outlasts the hover lift. Only here: under
       a phone's sheet the change would animate the board (mobile.spec.js). */
    .card.card-open,
    .card.card-open:hover {
        background: color-mix(in oklab, var(--brand) 13%, var(--bg-1));
        box-shadow: var(--shadow-card-hover), 0 0 0 2px var(--brand);
    }
}

@media (min-width: 1100px) and (prefers-reduced-motion: no-preference) {
    .modal-backdrop-dockable .modal { animation: inspectorIn 240ms var(--ease-out); }
}

@keyframes inspectorIn {
    from { opacity: 0; transform: translateX(16px); }
    to   { opacity: 1; transform: none; }
}


/* ── Signup / local login / invites ── */

.signup-handle-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.signup-handle-row input {
    flex: 1;
    min-width: 0;
}

.signup-handle-domain {
    color: var(--fg-2);
    font-size: 14px;
    font-weight: 500;
    white-space: nowrap;
}

/* The panel's foot: where else to go, under a hairline. */
.login-card .login-links {
    padding-top: 16px;
    border-top: 1px solid var(--border-2);
    font-size: 12px;
    line-height: 16px;
    font-weight: 500;
    color: var(--fg-2);
}

.login-alt-link {
    color: var(--brand-ink);
    font-weight: 500;
    text-decoration: none;
}

.login-alt-link:hover {
    text-decoration: underline;
}

.invite-code {
    font-size: 17px;
    font-weight: 600;
    line-height: 22px;
    letter-spacing: 0.06em;
}

/* ── Access requests ── */

.hp-field {
    position: absolute !important;
    left: -9999px !important;
    width: 1px;
    height: 1px;
    opacity: 0;
    pointer-events: none;
}

/* A request's own address: shown cut in the middle, shared whole. */
.request-link {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 6px 6px 14px;
    border-radius: var(--radius-sm);
    background: var(--fill-1);
    font-size: 13px;
    line-height: 18px;
    text-align: left;
}

.request-link > span {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.request-link .share-button {
    width: 32px;
    height: 32px;
    justify-content: center;
    padding: 0;
    background: var(--material);
    box-shadow: var(--shadow-material);
    color: var(--fg);
    -webkit-backdrop-filter: blur(14px);
    backdrop-filter: blur(14px);
}

.login-card .request-keep-link {
    font-size: 12px;
    line-height: 16px;
    font-weight: 500;
    color: var(--fg-3);
}

.login-card > .invite-code {
    padding: 10px 14px;
    border-radius: var(--radius-sm);
    background: var(--fill-1);
}

/* Who OAuth says this is, and the way out if it is the wrong account. */
.access-identity {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 12px 8px 8px;
    border-radius: var(--radius-sm);
    background: var(--fill-1);
    text-align: left;
}

.access-identity-face {
    display: grid;
    place-items: center;
    flex: none;
    width: 28px;
    height: 28px;
    border: 2px solid var(--bg-1);
    border-radius: 50%;
    background: var(--brand-glow);
    color: var(--brand-ink);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: -0.03em;
}

.access-identity-text {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-width: 0;
    font-size: 11px;
    line-height: 14px;
    font-weight: 600;
    color: var(--fg-2);
}

.access-identity-handle {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 14px;
    line-height: 19px;
    letter-spacing: -0.006em;
    color: var(--fg);
}

.access-identity .login-alt-link {
    font-size: 12px;
    line-height: 16px;
}

.access-pending {
    padding: 12px 14px;
    border-radius: var(--radius-sm);
    background: var(--brand-glow);
    color: var(--fg);
    font-size: 13px;
    line-height: 18px;
}

/* ── Auth tabs ── */

/* A segmented control: the chosen page is a raised thumb on a recessed track. */
.auth-tabs {
    display: flex;
    padding: 2px;
    border-radius: var(--radius-pill);
    background: var(--fill-1);
}

.auth-tab {
    flex: 1;
    display: grid;
    place-items: center;
    height: 28px;
    padding: 0 14px;
    border-radius: var(--radius-pill);
    font-size: 13.5px;
    font-weight: 600;
    letter-spacing: -0.003em;
    color: var(--fg-2);
    text-decoration: none;
    transition: color var(--duration) var(--ease);
}

.auth-tab:hover,
.auth-tab-active {
    color: var(--fg);
}

.auth-tab-active {
    background: var(--bg-thumb);
    box-shadow: var(--shadow-sm);
}

/* ── Auth page polish ── */

/* Left-aligned on purpose in the centred panel: a sentence to read, not more
   intro copy. */
.auth-note {
    display: flex;
    gap: 10px;
    align-items: flex-start;
    padding: 12px 14px;
    border-radius: var(--radius-sm);
    background: var(--brand-glow);
    text-align: left;
}

.auth-note-icon {
    flex-shrink: 0;
    width: 18px;
    height: 18px;
    color: var(--brand-ink);
}

.auth-note-body {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.auth-note-title {
    margin: 0;
    font-size: 13px;
    line-height: 18px;
    font-weight: 600;
    color: var(--fg);
}

.auth-note-text {
    margin: 0;
    font-size: 12px;
    line-height: 16px;
    color: var(--fg-2);
}

.auth-note .login-alt-link {
    margin-top: 4px;
    font-size: 12px;
    line-height: 16px;
}

.form-step {
    display: flex;
    flex-direction: column;
    gap: 16px;
    margin: 0;
    padding: 16px 0 0;
    border: none;
    border-top: 1px solid var(--border-1);
}

.form-step:first-of-type {
    border-top: none;
    padding-top: 0;
}

.form-step-title {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0;
    font-size: 13px;
    line-height: 18px;
    font-weight: 600;
    color: var(--fg);
}

.form-step-num {
    display: grid;
    place-items: center;
    width: 20px;
    height: 20px;
    flex-shrink: 0;
    border-radius: 50%;
    background: var(--brand-glow);
    color: var(--brand-ink);
    font-size: 11px;
    font-weight: 700;
}

/* Plays once per load and per auth page switch. js/auth-enter.js drives the
   data-auth state machine: the card's nodes are created more than once, and a
   plain animation replayed on each. */
@keyframes auth-card-in {
    from { opacity: 0; transform: translateY(12px); }
    to   { opacity: 1; transform: none; }
}

/* Hidden before the entrance, so the discarded prerendered card never animates;
   only with js, so without it the card is simply visible. data-auth="done"
   matches nothing, so later node churn can't restart it. */
html.js[data-auth="pending"] .login-card,
html.js[data-auth="pending"] .login-card > * {
    opacity: 0;
}

html[data-auth="playing"] .login-card {
    animation: auth-card-in 0.28s var(--ease) both;
}

@media (prefers-reduced-motion: reduce) {
    html[data-auth="playing"] .login-card {
        animation: none;
    }
}

/* ── Brand mark ── */

.brand-mark {
    width: 28px;
    height: 28px;
    flex-shrink: 0;
    display: block;
}

.board-title-link {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
}

.login-card .brand-mark-lg {
    width: 48px;
    height: 48px;
}

/* ── Wind details and focus ── */

.brand-mark .brand-wind path {
    animation: wind-sway 3.4s ease-in-out infinite alternate;
    transform-box: fill-box;
}

.brand-mark .brand-wind path:nth-child(2) { animation-delay: 0.5s; }
.brand-mark .brand-wind path:nth-child(3) { animation-delay: 1s; }

@keyframes wind-sway {
    from { transform: translateX(1.6px); }
    to   { transform: translateX(-1.6px); }
}

/* One brand focus ring. No border-radius: it would reshape pill and circle controls. */
button:focus-visible,
a:focus-visible,
[role="button"]:focus-visible {
    outline: 2px solid var(--brand-ink);
    outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
    .brand-mark .brand-wind path,
    .spinner::before,
    .spinner::after {
        animation: none;
    }
}

/* ── Texture and entrances ── */

/* Fine grain over everything; fixed and non-interactive. */
body::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: 9000;
    pointer-events: none;
    opacity: 0.022;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}

[data-theme="dark"] body::before {
    opacity: 0.04;
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) body::before {
        opacity: 0.04;
    }
}

@keyframes rise-in {
    from { opacity: 0; transform: translateY(14px); }
    to   { opacity: 1; transform: none; }
}

/* The columns are animated from JS (the board page calls it once its data has
   arrived), never by a CSS rule: the board is built up to three times per load,
   and a creation-keyed animation raced that churn. This only hides them until
   then, and only with js. The timings live here; the JS reads them off :root. */
:root {
    --entrance-duration: 450ms;
    --entrance-lead: 30ms;
    --entrance-stagger: 60ms;
    --entrance-rise: 14px;
    /* Home's entrance may use a spring; the board's may not. A drag springs. */
    --home-stagger: 35ms;
    --home-pop-duration: 420ms;
    --ease-spring: cubic-bezier(0.34, 1.45, 0.64, 1);
}

html.js:not([data-board-entered]) .board > .column {
    opacity: 0;
}

/* Home and calendar parts, hidden until auth-enter.js animates them like the
   columns; there is no CSS entrance rule for them on purpose. */
html.js:not([data-home-entered]) .home-title,
html.js:not([data-home-entered]) .home-deleted-title,
html.js:not([data-home-entered]) .foryou-card,
html.js:not([data-home-entered]) .home-tiles > .board-tile,
html.js:not([data-home-entered]) .home-empty,
html.js:not([data-home-entered]) .home-deleted-row {
    opacity: 0;
}

html.js:not([data-calendar-entered]) .calendar-week,
html.js:not([data-calendar-entered]) .calendar-mini-week,
html.js:not([data-calendar-entered]) .timeline-row,
html.js:not([data-calendar-entered]) .calendar-agenda-day {
    opacity: 0;
}

/* Gated on the card's own state, so both halves of the entrance stay in step. */
html[data-auth="playing"] .login-card > * {
    animation: rise-in 0.4s var(--ease-out) both;
}

html[data-auth="playing"] .login-card > :nth-child(1) { animation-delay: 0.05s; }
html[data-auth="playing"] .login-card > :nth-child(2) { animation-delay: 0.1s; }
html[data-auth="playing"] .login-card > :nth-child(3) { animation-delay: 0.15s; }
html[data-auth="playing"] .login-card > :nth-child(4) { animation-delay: 0.2s; }
html[data-auth="playing"] .login-card > :nth-child(n+5) { animation-delay: 0.25s; }

@media (prefers-reduced-motion: reduce) {
    html[data-auth="playing"] .login-card > * {
        animation: none;
    }
}
