/*  APRS Connect - marketing site
    Palette comes from the app's brand: charcoal ground, amber accent, cream type.
    Kept as one hand-written stylesheet on purpose: no build step, no external requests. */

:root {
    --charcoal: #201e1d;
    --charcoal-deep: #171514;
    --charcoal-lift: #2a2725;
    --amber: #d17d0c;
    --amber-bright: #eb9420;
    --amber-deep: #874d05;
    --cream: #f8e3c2;
    --cream-dim: rgba(248, 227, 194, 0.68);
    --cream-faint: rgba(248, 227, 194, 0.14);
    --ok: #6fbf73;
    --err: #e0705f;

    --wrap: 68rem;
    --radius: 14px;

    --sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

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

/* The hidden attribute must win over any class that sets display (.btn and friends would
   otherwise resurrect a script-hidden button). */
[hidden] { display: none !important; }

html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
    * { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}

body {
    margin: 0;
    background: var(--charcoal);
    color: var(--cream);
    font-family: var(--sans);
    font-size: 17px;
    line-height: 1.65;
    -webkit-font-smoothing: antialiased;
}

.wrap {
    width: 100%;
    max-width: var(--wrap);
    margin: 0 auto;
    padding: 0 1.5rem;
}

a { color: var(--amber-bright); text-decoration-thickness: 1px; text-underline-offset: 3px; }
a:hover { color: var(--cream); }

h1, h2, h3 {
    line-height: 1.15; letter-spacing: -0.02em; margin: 0 0 0.6em;
    /* Evens out the lines so a heading can't leave one orphaned word on the last line. */
    text-wrap: balance;
}
h1 { font-size: clamp(2.1rem, 5.5vw, 3.4rem); font-weight: 800; }
h2 { font-size: clamp(1.5rem, 3.2vw, 2.05rem); font-weight: 750; }
h3 { font-size: 1.13rem; font-weight: 700; letter-spacing: -0.01em; }
p { margin: 0 0 1.1em; }

.accent { color: var(--amber); }

.sr-only {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.skip-link {
    position: absolute; left: -9999px; top: 0; z-index: 100;
    background: var(--amber); color: var(--charcoal); padding: 0.6rem 1rem; font-weight: 700;
}
.skip-link:focus { left: 0; }

:focus-visible { outline: 2px solid var(--amber-bright); outline-offset: 3px; border-radius: 4px; }

/* ---------- header ---------- */

.site-header {
    position: sticky; top: 0; z-index: 20;
    background: rgba(23, 21, 20, 0.92);
    backdrop-filter: blur(8px);
    border-bottom: 1px solid var(--cream-faint);
}
.header-inner {
    display: flex; align-items: center; justify-content: space-between;
    gap: 1rem; min-height: 4.25rem;
}
.brand { display: flex; align-items: center; gap: 0.7rem; text-decoration: none; color: var(--cream); }
.brand img { border-radius: 9px; display: block; }
.brand-name { font-weight: 800; letter-spacing: -0.015em; font-size: 1.08rem; }

.nav { display: flex; align-items: center; gap: 1.4rem; }
.nav a { color: var(--cream-dim); text-decoration: none; font-size: 0.96rem; font-weight: 600; }
.nav a:hover { color: var(--cream); }
.nav .nav-cta {
    color: var(--charcoal); background: var(--amber);
    padding: 0.42rem 0.95rem; border-radius: 999px;
}
.nav .nav-cta:hover { background: var(--amber-bright); color: var(--charcoal); }

@media (max-width: 640px) {
    .nav { gap: 0.9rem; }
    .nav a:not(.nav-cta) { display: none; }
    .brand-name { font-size: 1rem; }
}

/* ---------- hero ---------- */

.hero {
    position: relative;
    padding: clamp(3rem, 8vw, 6rem) 0 clamp(2.5rem, 6vw, 4.5rem);
    text-align: center;
    overflow: hidden;
}
.hero::before {
    content: "";
    position: absolute; inset: -30% 0 auto 50%;
    width: min(46rem, 130%); height: 34rem; transform: translateX(-50%);
    background: radial-gradient(closest-side, rgba(209, 125, 12, 0.22), transparent 72%);
    pointer-events: none;
}
.hero-inner { position: relative; }
/* The Play Store feature graphic doubles as the site banner; it carries its own dark
   ground, so it reads as a card against the page. */
.hero-banner {
    width: min(34rem, 100%); height: auto;
    border-radius: var(--radius);
    border: 1px solid rgba(248, 227, 194, 0.18);
    box-shadow: 0 18px 50px rgba(0, 0, 0, 0.55);
    margin-bottom: 1.8rem;
}
.eyebrow {
    text-transform: uppercase; letter-spacing: 0.13em; font-size: 0.74rem; font-weight: 700;
    color: var(--amber); margin-bottom: 0.9rem;
}
.hero h1 { max-width: 18ch; margin-inline: auto; }
.lead {
    font-size: clamp(1.02rem, 2vw, 1.2rem); color: var(--cream-dim);
    max-width: 46rem; margin: 0 auto 1.8rem;
}
.lead-narrow { font-size: 1.08rem; color: var(--cream-dim); max-width: 44rem; }

.cta-row { display: flex; flex-wrap: wrap; gap: 0.8rem; justify-content: center; }

.btn {
    display: inline-block; padding: 0.72rem 1.4rem; border-radius: 999px;
    font-weight: 700; font-size: 0.98rem; text-decoration: none; border: 1px solid transparent;
    cursor: pointer; font-family: inherit; transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.btn-primary { background: var(--amber); color: var(--charcoal); }
.btn-primary:hover { background: var(--amber-bright); color: var(--charcoal); }
.btn-ghost { border-color: var(--cream-faint); color: var(--cream); }
.btn-ghost:hover { border-color: var(--amber); color: var(--amber); }

/* ---------- screenshots ---------- */

/* A horizontal strip: phone screenshots are tall, so a grid would push everything below
   the fold. Scroll-snap keeps a frame aligned after a flick. */
.screens {
    display: grid; grid-auto-flow: column;
    grid-auto-columns: clamp(14rem, 26vw, 16.5rem);
    gap: 1.2rem;
    overflow-x: auto;
    padding: 0.4rem 0.25rem 1.1rem;
    scroll-snap-type: x proximity;
    scrollbar-color: var(--amber-deep) transparent;
}
.screens figure { margin: 0; scroll-snap-align: start; }
.screens img {
    display: block; width: 100%; height: auto;
    border-radius: 16px;
    border: 1px solid var(--cream-faint);
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
    background: var(--charcoal-lift);
}
.screens figcaption {
    font-size: 0.84rem; line-height: 1.5;
    color: rgba(248, 227, 194, 0.55);
    margin-top: 0.7rem; padding: 0 0.2rem;
}

/* ---------- sections ---------- */

.section { padding: clamp(2.75rem, 6vw, 4.5rem) 0; }
/* The header is sticky, so a jump to #notify would otherwise land with the heading underneath it. */
.section[id] { scroll-margin-top: 5rem; }
.section-alt { background: var(--charcoal-deep); border-block: 1px solid var(--cream-faint); }
.page-head { padding-bottom: 0.5rem; }
/* A title block and the section under it are one unit; two full section paddings between them
   opens a gap that reads as a missing element. */
.page-head + .section { padding-top: 1rem; }

.cards {
    display: grid; gap: 1.1rem;
    grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
}
.card {
    background: var(--charcoal-lift);
    border: 1px solid var(--cream-faint);
    border-radius: var(--radius);
    padding: 1.5rem 1.4rem 1.1rem;
    transition: border-color 0.15s, transform 0.15s;
}
.card:hover { border-color: rgba(209, 125, 12, 0.55); transform: translateY(-2px); }
.card h3 { color: var(--amber); }
.card p { color: var(--cream-dim); margin-bottom: 0.6rem; font-size: 0.98rem; }
.cards-more { margin: 1.4rem 0 0; color: var(--cream-dim); font-size: 0.98rem; }

/* ---------- on-the-air op spotlight ---------- */

.spotlight {
    display: grid; gap: clamp(1.8rem, 4vw, 3rem);
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
    align-items: center;
}
@media (max-width: 860px) {
    .spotlight { grid-template-columns: 1fr; }
}
.spotlight-copy p { color: var(--cream-dim); }
/* No max-width here: the grid column is already the measure, and capping it again only
   forces an orphan onto a third line. */
.spotlight-kicker {
    border-left: 2px solid var(--amber);
    padding-left: 1rem;
    margin-bottom: 0;
}

/* The transcript panel: real APRS traffic, set in mono so it reads as machine wire format
   rather than as more marketing prose. */
.exchange-figure { margin: 0; }
.exchange {
    list-style: none; margin: 0; padding: 1.2rem;
    background: var(--charcoal-lift);
    border: 1px solid var(--cream-faint);
    border-radius: var(--radius);
    display: grid; gap: 0.85rem;
}
.exchange li { display: grid; grid-template-columns: 3.1rem minmax(0, 1fr); gap: 0.8rem; align-items: baseline; }
.ex-who {
    font-family: var(--mono); font-size: 0.68rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.08em; text-align: right;
}
.ex-out .ex-who { color: var(--amber); }
.ex-in .ex-who { color: rgba(248, 227, 194, 0.42); }
.ex-msg {
    font-family: var(--mono); font-size: 0.83rem; line-height: 1.5;
    /* Callsigns and grid squares must not be broken across lines to fit a narrow column. */
    overflow-wrap: break-word;
}
.ex-out .ex-msg { color: var(--cream); }
.ex-in .ex-msg { color: var(--cream-dim); }
.exchange-figure figcaption {
    font-size: 0.84rem; color: rgba(248, 227, 194, 0.5);
    margin-top: 0.9rem; padding: 0 0.3rem;
}
.exchange-figure em { color: var(--cream-dim); font-style: normal; font-weight: 600; }

/* ---------- notify form ---------- */

.notify-inner { max-width: 40rem; }
.notify-lead { color: var(--cream-dim); }

/* The download CTA sits above the mail signup in one section, so the rule is what separates
   "install it now" from "tell me when there's more" — without making them look like rivals. */
.cta-row-start { justify-content: flex-start; margin-top: 1.2rem; }
.get-more {
    margin-top: 2.4rem;
    padding-top: 1.8rem;
    border-top: 1px solid var(--cream-faint);
}
.get-more h3 { margin: 0 0 0.5rem; }

.notify-form { display: flex; flex-wrap: wrap; gap: 0.6rem; margin: 1.2rem 0 0.7rem; }
.notify-form input[type="email"],
.notify-form input[type="password"] {
    flex: 1 1 16rem;
    padding: 0.72rem 1rem;
    border-radius: 999px;
    border: 1px solid var(--cream-faint);
    background: var(--charcoal);
    color: var(--cream);
    font: inherit;
    font-size: 0.98rem;
}
.notify-form input::placeholder { color: rgba(248, 227, 194, 0.38); }
.notify-form input:focus { border-color: var(--amber); }

/* Labelled fields stacked vertically — the single-line .notify-form shape doesn't survive
   more than one input plus a button. The controls themselves take the shared admin form
   styling at the end of this file; only the layout lives here. */
.stacked-form { display: grid; gap: 1rem; justify-items: start; margin: 1.2rem 0 0; }
.stacked-form .field { display: grid; gap: 0.35rem; width: 100%; }
.stacked-form label {
    font-size: 0.78rem; font-weight: 700; color: var(--cream-dim);
    text-transform: uppercase; letter-spacing: 0.08em;
}
.stacked-form input,
.stacked-form select { width: 100%; }

/* Role legend under the account list. */
.role-key {
    margin: 2rem 0 0; display: grid; gap: 0.3rem 1rem;
    grid-template-columns: max-content 1fr; align-items: baseline;
    font-size: 0.9rem;
}
.role-key dt { font-weight: 700; color: var(--amber); }
.role-key dd { margin: 0; color: var(--cream-dim); }

/* Honeypot: parked off-screen rather than display:none, which some bots detect and skip. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.fineprint { font-size: 0.86rem; color: rgba(248, 227, 194, 0.5); }

.notice {
    border-radius: 10px; padding: 0.75rem 1rem; margin: 0 0 1rem;
    border: 1px solid var(--cream-faint); background: var(--charcoal-lift);
    font-size: 0.96rem;
}
.notice-ok { border-color: rgba(111, 191, 115, 0.45); color: var(--ok); }
.notice-error, .notice-bad { border-color: rgba(224, 112, 95, 0.45); color: var(--err); }

/* ---------- status list & pills ---------- */

.status-list { list-style: none; padding: 0; margin: 1.5rem 0 0; display: grid; gap: 0.8rem; }
.status-list li {
    display: flex; gap: 0.9rem; align-items: baseline; flex-wrap: wrap;
    color: var(--cream-dim); font-size: 0.99rem;
    border-left: 2px solid var(--cream-faint); padding-left: 1rem;
}

.pill {
    display: inline-block; flex: none;
    text-transform: uppercase; letter-spacing: 0.08em; font-size: 0.68rem; font-weight: 800;
    padding: 0.2rem 0.6rem; border-radius: 999px; border: 1px solid currentColor;
}
.pill-on { color: var(--ok); }
.pill-wip { color: var(--amber); }
.pill-next, .pill-off { color: rgba(248, 227, 194, 0.45); }

/* ---------- download links (admin) ---------- */

/* The URL and its QR side by side on a desktop, stacked on a phone. This block is the reason the
   page exists: everything else on it is reading, and this is the one thing to do. */
.link-hand { display: flex; gap: 1.6rem; align-items: flex-start; flex-wrap: wrap; }
.link-hand-main { flex: 1 1 22rem; min-width: 0; }
.link-hand-label { display: block; font-weight: 700; margin-bottom: 0.4rem; }

/* Monospace and oversized: this is meant to be read back over a phone call, not just copied. */
.link-hand-url {
    width: 100%;
    font-family: var(--mono);
    font-size: 1.05rem;
    padding: 0.7rem 0.8rem;
    color: var(--cream);
    background: var(--charcoal-deep);
    border: 1px solid var(--cream-faint);
    border-radius: 10px;
}
.link-hand-actions { display: flex; gap: 0.6rem; margin-top: 0.7rem; }

.link-hand-qr { margin: 0; flex: 0 0 auto; text-align: center; }
.link-hand-qr figcaption { font-size: 0.82rem; color: var(--cream-dim); margin-top: 0.5rem; }

/* Same white quiet zone as the public page's code, for the same scanning reason. */
.link-qr { background: #fff; border-radius: 10px; padding: 0.6rem; width: 11rem; }
.link-qr svg { display: block; width: 100%; height: auto; }

/* The slug field reads as one address: a fixed prefix and the part you type. */
.slug-field { display: flex; align-items: stretch; flex-wrap: wrap; }
.slug-prefix {
    font-family: var(--mono); font-size: 0.86rem;
    display: flex; align-items: center;
    padding: 0 0.6rem;
    color: var(--cream-dim);
    background: var(--charcoal-deep);
    border: 1px solid var(--cream-faint); border-right: 0;
    border-radius: 10px 0 0 10px;
}
.slug-field input { flex: 1 1 10rem; min-width: 0; border-radius: 0 10px 10px 0; }
@media (max-width: 30rem) {
    .slug-prefix { border-right: 1px solid var(--cream-faint); border-radius: 10px 10px 0 0; }
    .slug-field input { border-radius: 0 0 10px 10px; }
}

/* A hint under a field, not a label: the form has enough of these that they must read as quieter
   than everything around them or the page turns into a wall. */
.field-note { font-size: 0.82rem; color: var(--cream-dim); margin: 0.3rem 0 1.1rem; }
.field-optional {
    font-weight: 400; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.06em;
    color: rgba(248, 227, 194, 0.4);
}
.link-form label { margin-bottom: 0.3rem; }

/* The raw slug under a link's name, so a row is identifiable by either. */
.link-url { font-size: 0.8rem; color: var(--cream-dim); margin-top: 0.15rem; }

/* Sits inside the slug field, after the input, so the three read as one control. */
.slug-random { flex: none; margin-left: 0.5rem; align-self: center; }
@media (max-width: 30rem) {
    .slug-random { margin: 0.5rem 0 0; }
}

/* A qualifier under a number — "43% of Android" beneath the install count. Its own line so the
   figure stays the thing the eye lands on. */
.metric-sub { display: block; font-size: 0.75rem; color: var(--cream-dim); }

/* ---------- download link (/get) ---------- */

/* Its own centred ground rather than the site shell: this page has no header, no nav and no
   footer, because everything on it is either the redirect or the one thing to do instead. */
.get-body { display: flex; min-height: 100vh; }

.get-page {
    margin: auto;
    padding: 2rem 1.2rem;
    width: 100%;
    max-width: 30rem;
    text-align: center;
}

.get-card {
    background: var(--charcoal-lift);
    border: 1px solid var(--cream-faint);
    border-radius: var(--radius);
    padding: 2.4rem 1.6rem;
}

.get-mark { display: block; margin: 0 auto 1.2rem; }

/* Only ever seen by whoever asked for it. Loud enough not to be missed on a page that is about
   to redirect, quiet enough not to look like an error to anyone who lands on it by accident. */
.get-test {
    margin: 0 0 1rem;
    padding: 0.35rem 0.7rem;
    display: inline-block;
    border: 1px dashed var(--amber);
    border-radius: 999px;
    color: var(--amber);
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-weight: 800;
}
.get-card h1 { font-size: 1.7rem; margin: 0 0 0.4rem; }
.get-sub { color: var(--cream-dim); margin: 0 0 1.4rem; }

/* White quiet zone included in the tile, because a QR on a charcoal card will not scan without
   one and the code itself only draws its dark modules. */
.get-qr {
    background: #fff;
    border-radius: 10px;
    padding: 0.7rem;
    width: 15rem;
    margin: 0 auto 1.6rem;
}
.get-qr svg { display: block; width: 100%; height: auto; }

.get-stores { display: flex; flex-direction: column; gap: 0.7rem; align-items: stretch; }
.get-stores .pill { align-self: center; }

/* The landing page's signup is a row because it has a full page width to sit in. Inside this
   card, on a phone held in one hand, the same row squeezes the field down to nothing — so it
   stacks, like the store buttons directly above it. */
.get-notify { flex-direction: column; align-items: stretch; margin: 0; }
/* The shared rule above sets flex: 1 1 16rem, which is a WIDTH while the form runs as a row.
   Turning the axis vertical made that basis a height instead, and the pill radius turned a
   256px-tall box into an ellipse the size of the card. Height comes from the padding here,
   the same as everywhere else the field is used. */
.get-notify input[type="email"] { flex: 0 0 auto; width: 100%; text-align: center; }
.get-card .notice { margin: 0 0 1.2rem; text-align: left; }

.get-fallback { margin: 1.4rem 0 0; font-size: 0.9rem; color: var(--cream-dim); }

/* Three dots that fade in turn, so the wait reads as progress rather than as a stall. The
   reduced-motion rule at the top of this file already flattens the animation to nothing. */
.get-dots { display: flex; gap: 0.45rem; justify-content: center; margin: 0.4rem 0 0.2rem; }
.get-dots span {
    width: 0.5rem; height: 0.5rem; border-radius: 50%;
    background: var(--amber);
    animation: get-pulse 1.2s ease-in-out infinite;
}
.get-dots span:nth-child(2) { animation-delay: 0.2s; }
.get-dots span:nth-child(3) { animation-delay: 0.4s; }
@keyframes get-pulse {
    0%, 80%, 100% { opacity: 0.25; }
    40% { opacity: 1; }
}

/* ---------- prose pages ---------- */

.prose { max-width: 44rem; }
.prose.narrow { max-width: 26rem; }
.prose h2 { margin-top: 2.2rem; }
.prose h3 { margin-top: 1.6rem; color: var(--amber); }
.prose p, .prose li { color: var(--cream-dim); }
.prose ul { padding-left: 1.2rem; margin: 0 0 1.2em; }
.prose li { margin-bottom: 0.5em; }
.prose strong { color: var(--cream); }
.prose code {
    font-family: var(--mono); font-size: 0.9em;
    background: var(--charcoal-lift); padding: 0.12em 0.4em; border-radius: 5px;
}

/* ---------- admin shell ---------- */

/* A grouped left sidebar that folds into a hamburger on a phone, the same shape as the Yard
   Sale Finder console. The fold is driven by a checkbox rather than script: the admin console
   carries no bundle, and a nav that needs JavaScript to open is a nav that can lock you out.
   Every rule below hangs off .admin-nav-toggle with a sibling combinator, so that input has to
   stay the first child of .admin-sidebar. */

.admin-layout { display: flex; align-items: flex-start; min-height: 100vh; }

.admin-sidebar {
    /* Stays put while the page scrolls, and scrolls internally if the groups ever outgrow the
       window — never the whole sidebar sliding off the top of a long crash list. */
    position: sticky; top: 0;
    flex: none; width: 15rem; height: 100vh; overflow-y: auto;
    display: flex; flex-direction: column;
    background: var(--charcoal-deep);
    border-right: 1px solid var(--cream-faint);
}
.admin-main { flex: 1 1 auto; min-width: 0; }

.admin-brand {
    flex: none;
    display: flex; align-items: center; justify-content: space-between; gap: 0.6rem;
    padding: 0.95rem 1.1rem;
    border-bottom: 1px solid var(--cream-faint);
}
.admin-brand .brand-name { font-size: 0.98rem; line-height: 1.25; }
.admin-brand-sub {
    display: block; margin-top: 0.1rem;
    font-size: 0.62rem; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase;
    color: rgba(248, 227, 194, 0.38);
}

.admin-nav { flex: 1 1 auto; padding: 0.55rem 0; }

/* The group header. Deliberately quieter than its links — it labels the section, it isn't a
   destination, and a summary styled like a link invites a click that goes nowhere. */
.admin-nav > details > summary {
    display: flex; align-items: center; gap: 0.5rem;
    padding: 0.55rem 1.1rem; cursor: pointer; user-select: none; list-style: none;
    font-size: 0.71rem; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase;
    color: rgba(248, 227, 194, 0.45);
}
.admin-nav > details > summary::-webkit-details-marker { display: none; }
.admin-nav > details > summary::before {
    content: "\25B8"; font-size: 0.6rem; color: rgba(248, 227, 194, 0.3);
    transition: transform 0.12s;
}
.admin-nav > details[open] > summary::before { transform: rotate(90deg); }
.admin-nav > details > summary:hover { color: var(--cream); }
.admin-nav > details + details { margin-top: 0.1rem; }

.admin-nav a {
    display: block; padding: 0.44rem 1.1rem 0.44rem 2rem;
    color: var(--cream-dim); text-decoration: none;
    font-size: 0.94rem; font-weight: 600;
    /* Reserved on every link, not added on the active one, so nothing shifts sideways as you
       move between pages. */
    border-left: 3px solid transparent;
}
.admin-nav a:hover { background: rgba(248, 227, 194, 0.06); color: var(--cream); }
.admin-nav a.is-active {
    background: rgba(209, 125, 12, 0.16); border-left-color: var(--amber);
    color: var(--cream); font-weight: 700;
}

.admin-foot {
    flex: none; display: grid; gap: 0.45rem; justify-items: start;
    padding: 0.9rem 1.1rem 1.1rem;
    border-top: 1px solid var(--cream-faint);
}
.admin-who {
    font-family: var(--mono); font-size: 0.84rem; font-weight: 700;
    color: var(--cream); text-decoration: none; overflow-wrap: anywhere;
}
.admin-who:hover, .admin-who.is-active { color: var(--amber); }
.admin-role {
    margin-top: -0.35rem;
    font-size: 0.66rem; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase;
    color: rgba(248, 227, 194, 0.38);
}
.admin-foot-link { color: var(--cream-dim); text-decoration: none; font-size: 0.88rem; font-weight: 600; }
.admin-foot-link:hover { color: var(--cream); }
.admin-foot form { margin: 0; }

.admin-nav-toggle { position: absolute; opacity: 0; width: 0; height: 0; }
.admin-burger {
    display: none; flex: none; cursor: pointer; user-select: none;
    color: var(--cream-dim); font-size: 1.35rem; line-height: 1; padding: 0.15rem 0.4rem;
}
.admin-burger:hover { color: var(--cream); }
.admin-burger .burger-close { display: none; }

/* Below this the sidebar becomes a bar across the top with everything behind the hamburger.
   15rem of chrome beside the content stops paying for itself well before a phone. */
@media (max-width: 860px) {
    .admin-layout { display: block; }
    .admin-sidebar {
        z-index: 20; width: 100%; height: auto; max-height: 100vh;
        border-right: 0; border-bottom: 1px solid var(--cream-faint);
    }
    .admin-brand { border-bottom: 0; }
    .admin-burger { display: block; }
    .admin-nav, .admin-foot { display: none; }
    .admin-nav-toggle:checked ~ .admin-nav { display: block; padding-bottom: 0.4rem; }
    .admin-nav-toggle:checked ~ .admin-foot { display: grid; }
    /* Swap the burger glyph to an ✕ while the menu is open. */
    .admin-nav-toggle:checked ~ .admin-brand .admin-burger .burger-open { display: none; }
    .admin-nav-toggle:checked ~ .admin-brand .admin-burger .burger-close { display: inline; }
    /* Rows a finger can actually hit, now that they're the whole navigation. */
    .admin-nav a { padding-top: 0.6rem; padding-bottom: 0.6rem; }
}

/* ---------- admin: archive & delete ---------- */

/* Live list vs archive. A pair of links rather than a dropdown: two views, both worth being
   one click and one glance away, and the counts belong on the tabs where they say which is
   which before you click. */
.view-tabs { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 1.4rem; }
.view-tab {
    display: inline-flex; align-items: baseline; gap: 0.45rem;
    padding: 0.4rem 0.9rem; border-radius: 999px;
    border: 1px solid var(--cream-faint); color: var(--cream-dim);
    text-decoration: none; font-size: 0.9rem; font-weight: 600;
}
.view-tab:hover { border-color: var(--amber); color: var(--amber); }
.view-tab.is-current {
    background: rgba(209, 125, 12, 0.16); border-color: var(--amber); color: var(--cream);
}
.view-tab-count { font-family: var(--mono); font-size: 0.8rem; opacity: 0.7; }

/* Per-row controls. Each is its own form (a POST handler apiece), so they need to be laid out
   as siblings rather than relying on inline-block whitespace. */
.item-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.8rem; }
.item-actions form { margin: 0; }

.btn-sm {
    padding: 0.34rem 0.85rem; font-size: 0.84rem; font-weight: 600;
    border-radius: 999px; border: 1px solid var(--cream-faint);
    background: none; color: var(--cream-dim);
    cursor: pointer; font-family: inherit;
}
.btn-sm:hover { border-color: var(--amber); color: var(--amber); text-decoration: none; }
/* Destructive, so it reads as destructive on hover rather than going the same amber as
   everything else. Not red at rest: a row of red buttons down the page is noise, and the
   thing being warned about only happens on the click. */
.btn-danger:hover { border-color: var(--err); color: var(--err); }

/* An archived row stays fully legible — it was filed away, not struck out — but loses the
   left-hand accent so a mixed list could never be misread. */
.is-archived { opacity: 0.72; }
.archived-tag {
    font-size: 0.68rem; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase;
    color: rgba(248, 227, 194, 0.45);
}

/* The selection bar: select-all on the left, the actions it feeds on the right. Sticks to the
   top of the viewport so the buttons stay reachable while you work down a long list — which is
   the thing that makes tick-boxes better than a button at the foot of the page. */
.bulk-bar {
    position: sticky; top: 0; z-index: 5;
    display: flex; flex-wrap: wrap; gap: 0.8rem 1rem;
    align-items: center; justify-content: space-between;
    padding: 0.7rem 0.9rem; margin-bottom: 1rem;
    background: var(--charcoal-lift);
    border: 1px solid var(--cream-faint); border-radius: var(--radius);
}
.bulk-all {
    display: inline-flex; align-items: center; gap: 0.55rem;
    cursor: pointer; user-select: none;
    font-size: 0.9rem; font-weight: 600; color: var(--cream-dim);
}
.bulk-all:hover { color: var(--cream); }
.bulk-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; }
/* A disabled action is one that would act on an empty selection. Kept visible rather than
   hidden, so the bar does not change shape every time you tick a box. */
.bulk-actions button:disabled { opacity: 0.4; cursor: default; }
.bulk-actions button:disabled:hover { border-color: var(--cream-faint); color: var(--cream-dim); }

/* The boxes themselves are drawn by the shared admin checkbox styling at the end of this
   file — these are the primary control on the page, so they lean on that rather than the
   tiny browser default. */

/* Each row becomes: tick-box, then everything the row already was. The label is padded so the
   target is comfortably bigger than the box drawn inside it. */
.feedback-item, .crash-item { display: flex; align-items: flex-start; gap: 0.15rem; }
.pick { display: flex; align-items: center; padding: 0.35rem 0.7rem 0.35rem 0; cursor: pointer; }
.pick-body { flex: 1 1 auto; min-width: 0; }

/* The same control as a table column, on the notify list. Sized to the box so it never steals
   width from the address beside it. */
.data-table .pick-col { width: 1px; padding-right: 0; }
.data-table .pick-col .pick { padding: 0; }

/* Below 860px the sidebar stops being a left column and becomes a sticky bar across the top of
   the page (z-index 20). A second sticky bar underneath it would scroll straight under that one
   and disappear, so on those widths this one simply scrolls with the page. Matching the
   sidebar's own breakpoint, not the phone one, because that is the width where it changes. */
@media (max-width: 860px) {
    .bulk-bar { position: static; }
}

@media (max-width: 640px) {
    /* A .btn-sm comes out around 31px tall, well under what a finger hits reliably. */
    .btn-sm { padding: 0.6rem 1rem; font-size: 0.88rem; }
    .pick { padding: 0.5rem 0.7rem 0.5rem 0; }
}

/* ---------- admin ---------- */

.stat-row { display: flex; flex-wrap: wrap; gap: 1rem; align-items: center; margin-bottom: 1.8rem; }
.stat {
    background: var(--charcoal-lift); border: 1px solid var(--cream-faint);
    border-radius: var(--radius); padding: 1rem 1.4rem; min-width: 8rem;
}
.stat-action { background: none; border: none; padding: 0; }
.stat-value { font-size: 1.9rem; font-weight: 800; line-height: 1.1; color: var(--amber); }
.stat-label {
    text-transform: uppercase; letter-spacing: 0.1em; font-size: 0.7rem;
    font-weight: 700; color: var(--cream-dim);
}

.table-scroll { overflow-x: auto; }
.data-table { width: 100%; border-collapse: collapse; font-size: 0.94rem; }
/* Cells wrap by default — a blanket nowrap is what used to push every table into sideways
   scroll. The pieces that would wrap badly opt back out below: a timestamp may break between
   date and clock but never inside either, and a pill is a single token. */
.data-table th, .data-table td {
    text-align: left; padding: 0.55rem 0.7rem; border-bottom: 1px solid var(--cream-faint);
}
.data-table th {
    text-transform: uppercase; letter-spacing: 0.08em; font-size: 0.7rem;
    color: var(--cream-dim); font-weight: 700;
    /* Two-line headers beat a column as wide as "Connected h" on one line. */
    vertical-align: bottom;
}
/* Fact tables (the install page) use row headers; those are short labels, keep them whole. */
.data-table th[scope="row"] { white-space: nowrap; vertical-align: top; }
.data-table td {
    color: var(--cream-dim); vertical-align: top;
    font-variant-numeric: tabular-nums;
}
/* Only the genuinely long tokens — ids, URLs — get to break mid-token. A blanket anywhere
   lets auto-layout crush the short columns instead ("1.1.0" folding onto two lines while the
   id column keeps its width), because it zeroes every cell's minimum width at once. */
.data-table td code, .data-table td a { overflow-wrap: anywhere; }
/* A timestamp folding into "date / clock" while an id keeps its width helps nobody — hold
   the clock on one line and let the id column be the one that wraps. */
.data-table time { white-space: nowrap; }
.data-table .pill { white-space: nowrap; }

/* Dense numeric tables (Traffic's daily rollup, Devices): numbers right-aligned and compact,
   and the row's identity — the day, the device — stays put while the metrics scroll under it. */
.data-table-metrics { font-size: 0.85rem; }
.data-table-metrics th, .data-table-metrics td { padding: 0.4rem 0.55rem; }
/* Every cell is a number; a figure split across lines reads as two numbers. The table takes
   the scroll instead when it must. */
.data-table-metrics td { white-space: nowrap; overflow-wrap: normal; }
.data-table-metrics th:not(:first-child), .data-table-metrics td:not(:first-child) { text-align: right; }
.data-table-metrics th:first-child, .data-table-metrics td:first-child {
    position: sticky; left: 0; z-index: 1;
    background: var(--charcoal);
    box-shadow: inset -1px 0 0 var(--cream-faint);
    white-space: nowrap;
}

/* Entity tables opt into becoming labelled cards on a phone — reading a row by scrolling it
   sideways past its own header never works. Each td carries its column name as data-label;
   an action cell goes without and renders as a plain row at the foot of the card. */
@media (max-width: 720px) {
    .data-table-cards thead { display: none; }
    .data-table-cards, .data-table-cards tbody, .data-table-cards tr, .data-table-cards td { display: block; }
    .data-table-cards tr {
        background: var(--charcoal-lift);
        border: 1px solid var(--cream-faint); border-radius: var(--radius);
        padding: 0.65rem 0.9rem; margin-bottom: 0.7rem;
    }
    /* A card is as wide as the phone, so here a long email or id may break anywhere it must. */
    .data-table-cards td { border-bottom: none; padding: 0.22rem 0; display: flex; gap: 0.8rem; align-items: baseline; overflow-wrap: anywhere; min-width: 0; }
    .data-table-cards td[data-label]::before {
        content: attr(data-label);
        flex: none; width: 6.2rem;
        font-size: 0.68rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
        color: rgba(248, 227, 194, 0.45);
    }
    .data-table-cards .pick-col { padding: 0 0 0.3rem; }
}

/* Feedback reads as a list of notes, not a table: the comment is the whole point and a
   4,000-character cell in a row of short ones is unreadable at every window width. */
.feedback-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 1rem; }
.feedback-item {
    background: var(--charcoal-lift); border: 1px solid var(--cream-faint);
    border-radius: var(--radius); padding: 1rem 1.2rem;
}
.feedback-meta {
    display: flex; flex-wrap: wrap; gap: 0.4rem 1rem; align-items: baseline;
    font-size: 0.82rem; font-family: var(--mono);
}
.feedback-when { color: rgba(248, 227, 194, 0.45); }
.feedback-who { color: var(--amber); font-weight: 700; }
.feedback-meta a { color: var(--cream-dim); }
.feedback-dim { color: rgba(248, 227, 194, 0.32); }
.feedback-comment {
    margin: 0.6rem 0; color: var(--cream); font-size: 1rem; line-height: 1.55;
    /* Reports arrive with the sender's own line breaks in them; honour them. */
    white-space: pre-wrap; overflow-wrap: anywhere;
}
.feedback-context {
    display: flex; flex-wrap: wrap; gap: 0.3rem 1rem;
    font-size: 0.76rem; font-family: var(--mono); color: rgba(248, 227, 194, 0.45);
}

/* ---------- crashes ---------- */

/* Same argument as the feedback list: the error message is the thing being read, it runs long,
   and putting it in a cell beside six short columns makes every row unreadable. The counts and
   the build facts go underneath in mono, where the eye can skip them until it wants them. */
.crash-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 0.8rem; }
.crash-item {
    background: var(--charcoal-lift); border: 1px solid var(--cream-faint);
    border-radius: var(--radius); padding: 0.9rem 1.1rem;
    /* The status stripe. Overridden per status below; the default is the unread one. */
    border-left: 3px solid var(--amber);
}
.crash-item.crash-open { border-left-color: rgba(248, 227, 194, 0.35); }
.crash-item.crash-fixed { border-left-color: var(--ok); }
.crash-item.crash-ignored { border-left-color: rgba(248, 227, 194, 0.15); }

