/* ==========================================================================
   otterly.social

   Mobile-first: every rule outside a media query describes the small screen.
   Breakpoints add capability rather than taking it away.

     (base)   phones, 360–639px
     ≥640px   large phones and small tablets — two-column forms
     ≥900px   the calendar month grid and the queue table become usable
     ≥1024px  sidebar replaces the bottom tab bar; composer gains a preview column

   Self-hosted only: system font stack, inline SVG, no webfonts or CDN, so a
   strict content security policy needs no exceptions.

   Colour comes from the design system's Sea palette. The raw scale is declared
   once below and everything else is an alias onto it — no component holds a hex
   value except the three platform brand colours, which are not ours to choose.
   ========================================================================== */

:root {
    /* ---- Sea — brand ---------------------------------------------------- */
    --sea-900: #0c3a4a;
    --sea-800: #0f4a5e;
    --sea-700: #146b82;
    --sea-600: #1a839e;
    --sea-500: #2596b3;
    --sea-400: #4db3cc;
    --sea-300: #85cfe0;
    --sea-200: #bfe6ee;
    --sea-100: #e2f5f8;
    --sea-050: #f2fbfc;

    /* ---- Sand — otter warmth, used sparingly ---------------------------- */
    --sand-600: #d9a441;
    --sand-500: #f2c98a;
    --sand-300: #f7ddb0;
    --sand-100: #fbeed8;

    /* ---- Fur — the mascot only, never UI chrome ------------------------- */
    --fur-dark: #6b4a35;
    --fur: #8a6244;
    --fur-light: #a67c52;

    /* ---- Surfaces and ink ----------------------------------------------- */
    --bg: #f4f8f7;
    --surface: #ffffff;
    --surface-2: #eef4f2;
    --border: #dde9e6;
    --text: #10262c;
    --text-dim: #5b7472;

    /* Sea-500 is the brand, and it is what fills a badge mark, rings today's
       cell and marks the active tab. It is NOT what sits under white label
       text: #fff on #2596b3 is 3.4:1, below the 4.5:1 floor for body-sized
       text. Sea-700 is the same hue at 6.1:1, so every text-bearing brand
       surface uses it. See docs/plan/09-ui.md. */
    --accent: var(--sea-700);
    --accent-text: #ffffff;
    --accent-quiet: var(--sea-100);
    --accent-mark: var(--sea-500);

    /* ---- Semantic — never colour alone, always paired with a label ------- */
    --ok: #1a8f5a;
    --ok-bg: #e1f6ea;
    /* The design's #9c6b00 lands at 4.2:1 on its own tint, just under the floor
       for the 12px bold pill label. Darkened one step to clear it. */
    --warn: #8a5a00;
    --warn-bg: #fdf1d6;
    --bad: #b23636;
    --bad-bg: #fbe6e6;
    --busy: #5b57c9;
    --busy-bg: #eceafc;

    /* ---- Shape ---------------------------------------------------------- */
    --radius: 12px;
    --radius-sm: 8px;
    --gap: 1rem;

    /* Never below 44px: the accepted floor for a reliable touch target. */
    --tap: 44px;

    /* One soft shadow. No glow, no gradients. */
    --shadow: 0 1px 2px rgb(16 24 23 / 6%), 0 2px 8px rgb(16 24 23 / 5%);

    --header-h: 56px;
    --bottomnav-h: 60px;
}

/* The palette ships light only. This is derived from it: the same hues, with
   surfaces pulled down through sea-900 and the accent lifted to sea-400 so it
   still carries dark text at 6.8:1. */
@media (prefers-color-scheme: dark) {
    :root {
        --bg: #071e26;
        --surface: #0d2f3b;
        --surface-2: #14404f;
        --border: #1d5164;
        --text: #e6f4f8;
        --text-dim: #9fbdc7;

        --accent: var(--sea-400);
        --accent-text: #06222c;
        --accent-quiet: #14404f;
        --accent-mark: var(--sea-400);

        --ok: #6ddc9d;
        --ok-bg: #10331f;
        --warn: #f0c26b;
        --warn-bg: #3a2d0d;
        --bad: #f08d8d;
        --bad-bg: #3a1717;
        --busy: #a9a6f0;
        --busy-bg: #201e46;

        --shadow: 0 1px 2px rgb(0 0 0 / 40%);
    }
}

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

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

