/* Broadview Heights Today — admin panel (self-hosted). */
:root { --blue:#1f66a8; --blue-d:#123f6b; --green:#268a58; --line:#e2e8ee; --ink:#1c2530; --soft:#4a5866; --bg:#f2f5f9; }
* { box-sizing: border-box; }
body.admin { margin:0; font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Arial,sans-serif; background:var(--bg); color:var(--ink); }
a { color:var(--blue); text-decoration:none; } a:hover { text-decoration:underline; }

.admin-top { background:linear-gradient(180deg,#0b2e4f,#123f6b); color:#fff; border-bottom:3px solid #d99a2b; }
.admin-top-inner { max-width:1080px; margin:0 auto; display:flex; align-items:center; gap:20px; padding:12px 20px; flex-wrap:wrap; }
.admin-brand { color:#fff; font-weight:800; font-size:1.1rem; }
.admin-nav { display:flex; gap:6px; flex-wrap:wrap; }
.admin-nav a { color:#dbe7f3; padding:7px 12px; border-radius:8px; font-weight:600; font-size:.92rem; }
.admin-nav a:hover { background:rgba(255,255,255,.12); color:#fff; text-decoration:none; }
.admin-user { margin-left:auto; display:flex; align-items:center; gap:12px; font-size:.9rem; }
.admin-user a { color:#ffd98a; }

.admin-main { max-width:1080px; margin:24px auto; padding:0 20px 60px; }
h1 { font-size:1.6rem; color:var(--blue-d); margin:0 0 18px; }
h2 { font-size:1.2rem; color:var(--blue-d); }

.flash { padding:12px 16px; border-radius:9px; margin-bottom:18px; font-weight:600; }
.flash.ok { background:#e9f6ee; color:#1f7a4d; border:1px solid #bfe6cf; }
.flash.err { background:#fbeaea; color:#a23; border:1px solid #f0c9c9; }

.cards { display:grid; grid-template-columns:repeat(auto-fit,minmax(180px,1fr)); gap:16px; margin-bottom:24px; }
.stat { background:#fff; border:1px solid var(--line); border-radius:12px; padding:18px 20px; box-shadow:0 1px 2px rgba(16,42,67,.05); }
.stat .n { font-size:2rem; font-weight:800; color:var(--blue-d); }
.stat .l { color:var(--soft); font-size:.85rem; text-transform:uppercase; letter-spacing:.05em; }

.panel { background:#fff; border:1px solid var(--line); border-radius:12px; box-shadow:0 1px 2px rgba(16,42,67,.05); margin-bottom:22px; overflow:hidden; }
.panel-h { padding:14px 18px; border-bottom:1px solid var(--line); font-weight:700; display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap; }
.panel-b { padding:18px; }

.status-dot { display:inline-block; width:9px; height:9px; border-radius:50%; margin-right:6px; vertical-align:middle; }
.status-dot.up { background:#268a58; } .status-dot.down { background:#c0392b; }

table { width:100%; border-collapse:collapse; }
th, td { text-align:left; padding:11px 12px; border-bottom:1px solid var(--line); font-size:.94rem; vertical-align:top; }
th { color:var(--soft); font-size:.76rem; text-transform:uppercase; letter-spacing:.05em; }
tr:hover td { background:#f8fafc; }
.tag { display:inline-block; font-size:.68rem; font-weight:700; text-transform:uppercase; letter-spacing:.05em; padding:2px 8px; border-radius:999px; }
.tag.draft { background:#fff3d6; color:#8a6417; } .tag.published { background:#e9f6ee; color:#1f7a4d; }
.tag.news { background:#eef5fb; color:#1a5088; } .tag.weather { background:#e9f6ee; color:#1f7a4d; }
.tag.history { background:#f6efe0; color:#8a6417; } .tag.feature { background:#f0eefb; color:#5b46a8; }
.tag.audio-ready { background:#e9f6ee; color:#1f7a4d; } .tag.audio-pending { background:#fff3d6; color:#8a6417; }
.tag.audio-failed { background:#fbeaea; color:#a23; } .tag.audio-none { background:#eef1f4; color:#667; }

.btn { display:inline-block; background:var(--blue); color:#fff; border:0; padding:8px 16px; border-radius:8px; font-weight:600; cursor:pointer; font-size:.9rem; }
.btn:hover { background:var(--blue-d); text-decoration:none; }
.btn.green { background:var(--green); } .btn.green:hover { background:#1f7a4d; }
.btn.ghost { background:#eef2f6; color:var(--blue-d); } .btn.ghost:hover { background:#dfe7ef; }
.btn.danger { background:#c0392b; } .btn.danger:hover { background:#a5281c; }
.btn.sm { padding:5px 10px; font-size:.82rem; }
.inline-form { display:inline; }
.row-actions { display:flex; gap:6px; flex-wrap:wrap; }

.field { margin-bottom:16px; }
.field label { display:block; font-weight:600; margin-bottom:6px; color:var(--blue-d); font-size:.92rem; }
.field .hint { color:var(--soft); font-size:.82rem; margin-top:4px; }
input[type=text], input[type=url], input[type=email], input[type=password], textarea, select {
    width:100%; padding:10px 12px; border:1px solid var(--line); border-radius:8px; font-size:.98rem; font-family:inherit; background:#fff;
}
textarea { min-height:220px; resize:vertical; line-height:1.5; }
.checkbox { display:flex; align-items:center; gap:10px; }
.checkbox input { width:auto; }
.grid2 { display:grid; grid-template-columns:1fr 1fr; gap:16px; }
@media (max-width:640px){ .grid2 { grid-template-columns:1fr; } }

.toolbar { display:flex; gap:10px; align-items:center; margin-bottom:18px; flex-wrap:wrap; }
.filters a { padding:6px 12px; border-radius:999px; background:#fff; border:1px solid var(--line); color:var(--soft); font-weight:600; font-size:.85rem; }
.filters a.active { background:var(--blue); color:#fff; border-color:var(--blue); }

/* Login */
.login-wrap { max-width:380px; margin:8vh auto; }
.login-card { background:#fff; border:1px solid var(--line); border-radius:14px; box-shadow:0 8px 30px rgba(16,42,67,.12); overflow:hidden; }
.login-head { background:linear-gradient(135deg,#0b2e4f,#1a5088); color:#fff; padding:24px; text-align:center; }
.login-head h1 { color:#fff; margin:0; font-size:1.3rem; }
.login-head p { margin:6px 0 0; color:#bcd2e6; font-size:.9rem; }
.login-body { padding:24px; }

/* ============================================================= R2: portal additions */
.stat .n .of { font-size:1rem; font-weight:600; color:var(--soft); }

.type-chips { display:flex; gap:10px; flex-wrap:wrap; margin:-8px 0 22px; }
.type-chips .chip { display:inline-flex; align-items:center; gap:8px; background:#fff; border:1px solid var(--line); border-radius:999px; padding:5px 12px; color:var(--ink); font-size:.85rem; }
.type-chips .chip b { font-weight:800; color:var(--blue-d); }

.grid-2col { display:grid; grid-template-columns:1fr 1fr; gap:20px; margin-bottom:20px; }
@media (max-width:820px){ .grid-2col { grid-template-columns:1fr; } }
.panel .panel-b hr { border:0; border-top:1px solid var(--line); margin:14px 0; }

/* schedule panel */
.sched-next { font-size:1.05rem; margin:0 0 4px; }
.sched-countdown { display:inline-block; background:#e9f6ee; color:#1f7a4d; font-weight:700; font-size:.82rem; padding:2px 10px; border-radius:999px; margin-left:6px; }
.sched-date { color:var(--soft); margin:0 0 4px; }
.sched-last { margin:0 0 4px; }
.sched-summary { color:var(--ink); background:#f4f7fb; border:1px solid var(--line); border-radius:8px; padding:8px 12px; margin:6px 0 0; font-size:.9rem; }

.quick-actions { display:flex; gap:8px; flex-wrap:wrap; align-items:center; }

/* image picker */
.imgpick-current { display:flex; gap:14px; align-items:flex-start; margin-bottom:12px; }
.imgpick-current img { width:180px; height:auto; max-height:120px; object-fit:cover; border-radius:8px; border:1px solid var(--line); }
.imgpick-none { color:var(--soft); font-size:.9rem; background:#f4f7fb; border:1px dashed var(--line); border-radius:8px; padding:12px 14px; }
.imgpick-modes { display:flex; gap:16px; flex-wrap:wrap; margin-bottom:12px; }
.imgpick-modes .rad { font-weight:500; color:var(--ink); display:inline-flex; align-items:center; gap:6px; font-size:.9rem; }
.imgpick-panel { background:#f7f9fc; border:1px solid var(--line); border-radius:8px; padding:14px; margin-bottom:8px; }
.curated-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(150px,1fr)); gap:12px; }
.curated-item { display:block; cursor:pointer; text-align:center; border:2px solid transparent; border-radius:8px; padding:4px; }
.curated-item img { width:100%; aspect-ratio:16/9; object-fit:cover; border-radius:6px; display:block; border:1px solid var(--line); }
.curated-item span { display:block; font-size:.82rem; margin-top:4px; color:var(--soft); }
.curated-item input { position:absolute; opacity:0; }
.curated-item:has(input:checked) { border-color:var(--blue); background:#eaf2fb; }
.curated-item input:checked ~ img { outline:2px solid var(--blue); }

.edit-actions { display:flex; gap:10px; flex-wrap:wrap; margin-top:14px; }

/* cameras admin */
.cam-thumb { width:120px; height:68px; object-fit:cover; border-radius:6px; border:1px solid var(--line); display:block; background:#0d1b2a; }

/* ---- Directory moderation queues (admin) --------------------------------- */
.admin-flash.ok { background:#e8f2ef; border:1px solid #bfe0d5; color:#2c6e5e;
                  padding:11px 14px; border-radius:9px; margin-bottom:16px; }
.mod-counts { display:grid; grid-template-columns:repeat(auto-fit,minmax(150px,1fr)); gap:12px;
              margin:16px 0 26px; }
.mod-counts div { background:#fff; border:1px solid #e2e8ee; border-radius:12px; padding:14px 16px; }
.mod-counts strong { display:block; font-size:1.8rem; line-height:1; color:#1b2a33; }
.mod-counts span { font-size:.82rem; color:#4a5866; text-transform:uppercase; letter-spacing:.05em; }
.mod-note { color:#4a5866; font-size:.9rem; margin:-6px 0 14px; line-height:1.5; }
.mod-empty { color:#8892a0; font-style:italic; margin:0 0 24px; }
.mod-card { background:#fff; border:1px solid #e2e8ee; border-radius:12px; padding:14px 16px;
            margin-bottom:12px; box-shadow:0 1px 2px rgba(16,42,67,.05); }
.mod-card-head { display:flex; flex-wrap:wrap; gap:10px; align-items:baseline;
                 justify-content:space-between; margin-bottom:6px; }
.mod-meta { color:#8892a0; font-size:.82rem; }
.mod-body { margin:6px 0 12px; line-height:1.55; color:#1b242e; white-space:pre-wrap; }
.mod-actions { display:flex; flex-wrap:wrap; gap:10px; }
.mod-actions form { display:flex; gap:6px; align-items:center; }
.mod-actions input[type="text"] { padding:7px 10px; border:1px solid #e2e8ee; border-radius:8px;
                                  font:inherit; font-size:.86rem; min-width:190px; }
.mod-actions .btn { padding:7px 14px; font-size:.86rem; border:0; border-radius:8px;
                    cursor:pointer; color:#fff; font-weight:700; }
.mod-actions .btn.ok   { background:#2c6e5e; }
.mod-actions .btn.warn { background:#8a6414; }
.mod-actions .btn.bad  { background:#8a2b2b; }
.mod-log { width:100%; border-collapse:collapse; font-size:.86rem; background:#fff;
           border:1px solid #e2e8ee; border-radius:12px; overflow:hidden; }
.mod-log th, .mod-log td { padding:8px 12px; text-align:left; border-bottom:1px solid #eef1f4; }
.mod-log thead th { background:#eef3f7; font-size:.76rem; text-transform:uppercase;
                    letter-spacing:.05em; color:#1b2a33; }
.mod-log tr:last-child td { border-bottom:0; }
@media (max-width:640px){ .mod-actions form { width:100%; } .mod-actions input[type="text"]{ flex:1; } }

/* ---- Reported item that is not on the public page right now --------------
   Held review, hidden comment, suspended listing. The link still opens the
   listing; this says why the thing being judged will not be on it. Muted,
   not alarming: it is a normal state, not an error. */
.mod-offpage {
    display: inline-block; margin-left: 6px; padding: 1px 7px;
    font-size: .72rem; font-weight: 600; letter-spacing: .01em;
    color: #8a5a00; background: #fff4d6; border: 1px solid #f0d79a;
    border-radius: 999px; vertical-align: middle;
}

/* ---- Admin on a phone ----------------------------------------------------
   Two real faults on /admin/posts at 360px, plus a mobile treatment for the
   wide tables.

   1. FILTER PILLS COLLIDED. `.filters` had no rule of its own — only
      `.filters a` existed — so the anchors were INLINE. Vertical padding on an
      inline box does not grow the line box, so each pill painted outside its
      own row and overlapped the row above. Flex makes every pill a real block
      in a wrapping row, which is what the padding always assumed.

   2. HALF THE TABLE WAS UNREACHABLE. `.panel` sets overflow:hidden and the
      posts table is eight columns wide, so everything past ~310px was clipped
      with no way to scroll to it: Verification, Image, Audio, Date and every
      row's Edit/View/Delete. Not "hard to read" — genuinely inaccessible.

   The two table treatments below are deliberately MUTUALLY EXCLUSIVE via
   :not(.admin-cards). Written as overrides they fought each other on
   specificity — .panel-b:has(> table) > table beats .admin-cards — which left
   the card layout stuck at min-width:620px with its buttons off screen and a
   sticky first column it had already been told to drop.
--------------------------------------------------------------------------- */

.filters { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.filters a { display: inline-flex; align-items: center; white-space: nowrap; }

/* --- Tables that stay tables: let them scroll instead of being clipped ----
   Scoped with :has() so this only ever turns a TABLE panel into a scroll
   container. Applying overflow-x to every .panel-b would be wrong: a box with
   overflow-x:auto and overflow-y:visible computes y to auto as well, which
   would silently start clipping any panel with something meant to hang out of
   it. Browsers without :has() keep today's behaviour. */
.panel-b:has(> table:not(.admin-cards)) { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.panel-b:has(> table:not(.admin-cards)) > table { min-width: 620px; }

@media (max-width: 760px) {
    /* Pin the first column while the rest scrolls sideways, so a row never
       becomes an anonymous strip of yes/no. border-collapse drops borders on
       sticky cells, hence the box-shadow standing in for the right-hand rule. */
    .panel-b:has(> table:not(.admin-cards)) > table th:first-child,
    .panel-b:has(> table:not(.admin-cards)) > table td:first-child {
        position: sticky;
        left: 0;
        z-index: 1;
        background: #fff;
        box-shadow: 1px 0 0 var(--line);
        max-width: 44vw;
    }

    .panel-b:has(> table:not(.admin-cards)) > table th,
    .panel-b:has(> table:not(.admin-cards)) > table td { padding: 9px 10px; font-size: .9rem; }

    /* Full-width primary actions read better than cramped pills. */
    .toolbar > .btn, .toolbar > .inline-form { flex: 1 1 auto; }
    .toolbar > .inline-form > .btn { width: 100%; }
}

/* --- Tables that become cards -------------------------------------------
   Scrolling sideways past six columns to reach Edit/Delete is not a mobile
   view, it is a desktop table you can pan. Each row becomes a card and every
   action is in reach with no horizontal scrolling at all.

   The labels come from nth-child, so this is TIED TO THE COLUMN ORDER of the
   posts table: Title, Type, Status, Verification, Image, Audio, Date, Actions.
   That is why it is opt-in via .admin-cards rather than applied to every
   table — subscribers and ads have different columns and would get
   confidently wrong labels. Reorder or insert a column here and you must fix
   the labels below in the same commit. */
@media (max-width: 760px) {
    .panel-b:has(> .admin-cards) { overflow-x: visible; }

    table.admin-cards { display: block; width: 100%; min-width: 0; }
    table.admin-cards thead { display: none; }
    table.admin-cards tbody,
    table.admin-cards tr,
    table.admin-cards td { display: block; width: auto; max-width: none; }

    table.admin-cards tr {
        border-bottom: 8px solid var(--bg, #eef2f6);
        padding: 12px 14px 14px;
    }
    table.admin-cards tr:last-child { border-bottom: 0; }

    table.admin-cards td {
        border: 0;
        padding: 3px 0;
        font-size: .92rem;
        position: static;      /* never sticky in card mode */
        box-shadow: none;      /* and no left-column divider */
    }

    /* Title is the card heading: no label, full width. */
    table.admin-cards td:first-child { padding-bottom: 8px; font-size: 1rem; }

    /* Give every middle cell its column name back. */
    table.admin-cards td:nth-child(2)::before { content: 'Type'; }
    table.admin-cards td:nth-child(3)::before { content: 'Status'; }
    table.admin-cards td:nth-child(4)::before { content: 'Verification'; }
    table.admin-cards td:nth-child(5)::before { content: 'Image'; }
    table.admin-cards td:nth-child(6)::before { content: 'Audio'; }
    table.admin-cards td:nth-child(7)::before { content: 'Date'; }
    table.admin-cards td:nth-child(n+2):nth-child(-n+7)::before {
        display: inline-block;
        min-width: 92px;
        color: var(--soft);
        font-size: .74rem;
        text-transform: uppercase;
        letter-spacing: .05em;
        font-weight: 700;
    }

    /* Actions: a full-width row of thumb-sized buttons, all on screen. */
    table.admin-cards td:last-child {
        display: flex;
        flex-wrap: wrap;
        gap: 8px;
        padding-top: 10px;
        white-space: normal;
    }
    table.admin-cards td:last-child > .btn {
        flex: 1 1 auto;
        min-width: 0;
        text-align: center;
    }
}
/* ---- The dashboard on a phone -------------------------------------------
   The owner runs both hubs from a 360px screen. The dashboard was not broken
   there the way /admin/posts was — nothing overlapped, nothing was
   unreachable — it was just enormous: 1829px of page to show what is really
   about a dozen facts, because almost none of the dashboard's own text has a
   size of its own and the panel chrome was written for a 1080px column.

   Everything below is inside a media query. Desktop is byte-for-byte
   untouched by design, not by accident — see the note at the end about the
   one rule that arguably belongs at every width.
--------------------------------------------------------------------------- */

@media (max-width: 760px) {

    /* 1. `.hint` HAD NO RULE OF ITS OWN. admin.css only ever styles
       `.field .hint`, so the two bare hint paragraphs the dashboard uses fell
       all the way through to body text — 16px, full ink, the same weight as
       the heading they exist to explain. The auto-publish note rendered as a
       five-line wall of black prose that owned the card.

       These are the same three declarations `.field .hint` already carries,
       just no longer locked to a form field, so a hint looks like a hint
       wherever the admin puts one. Nothing is hidden and nothing is clipped:
       the label and the OFF pill actually get MORE prominent relative to the
       prose, because the prose stops shouting. */
    .hint { color: var(--soft); font-size: .82rem; line-height: 1.45; }
    .hint strong { color: var(--blue-d); }

    /* 2. `body.admin` never set a font-size, so every unclassed paragraph
       inherited the browser default. 16px is right for a 1080px column and
       too big for a 328px one, where each extra point costs a wrapped line
       per sentence. Set in REM, not px: someone who has raised their
       browser's default text size still gets a bigger admin, which a px value
       would have silently taken away from them. Everything else in admin.css
       is already sized in rem, so this moves the prose and nothing else —
       buttons, tags, table cells and headings are unaffected. */
    body.admin { font-size: .95rem; }
    .panel-b > p:not([class]) { margin: 0 0 9px; }

    /* 3. THE SCHEDULE SAID ONE FACT THREE TIMES, stacked three high: the
       recurrence rule, a countdown pill, and the absolute date. All three
       stay — the absolute date is the only unambiguous one and the countdown
       is the one you actually read — but they are not three peer facts, they
       are one fact plus two ways of checking it. So the recurrence keeps the
       headline, the countdown rides beside it, and the absolute date drops to
       small muted type with the gap between them closed to nothing. Same
       information, one block instead of three. */
    .sched-next { margin: 0; font-size: 1rem; line-height: 1.35; }
    .sched-date { margin: 1px 0 0; font-size: .8rem; line-height: 1.35; }

    /* 4. "Last run: Jul 27, 2026 6:10 AM (5 days ago) [ok]" wrapped and left
       "ago)" and the status badge stranded on a line of their own, where a
       lone green OK pill reads as a second, empty fact.

       Shrinking the type was the obvious fix and it is the wrong one: the run
       measures 57.5 + 208.4 + 33.4 + spaces = 307px into a 298px box, so it
       fits at .8rem by four pixels — and then "Dec 27, 2026 12:10 PM (12 days
       ago)" is two characters longer and it orphans again. Fitting today's
       string is not the same as fixing it.

       Flex fixes it for every string instead. The three children become flex
       items, so the date and its badge can no longer be split by a line break
       in the middle of the date; forcing the label onto its own row leaves the
       value and the badge 298px to share, which they need 247px of. Still two
       lines at 360px and up, and it costs a few pixels of height on the live
         string (36 -> 41.5px).

         HONEST LIMIT, measured: at 320px the badge still drops to its own row
         for about 5 of 8 realistic date strings and the block grows 36 -> 61px.
         Flex RELOCATES the orphan below ~344px, it does not abolish it — an
         earlier draft of this comment claimed 'for any string', which is false — but now it is a label with its
       value under it, not a sentence with its punchline knocked off.
       baseline alignment keeps the pill sitting on the text, not floating. */
    .sched-last {
        margin: 0; font-size: .84rem; line-height: 1.5;
        display: flex; flex-wrap: wrap; align-items: baseline; column-gap: 5px;
    }
    .sched-last > strong { flex: 1 0 100%; }
    .sched-summary { margin-top: 6px; padding: 7px 10px; font-size: .82rem; }

    /* 5. DEAD SPACE. `.panel` carries a 22px bottom margin and `.grid-2col`
       adds its own 20px row gap on top of it, so once the two-column grid
       stacked, every panel was separated from the next by 42px of nothing —
       three finger-widths of empty page between cards that belong together.
       Zeroing the margin inside the grid lets the gap be the single source of
       that spacing instead of the two of them adding up. */
    .grid-2col { gap: 14px; margin-bottom: 14px; }
    .grid-2col > .panel { margin-bottom: 0; }
    .panel { margin-bottom: 14px; }

    /* Panel chrome sized for a 1080px column: 18px of padding on a 360px
       screen is 11% of the width spent on air, and every pixel taken back
       here is a pixel of line length given to the text, which is worth more
       than once because it un-wraps lines further down the card. */
    .panel-h { padding: 12px 14px; }
    .panel-b { padding: 14px; }
    .panel .panel-b hr { margin: 10px 0; }

    /* 6. TAP TARGETS. `.btn.sm` is a 24px-tall pill and `.chip` a 28px one —
       fine under a mouse pointer, under every thumb-sized minimum there is.
       min-height rather than padding, so the box grows without the label
       growing with it and without the number changing if the label wraps;
       inline-flex is what makes min-height actually centre the text on an
       anchor. Row-level Edit gets 34px rather than 40 on purpose — it is a
       secondary action repeated once per row, and 40 would visibly inflate
       the table for no gain.

       Scoped to the dashboard's own controls plus plain table rows. The
       `.admin-cards` action buttons on /admin/posts were shipped and verified
       last week and are deliberately not in any selector here. */
    .type-chips .chip { min-height: 38px; }
    /* `.panel-b > form.inline-form > .btn` is here because the padding change
       above let 'Generate now' unwrap to a single line, taking it 49px -> 33px:
       the primary action on the page ending up SHORTER than the ghost buttons
       under it. This selector only ever enlarges. */
    .quick-actions .btn, .panel-h .btn, .panel-b > form.inline-form > .btn {
        display: inline-flex; align-items: center; justify-content: center;
        min-height: 40px; padding: 6px 14px;
    }
    .panel-b > table:not(.admin-cards) .btn.sm {
        display: inline-flex; align-items: center; justify-content: center;
        min-height: 34px; padding: 4px 12px;
    }

    /* The chip row sat 22px clear of the panels below it, set back when the
       stat cards above were a single 90px band. It is part of the same
       rhythm now. */
    .type-chips { margin-bottom: 14px; }
}

@media (max-width: 480px) {

    /* 7. THE STAT CARDS WERE 28% OF THE PAGE. Five cards showing five short
       numbers took 514px of a 1829px page, because `minmax(180px,1fr)` cannot
       fit two columns once the content box drops under ~396px, so auto-fit
       collapses to one and each number gets a full-width band of its own.
       Forcing two columns here is not an override of that grid so much as
       finishing it: at 480px auto-fit already yields two columns, so this
       changes nothing at the boundary and only bites below ~432px where the
       grid would otherwise give up. Tablets keep today's layout exactly.

       :last-child:nth-child(odd) stretches the final card across both columns
       ONLY when there is an odd number of them, so it never widens a card
       that has a partner. dashboard.php differs between the two hubs and may
       well show a different number of stats — this has to be right for four
       and six as well as five. */
    .cards { grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 16px; }
    .cards .stat:last-child:nth-child(odd) { grid-column: 1 / -1; }
    .stat { padding: 12px 14px; }
    .stat .n { font-size: 1.75rem; line-height: 1.15; }
    .stat .l { font-size: .7rem; line-height: 1.3; }

    /* Page chrome. `.admin-main` spends 40px of a 360px screen on side
       padding and opens with a 24px margin under a nav bar that already has
       its own. h1 at 1.6rem is a desktop headline; the page has exactly one
       and its job here is to say which screen you are on. */
    .admin-main { margin: 14px auto; padding: 0 12px 40px; }
    h1 { font-size: 1.35rem; margin-bottom: 12px; }
}

/* ---- Two things deliberately NOT changed above ---------------------------

   THE DASHBOARD'S "RECENT POSTS" TABLE KEEPS THE SCROLL TREATMENT. It is six
   columns — Title, Type, Status, Image, Updated, actions — and it is tempting
   to hand it `.admin-cards` and be done. Do not.

   The card labels above are generated by nth-child against the /admin/posts
   column order (Type, Status, Verification, Image, Audio, Date). Run this
   table through them and column 4 gets labelled "Verification" over an image
   source and column 5 "Image" over a date: not ugly, WRONG, on a live admin,
   in the one place the owner goes to check whether a post is safe to publish.

   A second, correct label set is easy to write; the problem is the selector
   to hang it on. There is no class on this table, so the hook would have to
   be structural — position, column count, an inline style — and dashboard.php
   is not the same file on the two hubs. A structural hook either misses on
   one hub (harmless) or matches a different six-column table somewhere in the
   admin and mislabels that instead (not harmless). Guessing at markup that is
   known to differ, to save one horizontal swipe on a two-row preview, is a
   bad trade.

   So this one genuinely does need the view: `class="admin-cards"` on the
   dashboard table in BOTH hubs, plus a label set keyed to its column order.
   Until then the table scrolls with its title column pinned, and "View all"
   is one tap from the posts list, which IS card-ified. Nothing is lost, two
   controls need a swipe.

   `.hint` IS FIXED ONLY BELOW 760px, AND THAT IS ARBITRARY. The missing rule
   is missing at every width — the same paragraph is three lines of full-size
   black prose on a 1280px desktop, where it is just as wrong and just as much
   the loudest thing in the card. Moving the two `.hint` declarations out of
   the media query fixes desktop too and is a two-line change. It is parked
   here rather than shipped because desktop was required to come out of this
   pass pixel-for-pixel identical, and that change moves it. Worth doing on
   purpose, with the owner looking.
--------------------------------------------------------------------------- */

/* Hides the admin-login honeypot. Moved out of an inline <style> in
   admin/login.php: style-src-elem carries a nonce, which makes 'unsafe-inline'
   inoperative for <style> elements, so under CSP the rule was refused and the
   trap field became visible to real people. */
.login-trap {
    position: absolute !important;
    left: -10000px !important;
    width: 1px !important;
    height: 1px !important;
    overflow: hidden !important;
}

/* ---- Social suggestions (admin) ------------------------------------------ */
.sugg { padding: 14px 16px; margin: 0 0 14px; }
.sugg-used { opacity: .62; }
.sugg-head { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.sugg-head .badge { background: #137333; color: #fff; border-radius: 999px; padding: 1px 9px; font-size: .74rem; }
.sugg-body {
    width: 100%; font: inherit; font-size: .95rem; line-height: 1.5;
    padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: #fbfcfd; resize: vertical;
}
.sugg-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-top: 10px; }
.sugg-actions form { margin: 0; }
.btn.copied { background: #137333; border-color: #137333; color: #fff; }

/* ==========================================================================
   Noticeboard queue. Extends the existing .mod-* moderation styling rather
   than replacing it, so the notice queue and the comment queue look alike.
   ========================================================================== */

.mod-block { margin: 26px 0 0; }
.mod-block h2 { margin: 0 0 6px; font-size: 1.08rem; }

.mod-card .pill { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: .74rem;
                  font-weight: 600; background: #eef3f7; color: #33505e; margin-right: 4px; }
.mod-card .pill-public { background: #fdf1e3; color: #92601f; }
.mod-card .pill-member { background: #e8f2ef; color: #2c6e5e; }
.mod-card .pill-staff  { background: #263a46; color: #fff; }

/* The whole body, not an excerpt: a moderator deciding whether words are
   defamatory has to be able to read the words. */
.mod-body { background: #fff; border: 1px solid #e2e8ee; border-radius: 9px;
            padding: 10px 12px; margin: 8px 0; font-size: .93rem; line-height: 1.6; }
.mod-body p:last-child { margin-bottom: 0; }
.mod-thumb { max-width: 220px; max-height: 160px; object-fit: cover; border-radius: 9px; margin: 4px 0 8px; }
.mod-contact, .mod-who { margin: 6px 0; font-size: .86rem; color: #4a5866; }

.admin-lede { margin: 0 0 18px; display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
.admin-form { max-width: 700px; }
.admin-form label { display: block; margin: 16px 0 5px; font-weight: 600; font-size: .94rem; }
.admin-form input[type=text], .admin-form select, .admin-form textarea, .admin-form input[type=file] {
    width: 100%; padding: 9px 11px; border: 1px solid #e2e8ee; border-radius: 9px; font: inherit; }
.admin-form textarea { resize: vertical; }
.admin-form button { margin-top: 20px; }
.admin-form .opt { font-weight: 400; color: #4a5866; font-size: .86rem; }