.crash-head { display: flex; gap: 0.8rem; align-items: baseline; justify-content: space-between; }
.crash-title { text-decoration: none; display: block; flex: 1 1 auto; }
.crash-title:hover .crash-message { color: var(--cream); }
.crash-error { font-weight: 800; color: var(--amber); font-family: var(--mono); font-size: 0.94rem; }
.crash-message {
    display: block; margin-top: 0.15rem; color: var(--cream); font-size: 1rem; line-height: 1.45;
    /* Runtime messages arrive with no spaces for pages at a time; let them break anywhere. */
    overflow-wrap: anywhere;
}
.crash-detail-title .crash-message { font-size: 1.15rem; margin-top: 0.35rem; }

.crash-badge {
    flex: none; font-size: 0.7rem; font-family: var(--mono); text-transform: uppercase;
    letter-spacing: 0.06em; padding: 0.15rem 0.5rem; border-radius: 999px;
    border: 1px solid var(--cream-faint); color: rgba(248, 227, 194, 0.55);
}
.crash-badge-new { border-color: var(--amber); color: var(--amber); }
.crash-badge-fixed { border-color: rgba(111, 191, 115, 0.5); color: var(--ok); }

.crash-frame {
    margin: 0.5rem 0 0; font-family: var(--mono); font-size: 0.78rem;
    color: rgba(248, 227, 194, 0.5); overflow-wrap: anywhere;
}
.crash-meta {
    display: flex; flex-wrap: wrap; gap: 0.3rem 1rem; margin-top: 0.5rem;
    font-size: 0.76rem; font-family: var(--mono); color: rgba(248, 227, 194, 0.45);
}
.crash-dim { color: rgba(248, 227, 194, 0.3); }
.crash-mono { font-family: var(--mono); font-size: 0.85rem; overflow-wrap: anywhere; }