body {
    margin: 0;
    background: var(--bg);
    color: var(--text);
    font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    /* Body: 16/24 · 400. */
    font-size: 16px;
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
}

h1, h2, h3 { line-height: 1.25; margin: 0; }
p { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }

a { color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: 2px; }

/* A visible focus ring everywhere. Keyboard users must always be able to see where they are. */
:focus-visible {
    outline: 3px solid var(--accent-mark);
    outline-offset: 2px;
    border-radius: 4px;
}

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

/* ---------- Shell ------------------------------------------------------- */

.shell { min-height: 100dvh; }

.shell__main {
    padding: var(--gap);
    /* Clear the fixed bottom bar, plus the iOS home indicator. */
    padding-bottom: calc(var(--bottomnav-h) + env(safe-area-inset-bottom) + var(--gap));
    max-width: 100%;
}

.shell__main--bare { padding: 0; }

.page { display: flex; flex-direction: column; gap: 1.25rem; max-width: 56rem; margin: 0 auto; }
.page--wide { max-width: 72rem; }
.page__head { display: flex; flex-direction: column; gap: 0.25rem; }
.page__head--row { flex-direction: row; align-items: flex-start; justify-content: space-between; gap: var(--gap); }

/* Page title: 24/28 · 700. */
.page__title { font-size: 1.5rem; line-height: 1.17; font-weight: 700; letter-spacing: -0.01em; }

/* The Meta size, but not its weight: 600 is for a data line like "Fri · 9:00 · Mastodon", where
   the boldness separates fields. A sentence of prose set in bold 13px just looks shouted. */
.page__sub { color: var(--text-dim); font-size: 0.8125rem; line-height: 1.38; }
.page-loading { padding: 2rem; text-align: center; color: var(--text-dim); }

/* ---------- Brand ------------------------------------------------------- */

.ottermark { display: block; flex: none; }

/* The full-body otter, for empty calendars and first-connection moments only. It never
   appears in dense UI — the badge mark carries the brand in lists and cards. */
.mascot { display: block; width: 160px; height: 160px; }

@media (min-width: 640px) {
    .mascot { width: 200px; height: 200px; }
}

.wordmark { font-weight: 800; letter-spacing: -0.02em; line-height: 1; }
.wordmark__tld { color: var(--sea-500); }

/* ---------- App bar ----------------------------------------------------- */

.appbar {
    position: sticky;
    top: 0;
    z-index: 20;
    display: flex;
    align-items: center;
    gap: 0.75rem;
    height: var(--header-h);
    padding: 0 var(--gap);
    padding-top: env(safe-area-inset-top);
    background: var(--sea-900);
    color: #fff;
}

/* min-height, not just content size: this is a link, so it needs a real target. */
.appbar__brand {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    min-height: var(--tap);
    padding-right: 0.25rem;
    color: #fff;
    text-decoration: none;
    flex: none;
}

.appbar__wordmark { font-size: 1rem; font-weight: 700; letter-spacing: -0.01em; }

/* Sea-500 is too dark to read on the sea-900 bar; the same accent one step lighter is not. */
.appbar .wordmark__tld { color: var(--sea-300); }

/* The wordmark is decoration; on the narrowest phones the space switcher needs the room. */
@media (max-width: 400px) {
    .appbar__wordmark { display: none; }
}

.appbar__signout { margin-left: auto; display: flex; }

.switcher { flex: 1 1 auto; min-width: 0; }

.switcher__select {
    width: 100%;
    min-height: var(--tap);
    padding: 0.25rem 0.5rem;
    border-radius: var(--radius-sm);
    border: 1px solid rgb(255 255 255 / 25%);
    background: rgb(255 255 255 / 12%);
    color: #fff;
    /* 16px exactly. Anything smaller and iOS Safari zooms the whole page when the picker opens,
       which leaves the user zoomed in on a header they then have to pinch back out of. */
    font-size: 16px;
    /* Truncate rather than widen: a long space name must not push the sign-out button off-screen. */
    text-overflow: ellipsis;
}

.switcher__select option { color: var(--text); background: var(--surface); }

/* ---------- Navigation -------------------------------------------------- */

.bottomnav {
    position: fixed;
    inset: auto 0 0 0;
    z-index: 20;
    display: flex;
    justify-content: space-around;
    background: var(--surface);
    border-top: 1px solid var(--border);
    padding-bottom: env(safe-area-inset-bottom);
}