/* The copy-everything block. Deliberately the most prominent thing on the detail page — it is
   the reason the page exists, and burying it under the fact table would mean nine manual
   copies every time somebody wants to pass a crash on. */
.triage {
    background: var(--charcoal-deep); border: 1px solid rgba(209, 125, 12, 0.4);
    border-radius: var(--radius); padding: 0.9rem; margin-bottom: 2rem;
}
.triage-text {
    width: 100%; box-sizing: border-box; resize: vertical;
    background: transparent; border: 0; color: var(--cream-dim);
    font-family: var(--mono); font-size: 0.8rem; line-height: 1.5;
}
.triage-text:focus-visible { outline: none; }
.triage-actions { display: flex; flex-wrap: wrap; gap: 0.8rem; align-items: center; margin-top: 0.6rem; }

.crash-stack {
    background: var(--charcoal-deep); border: 1px solid var(--cream-faint);
    border-radius: var(--radius); padding: 0.9rem 1rem;
    font-family: var(--mono); font-size: 0.8rem; line-height: 1.5; color: var(--cream-dim);
    /* Frames are long and must not be wrapped into ambiguity — scroll the block instead. */
    overflow-x: auto; white-space: pre;
}

.crash-facts {
    display: grid; grid-template-columns: minmax(7rem, max-content) 1fr;
    gap: 0.35rem 1.2rem; margin: 0 0 1.6rem;
}
.crash-facts dt {
    font-size: 0.78rem; font-family: var(--mono); text-transform: uppercase;
    letter-spacing: 0.05em; color: rgba(248, 227, 194, 0.4);
}
.crash-facts dd { margin: 0; color: var(--cream-dim); overflow-wrap: anywhere; }

.crash-trail { list-style: none; padding: 0; margin: 0 0 1.6rem; display: grid; gap: 0.1rem; }
.crash-trail li {
    display: grid; grid-template-columns: 5.5rem 7rem 1fr; gap: 0.6rem;
    font-family: var(--mono); font-size: 0.78rem; padding: 0.2rem 0;
    border-bottom: 1px solid rgba(248, 227, 194, 0.06);
}
.crash-trail-time { color: rgba(248, 227, 194, 0.35); }
.crash-trail-cat { color: var(--amber); }
.crash-trail-msg { color: var(--cream-dim); overflow-wrap: anywhere; }
@media (max-width: 640px) {
    /* Three mono columns do not fit a phone; stack the message under its own timestamp. */
    .crash-trail li { grid-template-columns: 5.5rem 1fr; }
    .crash-trail-msg { grid-column: 1 / -1; }
}

.crash-occurrences { width: 100%; border-collapse: collapse; font-size: 0.84rem; }
.crash-occurrences th, .crash-occurrences td {
    text-align: left; padding: 0.4rem 0.7rem 0.4rem 0;
    border-bottom: 1px solid rgba(248, 227, 194, 0.08);
}
.crash-occurrences th {
    font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em;
    color: rgba(248, 227, 194, 0.4); font-family: var(--mono);
}
.crash-occurrences td { color: var(--cream-dim); }