.bottomnav__item {
    flex: 1 1 0;
    min-width: 0;
    min-height: var(--bottomnav-h);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    padding: 0.35rem 0.15rem;
    color: var(--text-dim);
    text-decoration: none;
    font-size: 0.7rem;
}

.bottomnav__icon { display: flex; line-height: 1; }
.bottomnav__label { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.bottomnav__item.active {
    color: var(--accent);
    font-weight: 700;
    /* Not colour alone: the active tab also carries a bar, for colour-blind users. */
    box-shadow: inset 0 3px 0 0 var(--accent-mark);
}

.sidenav { display: none; }

/* Line icons, sized in one place so nav and buttons agree. */
.icon { width: 1.15em; height: 1.15em; flex: none; stroke-width: 1.9; }

/* ---------- Cards and lists --------------------------------------------- */

.cardlist { display: flex; flex-direction: column; gap: 0.6rem; }

.card {
    display: flex;
    align-items: center;
    /* Wrapping rather than squeezing: on a 360px phone a card with a status pill and an action
       has no room left for a long name, and shrinking it to an ellipsis loses the one thing the
       row is about. The action drops to its own line instead. */
    flex-wrap: wrap;
    gap: 0.75rem;
    padding: 0.9rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
}

.card--link { text-decoration: none; color: inherit; min-height: var(--tap); }
.card--link:hover { border-color: var(--accent-mark); }
.card__main { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; flex: 1 1 auto; }
.card__title { font-weight: 650; overflow-wrap: anywhere; }
.card__meta { color: var(--text-dim); font-size: 0.8125rem; font-weight: 600; }

.postcard { flex-direction: column; align-items: stretch; }
.postcard__head { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }
.postcard__when { font-weight: 650; font-variant-numeric: tabular-nums; }

.postcard__text {
    margin: 0.5rem 0 0.75rem;
    overflow-wrap: anywhere;
    font-size: 0.95rem;
    /* Four lines, so status never gets pushed below the fold by a long post. */
    display: -webkit-box;
    -webkit-line-clamp: 4;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.postcard__targets { display: flex; flex-direction: column; gap: 0.4rem; }

.targetrow {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    padding-top: 0.4rem;
    border-top: 1px dashed var(--border);
}

.targetrow__link {
    margin-left: auto;
    font-size: 0.85rem;
    font-weight: 600;
    /* A real target: this is the link out to the published post, not incidental prose. */
    display: inline-flex;
    align-items: center;
    min-height: var(--tap);
    padding: 0 0.5rem;
}
.targetrow__error { color: var(--bad); font-size: 0.8rem; font-weight: 600; overflow-wrap: anywhere; }

.section { display: flex; flex-direction: column; gap: 0.6rem; }

/* Micro / label: 11/14 · 700 caps, tracked. */
.section__title {
    font-size: 0.6875rem;
    line-height: 1.27;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-dim);
}

.section__title--warn { color: var(--warn); }

.panel {
    display: flex;
    flex-direction: column;
    gap: var(--gap);
    padding: var(--gap);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

/* Panel title: 16/22 · 650. */
.panel__title { font-size: 1rem; line-height: 1.375; font-weight: 650; }

.notice {
    padding: var(--gap);
    background: var(--surface-2);
    border-radius: var(--radius);
    color: var(--text-dim);
    font-size: 0.9rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.notice__title { font-size: 0.95rem; color: var(--text); }

.rolelist { margin: 0; display: grid; grid-template-columns: auto 1fr; gap: 0.25rem 0.75rem; }
.rolelist dt { font-weight: 650; color: var(--text); }
.rolelist dd { margin: 0; }

/* ---------- Pills and badges -------------------------------------------- */

.pill {
    display: inline-flex;
    align-items: center;
    padding: 0.15rem 0.6rem;
    border-radius: 999px;
    font-size: 0.75rem;
    font-weight: 700;
    white-space: nowrap;
}

.pill--ok { background: var(--ok-bg); color: var(--ok); }
.pill--bad { background: var(--bad-bg); color: var(--bad); }
.pill--warn { background: var(--warn-bg); color: var(--warn); }
.pill--busy { background: var(--busy-bg); color: var(--busy); }
.pill--neutral { background: var(--surface-2); color: var(--text-dim); }
.pill--muted { background: transparent; color: var(--text-dim); border: 1px solid var(--border); }

.chanbadge { display: inline-flex; align-items: center; gap: 0.45rem; min-width: 0; font-size: 0.85rem; font-weight: 600; }

/*
    A rounded square carrying the platform's initial in its own brand colour. The letter is
    decorative — aria-hidden, with the channel's name as the adjacent text — so its contrast is
    judged as a UI component (3:1) rather than as text.
*/
.chanbadge__mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    flex: none;
    border-radius: 7px;
    background: var(--chan, var(--text-dim));
    color: #fff;
    font-size: 12px;
    font-weight: 800;
    line-height: 1;
}

.chanbadge--mastodon { --chan: #6364ff; }
.chanbadge--instagram { --chan: #c1367e; }
.chanbadge--facebook { --chan: #1877f2; }
.chanbadge--neutral { --chan: var(--text-dim); }

/* 18px in dense rows — post cards, queue table, previews. */
.chanbadge--small .chanbadge__mark { width: 18px; height: 18px; border-radius: 5px; font-size: 10px; }
.chanbadge--small { font-size: 0.8125rem; }

.chanbadge__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 12ch; }

.chanbadge--wrap { align-items: flex-start; }
.chanbadge--wrap .chanbadge__name {
    overflow: visible;
    white-space: normal;
    overflow-wrap: anywhere;
    max-width: none;
}

@media (min-width: 640px) {
    .chanbadge__name { max-width: 24ch; }
}

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

.field { display: flex; flex-direction: column; gap: 0.3rem; }
.field__label { font-size: 0.85rem; font-weight: 650; }
.field__hint { font-size: 0.78rem; color: var(--text-dim); }
.field__error { font-size: 0.8rem; color: var(--bad); }

.field__input {
    width: 100%;
    min-height: var(--tap);
    padding: 0.6rem 0.7rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--text);
    /* 16px exactly: iOS Safari zooms the page for anything smaller. */
    font-size: 16px;
    font-family: inherit;
}

.field__input--text { min-height: 8rem; resize: vertical; line-height: 1.5; }
.field__input:focus-visible { border-color: var(--accent-mark); }

.formgrid { display: grid; gap: var(--gap); grid-template-columns: 1fr; }

@media (min-width: 640px) {
    .formgrid--2 { grid-template-columns: 1fr 1fr; }
}

/* ---------- Buttons ----------------------------------------------------- */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    min-height: var(--tap);
    min-width: var(--tap);
    padding: 0.55rem 1.1rem;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    font: inherit;
    font-weight: 650;
    cursor: pointer;
    background: var(--surface-2);
    color: var(--text);
    /* Some buttons are anchors — "Compose a post", "Connect a channel". Without this they inherit
       the link underline and read as text pretending to be a button. */
    text-decoration: none;
}

.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.btn--primary { background: var(--accent); color: var(--accent-text); }
.btn--ghost { background: transparent; color: inherit; border-color: transparent; }
/* Outline plus tint, so it reads as destructive before it is pressed rather than only after. */
.btn--danger { background: var(--bad-bg); color: var(--bad); border-color: var(--bad); }
.btn--block { width: 100%; }
/* Visually small, but still a full-size target. The reduced height only applies from 1024px,
   where a mouse is the likely input — `pointer: coarse` is not reported consistently enough to
   be the thing this depends on. */
.btn--small { min-height: var(--tap); padding: 0.3rem 0.75rem; font-size: 0.85rem; }
.btn--icon { padding: 0.4rem; }
.btngroup { display: flex; gap: 0.25rem; align-items: center; flex: none; }

.actionbar {
    position: sticky;
    bottom: calc(var(--gap) * -1);
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    padding: 0.75rem 0;
    background: linear-gradient(to top, var(--bg) 70%, transparent);
}

.actionbar__hint { font-size: 0.8rem; color: var(--text-dim); text-align: center; }
.actionbar__row { display: flex; gap: 0.5rem; }
.actionbar__row .btn { flex: 1 1 0; }

/* ---------- Segmented controls and filter chips -------------------------- */

/*
    Two of a kind: a small run of mutually exclusive options. The active one is filled, so the
    choice survives being photocopied — the border alone would not.
*/
.segmented {
    display: inline-flex;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    overflow: hidden;
    background: var(--surface);
}

.segmented__item {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: var(--tap);
    min-width: var(--tap);
    padding: 0 0.85rem;
    border: 0;
    border-right: 1px solid var(--border);
    background: transparent;
    color: var(--text);
    font: inherit;
    font-size: 0.8125rem;
    font-weight: 600;
    cursor: pointer;
}

.segmented__item:last-child { border-right: 0; }
.segmented__item:hover { background: var(--surface-2); }
.segmented__item.is-active { background: var(--accent); color: var(--accent-text); font-weight: 650; }
.segmented__item.is-active:hover { background: var(--accent); }

.chips { display: flex; gap: 0.4rem; flex-wrap: wrap; }

.chip {
    display: inline-flex;
    align-items: center;
    /* The design draws these 26px tall. That is a mouse target, not a finger one, so on touch
       widths they keep the 44px floor and only compress where a pointer is likely. */
    min-height: var(--tap);
    padding: 0 0.85rem;
    border-radius: 999px;
    border: 1px solid var(--border);
    background: var(--surface);
    color: var(--text);
    font: inherit;
    font-size: 0.8125rem;
    font-weight: 650;
    cursor: pointer;
    white-space: nowrap;
}

.chip.is-active { background: var(--accent); border-color: var(--accent); color: var(--accent-text); }
.chip__count { margin-left: 0.35rem; opacity: 0.75; font-variant-numeric: tabular-nums; }

.toolbar { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.toolbar__end { margin-left: auto; }

/* Month is not an option below 900px — the grid is hidden there — so neither is the toggle.
   Offering a choice that does nothing is worse than not offering it. */
.monthonly { display: none; }

/* Full width on a phone, where it is the only thing on its row and a wide target is a kindness.
   Capped and pushed right from 640px, where a search box the width of the page looks like the
   page's subject rather than a filter on it. */
.search { position: relative; display: flex; flex: 1 1 12rem; min-width: 0; }
.search__input { padding-left: 2.1rem; }

@media (min-width: 640px) {
    .search { flex: 0 1 16rem; margin-left: auto; }
}

.search__icon {
    position: absolute;
    left: 0.7rem;
    top: 50%;
    translate: 0 -50%;
    color: var(--text-dim);
    pointer-events: none;
}

/* ---------- Channel picker ---------------------------------------------- */

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

.chanpicker__item {
    padding: 0.75rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
}

.chanpicker__item.is-selected { border-color: var(--accent-mark); }

.chanpicker__label {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    min-height: var(--tap);
    cursor: pointer;
}

.chanpicker__check { width: 22px; height: 22px; accent-color: var(--accent-mark); flex: none; }
.chanpicker__handle { margin-left: auto; font-size: 0.8rem; color: var(--text-dim); }
.chanpicker__meter { display: flex; align-items: center; gap: 0.5rem; margin-top: 0.5rem; }
.chanpicker__violation { font-size: 0.8rem; color: var(--bad); margin-top: 0.35rem; }

.counter { font-size: 0.8rem; color: var(--text-dim); font-variant-numeric: tabular-nums; flex: none; }
.counter--over { color: var(--bad); font-weight: 700; }
.counter__bar { flex: 1 1 auto; height: 6px; }

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

/*
    One card per selected channel, so "what will this actually look like over there" is answered
    before scheduling rather than after. Below 1024px it sits under the form; above, beside it.
*/
.previewlist { display: flex; flex-direction: column; gap: 0.6rem; }

.previewcard {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding: 0.75rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 11px;
    box-shadow: var(--shadow);
}

.previewcard__head { display: flex; align-items: center; gap: 0.45rem; }
.previewcard__handle { margin-left: auto; font-size: 0.7rem; color: var(--text-dim); }

.previewcard__media {
    width: 100%;
    height: 110px;
    object-fit: cover;
    border-radius: 7px;
    background: var(--sand-100);
}

.previewcard__text { font-size: 0.8125rem; line-height: 1.45; overflow-wrap: anywhere; white-space: pre-wrap; }
.previewcard__empty { font-size: 0.8125rem; color: var(--text-dim); font-style: italic; }

/* ---------- Media ------------------------------------------------------- */

.medialist { display: flex; flex-direction: column; gap: 0.6rem; }

.mediaitem {
    display: grid;
    /* On a narrow phone the description gets the full width: sharing a row with a 72px thumbnail
       leaves about 230px, which is not enough to see what you have written. Thumbnail and remove
       button share the first row instead.  */
    grid-template-columns: 72px 1fr;
    grid-template-areas:
        "thumb  remove"
        "body   body";
    gap: 0.6rem 0.75rem;
    align-items: start;
    padding: 0.7rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

.mediaitem__thumb { grid-area: thumb; }
.mediaitem__body { grid-area: body; }
.mediaitem__remove { grid-area: remove; justify-self: end; }

@media (min-width: 560px) {
    /* Enough room to sit side by side without squeezing the description. */
    .mediaitem {
        grid-template-columns: 72px 1fr auto;
        grid-template-areas: "thumb body remove";
    }
}

.mediaitem__thumb {
    width: 72px;
    height: 72px;
    object-fit: cover;
    border-radius: var(--radius-sm);
    background: var(--surface-2);
}

.mediaitem__body { min-width: 0; }

.field__hint--warn { color: var(--warn); }

.uploader {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.25rem;
    /* Generous, because on a phone this is the primary way an image gets in. */
    padding: 1.25rem 1rem;
    border: 2px dashed var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--text-dim);
    text-align: center;
    cursor: pointer;
    min-height: calc(var(--tap) * 2);
}

.uploader:hover, .uploader:focus-within { border-color: var(--accent-mark); }
.uploader.is-busy { opacity: 0.6; pointer-events: none; }
.uploader__icon { font-size: 1.6rem; line-height: 1; }
.uploader__label { font-weight: 650; color: var(--text); }
.uploader__hint { font-size: 0.78rem; }

/* The native control is hidden but still focusable, so the whole label acts as the target
   without losing keyboard access. It keeps the 16px size the rest of the form uses — a form
   control left at the browser default is 13px, and the rule is simpler to hold if nothing is
   exempt from it. */
.uploader__input { position: absolute; width: 1px; height: 1px; opacity: 0; font-size: 16px; }

.postcard__media {
    display: flex;
    gap: 0.4rem;
    margin-bottom: 0.6rem;
    overflow-x: auto;
    /* Contained scroll: a carousel of ten images must not widen the page. */
    scrollbar-width: thin;
}

.postcard__media img {
    width: 56px;
    height: 56px;
    object-fit: cover;
    border-radius: var(--radius-sm);
    flex: none;
    background: var(--sand-100);
}

/* ---------- Queue table -------------------------------------------------- */

/* Card list below 900px, table above. The columns only start to earn their keep once there is
   room for the post text to say something useful. */
.queuetable { display: none; }

/* ---------- Calendar ---------------------------------------------------- */

.agenda { display: flex; flex-direction: column; gap: 1rem; }
.agenda__day { display: flex; gap: 0.75rem; }

.agenda__date {
    flex: none;
    width: 3rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    font-size: 0.7rem;
    color: var(--text-dim);
    text-transform: uppercase;
}

.agenda__dom { font-size: 1.35rem; font-weight: 700; color: var(--text); line-height: 1.1; }
.agenda__items { flex: 1 1 auto; display: flex; flex-direction: column; gap: 0.5rem; min-width: 0; }

.agendaitem {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    padding: 0.7rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    text-decoration: none;
    color: inherit;
}

.agendaitem__time { font-weight: 650; font-variant-numeric: tabular-nums; font-size: 0.85rem; }
.agendaitem__text { overflow-wrap: anywhere; font-size: 0.9rem; }
.agendaitem__chips { display: flex; flex-wrap: wrap; gap: 0.25rem; }

/* The month grid is unusable below ~900px; the agenda above covers those widths. */
.monthgrid { display: none; }

/* The day a "+N more" opens. A popover would need positioning logic and would be wrong on a
   phone anyway; a panel under the grid works at every width. */
.daypanel { display: flex; flex-direction: column; gap: 0.6rem; }
.daypanel__head { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }

/* ---------- Auth pages -------------------------------------------------- */

.authpage {
    min-height: 100dvh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--gap);
    background: linear-gradient(160deg, var(--sea-900), var(--sea-700));
}

.authcard {
    width: 100%;
    max-width: 26rem;
    display: flex;
    flex-direction: column;
    gap: var(--gap);
    padding: 1.5rem;
    background: var(--surface);
    border-radius: var(--radius);
    box-shadow: 0 10px 40px rgb(0 0 0 / 25%);
}

.authcard__brand { display: flex; align-items: center; gap: 0.6rem; }
.authcard__brand h1 { font-size: 1.35rem; font-weight: 800; letter-spacing: -0.02em; }
.authcard__tagline { font-size: 0.8125rem; color: var(--text-dim); }
.authcard__lede { color: var(--text-dim); font-size: 0.9rem; }
.authcard__alt { font-size: 0.9rem; color: var(--text-dim); text-align: center; }

/* Switching between sign-in and register is a primary action on these screens, not incidental
   prose, so it gets a real target rather than the inline-link exemption. */
.authcard__alt a {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: var(--tap);
    padding: 0 0.5rem;
}
.authcard form { display: flex; flex-direction: column; gap: var(--gap); }

/* ---------- Feedback ---------------------------------------------------- */

.banner {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    padding: 0.7rem 0.85rem;
    border-radius: var(--radius-sm);
    font-size: 0.9rem;
    overflow-wrap: anywhere;
}

.banner--error { background: var(--bad-bg); color: var(--bad); }
.banner--ok { background: var(--ok-bg); color: var(--ok); }
.banner__icon { flex: none; }

.empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.5rem;
    padding: 2rem 1rem;
    text-align: center;
    color: var(--text-dim);
}

.empty__icon { font-size: 2.5rem; line-height: 1; }
.empty__title { font-size: 1.05rem; font-weight: 650; color: var(--text); }
.empty__body { max-width: 32ch; font-size: 0.9rem; }

.skeleton-list { display: flex; flex-direction: column; gap: 0.6rem; }

.skeleton {
    background: linear-gradient(90deg, var(--surface-2), var(--surface), var(--surface-2));
    background-size: 200% 100%;
    border-radius: var(--radius);
    animation: shimmer 1.4s linear infinite;
}

.skeleton--card { height: 5.5rem; }

@keyframes shimmer {
    from { background-position: 200% 0; }
    to { background-position: -200% 0; }
}

/* Respect the OS setting. Vestibular disorders make looping motion genuinely unpleasant. */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.001ms !important;
    }
}