.crash-triage-form { display: grid; gap: 0.4rem; max-width: 34rem; margin-bottom: 2rem; }
.crash-triage-form label {
    font-size: 0.78rem; font-family: var(--mono); text-transform: uppercase;
    letter-spacing: 0.05em; color: rgba(248, 227, 194, 0.4); margin-top: 0.5rem;
}
.crash-triage-form button { justify-self: start; margin-top: 0.8rem; }

/* Amber rather than red: a warning here is "read this carefully", not "something failed". */
.notice-warn { border-color: rgba(209, 125, 12, 0.5); color: var(--amber); }

.signed-in-as {
    font-size: 0.86rem; color: rgba(248, 227, 194, 0.45);
    font-family: var(--mono);
}
@media (max-width: 640px) {
    .signed-in-as { display: none; }
}

.inline-form { display: inline; }
.linklike {
    background: none; border: none; padding: 0; cursor: pointer;
    color: var(--cream-dim); font: inherit; font-size: 0.96rem; font-weight: 600;
}
.linklike:hover { color: var(--cream); }

/* The push-notification nudge: one calm line at the top of the shell, not a modal. Hidden
   markup on every shell page; admin-push.js unhides it only where it applies. */
.push-nudge {
    display: flex; flex-wrap: wrap; gap: 0.6rem 1.2rem;
    align-items: center; justify-content: space-between;
    margin-top: 1.2rem; padding: 0.7rem 1.1rem;
    background: rgba(209, 125, 12, 0.1);
    border: 1px solid rgba(209, 125, 12, 0.45);
    border-radius: var(--radius);
    font-size: 0.93rem; color: var(--cream);
}
.push-nudge-actions { display: flex; flex-wrap: wrap; gap: 0.4rem 1rem; align-items: center; }
.push-nudge-on { border-color: var(--amber); color: var(--amber); }

/* ---------- admin: alerts ---------- */

/* The bell in the sidebar's brand row. A plain link to the feed, not a dropdown: the sidebar
   is a scroll container, which clips anything positioned outside it, and the feed page is the
   same single click a panel would be. */
.admin-bell {
    position: relative; flex: none; display: flex; align-items: center;
    padding: 0.3rem 0.35rem; color: var(--cream-dim);
}
.admin-bell:hover { color: var(--amber); }
.admin-bell-count {
    position: absolute; top: -0.25rem; right: -0.35rem;
    min-width: 1.1rem; padding: 0.05rem 0.28rem; border-radius: 999px;
    background: var(--amber); color: var(--charcoal); text-align: center;
    font-size: 0.62rem; font-weight: 800; font-family: var(--mono); line-height: 1.5;
}

/* The feed. Same argument as the feedback and crash lists: the title is the thing being read
   and runs long, so each alert is a card rather than a table row. The unread accent mirrors
   the crash list's status stripe. */
.alert-feed { list-style: none; padding: 0; margin: 0; display: grid; gap: 0.6rem; }
.alert-row {
    display: flex; align-items: flex-start; gap: 0.3rem;
    background: var(--charcoal-lift); border: 1px solid var(--cream-faint);
    border-radius: var(--radius);
    border-left: 3px solid transparent;
}
.alert-row.is-unread { border-left-color: var(--amber); }
.alert-row.is-unread .alert-title { color: var(--cream); font-weight: 700; }
.alert-main {
    flex: 1 1 auto; min-width: 0; display: block;
    padding: 0.75rem 0.4rem 0.75rem 1rem; text-decoration: none;
}
.alert-main:hover .alert-title { color: var(--amber); }
.alert-meta { display: flex; flex-wrap: wrap; gap: 0.4rem 0.8rem; align-items: baseline; }
.alert-badge {
    flex: none; font-size: 0.68rem; font-family: var(--mono); text-transform: uppercase;
    letter-spacing: 0.06em; padding: 0.12rem 0.5rem; border-radius: 999px;
    border: 1px solid var(--cream-faint); color: rgba(248, 227, 194, 0.55);
}
.alert-badge-warn { border-color: rgba(209, 125, 12, 0.5); color: var(--amber); }
.alert-badge-urgent { border-color: rgba(224, 108, 92, 0.6); color: var(--err); }
.alert-time { font-size: 0.76rem; font-family: var(--mono); color: rgba(248, 227, 194, 0.45); }
.alert-title {
    display: block; margin-top: 0.3rem; color: var(--cream-dim);
    font-size: 0.98rem; line-height: 1.45; overflow-wrap: anywhere;
}
.alert-sub {
    display: block; margin-top: 0.15rem; font-size: 0.82rem;
    color: rgba(248, 227, 194, 0.45); overflow-wrap: anywhere;
}
.alert-x { margin: 0; flex: none; }
.alert-x button {
    background: none; border: none; cursor: pointer;
    padding: 0.75rem 0.9rem; color: rgba(248, 227, 194, 0.35); font-size: 0.85rem;
}
.alert-x button:hover { color: var(--err); }

/* The filter select and mark-all button share the view-tabs row; the button hangs right.
   The select is compacted to the row's pill height at the end of this file, on top of the
   shared admin select styling. */
.alert-filter { display: inline-flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; margin: 0; }
.alert-markall { margin: 0 0 0 auto; }

.pager { display: flex; gap: 0.9rem; align-items: center; margin-top: 1.2rem; }
.pager-where { font-size: 0.84rem; font-family: var(--mono); color: rgba(248, 227, 194, 0.45); }

/* The subscriptions grid: one tick-box per category, grouped the way the catalog groups them. */
.sub-group-head {
    margin: 1.6rem 0 0.6rem; font-size: 0.74rem; font-weight: 800;
    letter-spacing: 0.1em; text-transform: uppercase; color: rgba(248, 227, 194, 0.45);
}
.sub-group-head:first-of-type { margin-top: 0; }
.sub-list { list-style: none; padding: 0; margin: 0 0 0.4rem; display: grid; gap: 0.5rem; }
.sub-row {
    background: var(--charcoal-lift); border: 1px solid var(--cream-faint);
    border-radius: var(--radius);
}
.sub-pick { display: flex; gap: 0.8rem; align-items: flex-start; padding: 0.8rem 1rem; cursor: pointer; }
.sub-body { flex: 1 1 auto; min-width: 0; }
.sub-label {
    display: flex; flex-wrap: wrap; gap: 0.6rem; align-items: baseline;
    color: var(--cream); font-weight: 700; font-size: 0.96rem;
}
.sub-desc { display: block; margin-top: 0.2rem; font-size: 0.84rem; color: rgba(248, 227, 194, 0.5); }