#blazor-error-ui {
    display: none;
    position: fixed;
    inset: auto 0 0 0;
    z-index: 60;
    padding: 0.85rem var(--gap);
    padding-bottom: calc(0.85rem + env(safe-area-inset-bottom));
    background: var(--bad-bg);
    color: var(--bad);
    font-size: 0.9rem;
}

#blazor-error-ui .toast__dismiss { float: right; cursor: pointer; padding: 0 0.5rem; }
.toast__action { color: inherit; font-weight: 700; }

/* ==========================================================================
   ≥640px — more horizontal room
   ========================================================================== */

@media (min-width: 640px) {
    .shell__main { padding: 1.25rem; padding-bottom: calc(var(--bottomnav-h) + env(safe-area-inset-bottom) + 1.25rem); }
    .targetrow { flex-wrap: nowrap; }
    .actionbar__row .btn { flex: 0 0 auto; min-width: 11rem; }
}

/* ==========================================================================
   ≥900px — the month grid and the queue table become readable
   ========================================================================== */

@media (min-width: 900px) {
    .agenda { display: none; }

    /* List view: the same agenda the phone gets, chosen deliberately at a wide width. */
    .agenda--always { display: flex; }
    .monthonly { display: inline-flex; }
    .monthgrid.is-hidden { display: none; }

    .monthgrid {
        display: grid;
        grid-template-columns: repeat(7, minmax(0, 1fr));
        gap: 1px;
        background: var(--border);
        border: 1px solid var(--border);
        border-radius: 10px;
        overflow: hidden;
    }

    .monthgrid__dow {
        background: var(--surface-2);
        padding: 0.4rem 0.5rem;
        font-size: 0.6875rem;
        font-weight: 700;
        text-transform: uppercase;
        letter-spacing: 0.04em;
        color: var(--text-dim);
        text-align: center;
    }

    .monthgrid__cell {
        background: var(--surface);
        min-height: 80px;
        padding: 0.35rem 0.4rem;
        display: flex;
        flex-direction: column;
        gap: 0.2rem;
        overflow: hidden;
    }

    .monthgrid__cell.is-outside { background: var(--surface-2); color: var(--text-dim); }
    .monthgrid__cell.is-today { box-shadow: inset 0 0 0 2px var(--accent-mark); }
    .monthgrid__num { font-size: 0.72rem; font-weight: 650; font-variant-numeric: tabular-nums; }

    .monthgrid__post {
        display: block;
        width: 100%;
        border: 0;
        text-align: left;
        font: inherit;
        font-size: 0.66rem;
        font-weight: 600;
        line-height: 1.5;
        padding: 2px 5px;
        border-radius: 4px;
        background: var(--accent-quiet);
        color: var(--text);
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        cursor: pointer;
    }

    .monthgrid__post--busy { background: var(--busy-bg); color: var(--busy); }

    .monthgrid__more {
        align-self: flex-start;
        border: 0;
        background: transparent;
        font: inherit;
        font-size: 0.65rem;
        font-weight: 650;
        color: var(--text-dim);
        cursor: pointer;
        padding: 0 2px;
        text-decoration: underline;
    }

    /* ---- Queue table ---- */
    .queuecards { display: none; }

    .queuetable {
        display: flex;
        flex-direction: column;
        background: var(--surface);
        border: 1px solid var(--border);
        border-radius: 11px;
        overflow: hidden;
    }

    .queuetable__head,
    .queuetable__row {
        display: grid;
        grid-template-columns: 10rem 7rem 6rem 1fr 7rem;
        gap: 0.6rem;
        align-items: center;
        padding: 0.55rem 0.9rem;
    }

    .queuetable__head {
        font-size: 0.6875rem;
        font-weight: 700;
        text-transform: uppercase;
        letter-spacing: 0.04em;
        color: var(--text-dim);
        background: var(--surface-2);
        border-bottom: 1px solid var(--border);
    }

    /* No zebra striping: one hairline per row is enough to track across, and stripes fight the
       status pills for attention. */
    .queuetable__row { border-bottom: 1px solid var(--border); }
    .queuetable__row:last-child { border-bottom: 0; }

    .queuetable__when { font-size: 0.8125rem; font-weight: 600; font-variant-numeric: tabular-nums; }
    .queuetable__chans { display: flex; gap: 0.25rem; }
    .queuetable__text { font-size: 0.8125rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .queuetable__end { display: flex; justify-content: flex-end; align-items: center; gap: 0.25rem; }
    .queuetable__error { font-size: 0.72rem; color: var(--bad); font-weight: 600; text-align: right; }
}

/* ==========================================================================
   ≥1024px — sidebar replaces the bottom bar, composer gains its preview
   ========================================================================== */

@media (min-width: 1024px) {
    .shell {
        display: grid;
        grid-template-columns: 208px 1fr;
        grid-template-rows: auto 1fr;
        grid-template-areas:
            "header header"
            "nav    main";
    }

    .appbar { grid-area: header; }
    .sidenav { grid-area: nav; }
    .shell__main { grid-area: main; padding: 1.25rem 1.6rem; padding-bottom: 2rem; }
    .shell__main--bare { grid-column: 1 / -1; padding: 0; }

    .bottomnav { display: none; }

    .sidenav {
        display: flex;
        flex-direction: column;
        gap: 1px;
        padding: 10px 8px;
        border-right: 1px solid var(--border);
        background: var(--surface);
        position: sticky;
        top: var(--header-h);
        height: calc(100dvh - var(--header-h));
    }

    .sidenav__item {
        display: flex;
        align-items: center;
        gap: 9px;
        min-height: 36px;
        padding: 0 10px;
        border-radius: 7px;
        color: var(--text);
        text-decoration: none;
        font-size: 0.845rem;
        font-weight: 550;
    }

    .sidenav__item:hover { background: var(--surface-2); }
    .sidenav__item.active { background: var(--accent-quiet); color: var(--sea-900); font-weight: 700; }

    .btn--small { min-height: 34px; }
    .segmented__item { min-height: 30px; min-width: 30px; }
    .chip { min-height: 28px; padding: 0 0.7rem; font-size: 0.75rem; }

    /* On a phone the switcher takes the free space; on a wide header that just makes a very long
       control with nothing in it. */
    .switcher { flex: 0 1 15rem; }
    .actionbar { position: static; background: none; padding: 0; }
    .actionbar .btn--block { width: auto; min-width: 12rem; }

    /* The composer's two columns, with the preview pinned while the form scrolls. */
    .composer { display: grid; grid-template-columns: 1fr 320px; gap: 1.4rem; align-items: start; }
    .composer__preview { position: sticky; top: calc(var(--header-h) + 1.25rem); }
}

@media (prefers-color-scheme: dark) and (min-width: 1024px) {
    .sidenav__item.active { color: var(--sea-100); }
}