.sub-save { margin-top: 1.4rem; }

/* Per-category channel toggles, right-hand side of each subscription row. */
.sub-pick { cursor: default; }
.sub-channels {
    flex: none; display: flex; flex-wrap: wrap; gap: 0.3rem 1.1rem; align-items: center;
    align-self: center;
}
.sub-channel {
    display: inline-flex; align-items: center; gap: 0.45rem; cursor: pointer;
    font-size: 0.84rem; font-weight: 600; color: var(--cream-dim); white-space: nowrap;
}
.sub-channel:hover { color: var(--cream); }
@media (max-width: 640px) {
    /* Stack the toggles under the description instead of squeezing beside it. */
    .sub-pick { flex-wrap: wrap; }
    .sub-channels { flex-basis: 100%; padding-top: 0.2rem; }
}

.sub-delivery-head { margin-top: 2.4rem; }
.sub-delivery { max-width: 34rem; }
.sub-digest-row { display: flex; flex-wrap: wrap; gap: 0.4rem 1.2rem; }
.sub-digest-row .field { flex: 1 1 9rem; }

/* ---------- admin: email log ---------- */

.test-email-form { display: flex; flex-wrap: wrap; gap: 0.6rem; align-items: center; max-width: 34rem; }
.test-email-form input { flex: 1 1 14rem; }

.email-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 0.6rem; }
.email-item {
    display: flex; align-items: flex-start; gap: 0.3rem;
    background: var(--charcoal-lift); border: 1px solid var(--cream-faint);
    border-radius: var(--radius);
    border-left: 3px solid transparent;
}
.email-item.email-queued { border-left-color: var(--amber); }
.email-item.email-failed { border-left-color: var(--err); }
.email-main {
    flex: 1 1 auto; min-width: 0; display: block;
    padding: 0.75rem 0.4rem 0.75rem 1rem; text-decoration: none;
}
.email-main:hover .email-subject { color: var(--amber); }
.email-meta {
    display: flex; flex-wrap: wrap; gap: 0.4rem 0.9rem; align-items: baseline;
    font-size: 0.78rem; font-family: var(--mono);
}
.email-badge {
    font-size: 0.68rem; font-family: var(--mono); text-transform: uppercase;
    letter-spacing: 0.06em; padding: 0.12rem 0.5rem; border-radius: 999px;
    border: 1px solid var(--cream-faint); color: rgba(248, 227, 194, 0.55);
}
.email-badge-sent { border-color: rgba(111, 191, 115, 0.5); color: var(--ok); }
.email-badge-failed { border-color: rgba(224, 108, 92, 0.6); color: var(--err); }
.email-badge-queued { border-color: var(--amber); color: var(--amber); }
.email-cat { color: rgba(248, 227, 194, 0.45); }
.email-when { color: rgba(248, 227, 194, 0.45); }
.email-subject {
    display: block; margin-top: 0.3rem; color: var(--cream);
    font-size: 0.98rem; font-weight: 600; overflow-wrap: anywhere;
}
.email-to {
    display: block; margin-top: 0.15rem; font-size: 0.82rem;
    color: rgba(248, 227, 194, 0.5); overflow-wrap: anywhere;
}
.email-dim { color: rgba(248, 227, 194, 0.32); }
.email-error {
    display: block; margin-top: 0.25rem; font-size: 0.8rem; font-family: var(--mono);
    color: var(--err); overflow-wrap: anywhere;
}
.email-retry { flex: none; margin: 0; padding: 0.75rem 0.9rem 0 0; }

.email-preview {
    width: 100%; height: 26rem; border: 1px solid var(--cream-faint);
    border-radius: var(--radius); background: #fff;
}

.back-link { margin: 0 0 0.4rem; }
.back-link a { color: var(--cream-dim); text-decoration: none; font-size: 0.9rem; }
.back-link a:hover { color: var(--cream); }

/* ---------- cookie consent banner ---------- */
/* Injected by /analytics.js, so it only ever appears when tracking is actually on. */

/* Deliberately sized to demand a decision: a centered card rather than an ignorable strip,
   but still nowhere near a modal — the page behind it stays fully usable. */
.cookie-banner {
    position: fixed; inset: auto 1rem 1.5rem 1rem; z-index: 50;
    max-width: 30rem; margin-inline: auto;
    display: grid; gap: 1.1rem;
    background: var(--charcoal-lift);
    border: 2px solid rgba(209, 125, 12, 0.75);
    border-radius: var(--radius);
    padding: 1.5rem 1.6rem;
    box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.4), 0 24px 70px rgba(0, 0, 0, 0.75);
}
.cookie-banner p { margin: 0; font-size: 1rem; line-height: 1.55; color: var(--cream); }
.cookie-actions { display: flex; gap: 0.8rem; }
.cookie-actions .cookie-btn { flex: 1 1 0; }
.cookie-btn {
    padding: 0.72rem 1.4rem; border-radius: 999px; font: inherit; font-size: 1rem;
    font-weight: 700; cursor: pointer; border: 1px solid transparent;
}
.cookie-accept { background: var(--amber); color: var(--charcoal); }
.cookie-accept:hover { background: var(--amber-bright); }
.cookie-decline { background: none; border-color: var(--cream-faint); color: var(--cream); }
.cookie-decline:hover { border-color: var(--amber); color: var(--amber); }

/* ---------- footer ---------- */

.site-footer {
    border-top: 1px solid var(--cream-faint);
    background: var(--charcoal-deep);
    padding: 2.5rem 0 2rem;
    margin-top: 2rem;
}
.footer-inner {
    display: flex; flex-wrap: wrap; gap: 1.5rem;
    align-items: center; justify-content: space-between;
}
.footer-brand { display: flex; align-items: center; gap: 0.8rem; }
.footer-brand img { border-radius: 8px; display: block; }
.footer-name { font-weight: 800; }
.footer-by { font-size: 0.87rem; color: var(--cream-dim); }
.footer-nav { display: flex; flex-wrap: wrap; gap: 1.2rem; }
.footer-nav a { color: var(--cream-dim); text-decoration: none; font-size: 0.94rem; }
.footer-nav a:hover { color: var(--cream); }

.footer-fine {
    margin-top: 1.8rem; padding-top: 1.4rem; border-top: 1px solid var(--cream-faint);
    display: flex; flex-wrap: wrap; gap: 1rem; justify-content: space-between; align-items: flex-start;
}
.footer-fine p { font-size: 0.8rem; color: rgba(248, 227, 194, 0.45); margin: 0; max-width: 48rem; }
.footer-fine .version { font-family: var(--mono); flex: none; }

/*  Admin mail: the conversation pane.
    A chat, shaped like one: a scrollable column pinned to the newest message, operator on the
    left, console on the right — the same orientation the operator sees, mirrored. The pane owns
    its own scrollbar so the live refresh never moves the page. */
.chat-pane {
    height: min(62vh, 42rem);
    overflow-y: auto;
    display: flex; flex-direction: column; gap: 0.6rem;
    padding: 1rem;
    border: 1px solid var(--cream-faint); border-radius: var(--radius);
    background: var(--charcoal-deep);
    scrollbar-color: var(--amber-deep) transparent;
}
.chat-msg {
    max-width: 78%;
    padding: 0.55rem 0.8rem 0.45rem;
    border-radius: 10px;
    font-size: 0.95rem;
}
.chat-body { white-space: pre-wrap; overflow-wrap: anywhere; }
.chat-in { align-self: flex-start; background: var(--charcoal-lift); border: 1px solid var(--cream-faint); }
.chat-out { align-self: flex-end; background: rgba(135, 77, 5, 0.45); border: 1px solid var(--amber-deep); }
/* Machine lines (the welcome, broadcast copies) sit on the console side but must not pass as
   somebody typing — the dashed edge is the visual for "a rule sent this". */
.chat-machine { border-style: dashed; }
.chat-meta {
    margin-top: 0.3rem;
    font-size: 0.76rem; color: var(--cream-dim);
    display: flex; flex-wrap: wrap; gap: 0.15rem 0.6rem; align-items: baseline;
}
.chat-meta .linklike { font-size: 0.76rem; }
.chat-meta details { width: 100%; }
.chat-empty { align-self: center; margin: auto; color: var(--cream-dim); }
.chat-edit textarea { width: 100%; min-width: 16rem; }
.chat-diag {
    margin: 0.4rem 0 0; padding: 0.5rem 0.7rem;
    display: grid; grid-template-columns: max-content 1fr; gap: 0.15rem 0.9rem;
    background: var(--charcoal); border-radius: 8px;
    font-size: 0.78rem;
}
.chat-diag dt { color: var(--cream-dim); }
.chat-diag dd { margin: 0; font-family: var(--mono); overflow-wrap: anywhere; }

/* ---------- admin: form controls ----------
   One shared look for every control in the console, so no page has to remember to opt in and
   nothing falls back to the browser default. Scoped to body.admin (the marketing pages keep
   their own forms), written element-level and kept at the end of the sheet so it wins over
   the older per-form rules above, while deliberate exceptions (.triage-text, the nav toggle)
   are excluded by name. */

body.admin { accent-color: var(--amber); }

body.admin input:not([type="checkbox"], [type="radio"], [type="hidden"]),
body.admin select,
body.admin textarea:not(.triage-text) {
    padding: 0.55rem 0.85rem;
    background-color: var(--charcoal-deep);
    border: 1px solid rgba(248, 227, 194, 0.22);
    border-radius: 10px;
    color: var(--cream);
    font: inherit; font-size: 0.95rem; line-height: 1.45;
    caret-color: var(--amber-bright);
    transition: border-color 0.12s, box-shadow 0.12s;
}
body.admin input:not([type="checkbox"], [type="radio"], [type="hidden"]):hover:not(:focus),
body.admin select:hover:not(:focus),
body.admin textarea:not(.triage-text):hover:not(:focus) {
    border-color: rgba(248, 227, 194, 0.42);
}
body.admin input:not([type="checkbox"], [type="radio"], [type="hidden"]):focus,
body.admin select:focus,
body.admin textarea:not(.triage-text):focus {
    outline: none;
    border-color: var(--amber);
    box-shadow: 0 0 0 3px rgba(209, 125, 12, 0.22);
}
body.admin input:disabled,
body.admin select:disabled,
body.admin textarea:disabled { opacity: 0.5; cursor: not-allowed; }
body.admin input::placeholder,
body.admin textarea::placeholder { color: rgba(248, 227, 194, 0.35); }

/* Selects drop the platform widget chrome and draw their own chevron; the padding reserves
   its lane. background-color (never the shorthand) everywhere, or the arrow vanishes. */
body.admin select {
    appearance: none; -webkit-appearance: none;
    padding-right: 2.3rem;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M1 1.5l5 5 5-5' fill='none' stroke='%23f8e3c2' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round' stroke-opacity='0.65'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.85rem center;
    background-size: 0.7rem auto;
    cursor: pointer;
}
/* The pop-up option list is painted by the OS from the control's colors; without this it
   comes up white-on-white on Windows. */
body.admin select option { background-color: var(--charcoal-deep); color: var(--cream); }

body.admin textarea:not(.triage-text) { width: 100%; resize: vertical; line-height: 1.5; }

/* The alert-feed filter select shares a row of pill tabs and buttons; match their height
   and shape on top of the shared select styling. */
body.admin .alert-filter select {
    padding: 0.36rem 2.1rem 0.36rem 0.85rem;
    border-radius: 999px;
    font-size: 0.88rem;
    background-position: right 0.8rem center;
}

/* Checkboxes drawn from scratch: a rounded box that fills amber, with the tick cut from a
   clip-path so it needs no image. The sidebar's nav toggle is a checkbox too and must stay
   invisible, hence the exclusion. */
body.admin input[type="checkbox"]:not(.admin-nav-toggle) {
    appearance: none; -webkit-appearance: none;
    width: 1.2rem; height: 1.2rem; flex: none; margin: 0;
    display: inline-grid; place-content: center;
    vertical-align: -0.25em;
    background-color: var(--charcoal-deep);
    border: 1.5px solid rgba(248, 227, 194, 0.4);
    border-radius: 6px;
    cursor: pointer;
    transition: background-color 0.12s, border-color 0.12s, box-shadow 0.12s;
}
body.admin input[type="checkbox"]:not(.admin-nav-toggle)::before {
    content: "";
    width: 0.68rem; height: 0.68rem;
    transform: scale(0);
    transition: transform 0.1s;
    background: var(--charcoal-deep);
    clip-path: polygon(14% 44%, 0 65%, 40% 100%, 100% 16%, 84% 0%, 38% 66%);
}
body.admin input[type="checkbox"]:not(.admin-nav-toggle):hover { border-color: var(--amber); }
body.admin input[type="checkbox"]:not(.admin-nav-toggle):checked,
body.admin input[type="checkbox"]:not(.admin-nav-toggle):indeterminate {
    background-color: var(--amber);
    border-color: var(--amber);
}
body.admin input[type="checkbox"]:not(.admin-nav-toggle):checked::before { transform: scale(1); }
/* The select-all box while only some rows are ticked (bulk-select.js sets it). */
body.admin input[type="checkbox"]:not(.admin-nav-toggle):indeterminate::before {
    transform: scale(1);
    clip-path: none;
    width: 0.7rem; height: 0.19rem; border-radius: 2px;
}
body.admin input[type="checkbox"]:not(.admin-nav-toggle):focus-visible {
    outline: none;
    border-color: var(--amber-bright);
    box-shadow: 0 0 0 3px rgba(209, 125, 12, 0.28);
}
/* Rows align the box with the first line of a wrapping label. */
body.admin .sub-pick input[type="checkbox"] { margin-top: 0.15rem; }
@media (max-width: 640px) {
    /* The tick-boxes are the thing being aimed at most often on a phone. */
    body.admin :is(.bulk-all, .pick) input[type="checkbox"] { width: 1.4rem; height: 1.4rem; }
}

/* A button with no class at all still comes out dressed — the quiet sibling of .btn-sm.
   Kept at zero specificity so every classed variant (.btn, .btn-sm, .linklike, .alert-x)
   overrides it untouched. */
:where(body.admin) button:where(:not([class])) {
    padding: 0.5rem 1.2rem;
    border-radius: 999px;
    background: var(--charcoal-lift);
    border: 1px solid rgba(248, 227, 194, 0.25);
    color: var(--cream);
    font: inherit; font-size: 0.92rem; font-weight: 700;
    cursor: pointer;
    transition: border-color 0.12s, color 0.12s, background 0.12s;
}
:where(body.admin) button:where(:not([class])):hover { border-color: var(--amber); color: var(--amber); }

/* Labelled field blocks, as used bare on Settings, Broadcasts and the mail pages — the same
   shape .stacked-form gives its children, without needing the wrapper. */
body.admin .field {
    display: grid; gap: 0.4rem; justify-items: start;
    margin: 0 0 1.3rem; max-width: 36rem;
}
body.admin .stacked-form .field { margin: 0; }
body.admin .field > input, body.admin .field > select, body.admin .field > textarea { justify-self: stretch; }
body.admin .field > label:not(:has(input)) {
    font-size: 0.78rem; font-weight: 700; color: var(--cream-dim);
    text-transform: uppercase; letter-spacing: 0.08em;
}
/* A label that wraps its own checkbox is a sentence, not a caption. */
body.admin .field label:has(input[type="checkbox"]) {
    display: flex; align-items: flex-start; gap: 0.6rem;
    color: var(--cream); font-size: 0.96rem; line-height: 1.5; cursor: pointer;
}
body.admin .field label:has(input[type="checkbox"]) input[type="checkbox"] { margin-top: 0.2rem; }
body.admin .field-note {
    margin: 0; font-size: 0.84rem; line-height: 1.55;
    color: rgba(248, 227, 194, 0.5);
}
body.admin .form-actions { margin-top: 1.6rem; }
