/* ============================================================
   admin.css — admin surfaces + /updates archive
   ============================================================
   Owns the admin console end-to-end and the public /updates
   archive (read-only mirror of admin announcements):

     - Admin console pr40 (two-column shell with left rail)
     - Admin announcements (an1 + fe-an1 redesign +
       FACETED FILTER + LIST ROWS inbox-style)
     - System status page (pwa5-followup-system-status)
     - /updates archive (up1/up2/up3/up4): facet form, banner
       title-link + see-all footer, dashboard column body
     - Announcement markdown bodies (an6)
     - Announcement composer cheat-sheet (an6)

   Reference: markdown/design/DESIGN_DECISIONS_CSS_REFACTOR.md
   ============================================================ */

/* ============================================================
   Admin console (pr40)
   ============================================================
   Two-column layout for the /admin/* surface: persistent left rail
   with grouped section links, content column on the right. Replaces
   the previous warn-yellow ADMIN strip + horizontal nav row.

   Token discipline: every color, radius, spacing value here is a
   var() reference so dark mode is free. The yellow "admin" label
   in the rail header uses --color-warning (the same token the rest
   of the app uses for warnings) so admin-land still feels distinct.

   Mobile: below 720px the rail stacks above content. Drag-handle and
   sticky behavior dropped on mobile — the rail just scrolls with
   the page.
   ============================================================ */

.admin-console {
    display: grid;
    grid-template-columns: 220px 1fr;
    gap: var(--space-5);
    max-width: 1600px;
    margin: var(--space-5) auto;
    padding: 0 var(--space-4);
    align-items: start;
}

/* ---- Left rail ---- */

.admin-rail {
    position: sticky;
    top: var(--space-5);
    background: var(--color-bg-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: var(--space-3);
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}

.admin-rail-header {
    padding: var(--space-2) var(--space-2) var(--space-3);
    border-bottom: 1px solid var(--color-border);
    margin-bottom: var(--space-2);
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.admin-rail-label {
    font-family: var(--font-mono);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-semibold);
    letter-spacing: 0.14em;
    color: var(--color-warning);
    text-transform: uppercase;
}

.admin-rail-sub {
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
}

/* Section group label — small, all-caps, muted. Sits above the
   links it groups. */
.admin-rail-group {
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-medium);
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    padding: var(--space-3) var(--space-2) var(--space-1);
}

.admin-rail-link {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: 8px 10px;
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-medium);
    color: var(--color-text);
    text-decoration: none;
    border-radius: var(--radius-sm);
    border-left: 2px solid transparent;
    transition:
        background var(--transition-fast),
        color var(--transition-fast),
        border-color var(--transition-fast);
}

.admin-rail-link:hover {
    background: var(--color-bg-subtle);
    color: var(--color-text);
}

.admin-rail-link.is-active {
    background: color-mix(in srgb, var(--color-accent) 12%, transparent);
    color: var(--color-text);
    border-left-color: var(--color-accent);
}

/* Disabled ("soon") stubs. Render as non-interactive <span> in markup;
   this class adds the visual cue + the "soon" tag. */
.admin-rail-link.is-disabled {
    opacity: 0.45;
    cursor: not-allowed;
}

.admin-rail-link.is-disabled::after {
    content: 'soon';
    margin-left: auto;
    font-size: 9px;
    font-weight: var(--font-weight-semibold);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--color-text-muted);
    padding: 1px 6px;
    background: var(--color-bg-subtle);
    border-radius: var(--radius-pill);
}

.admin-rail-link.is-disabled:hover {
    background: transparent;
}

/* ---- Content column ---- */

.admin-content {
    min-width: 0;
    /* min-width: 0 prevents grid items from refusing to shrink
       when their content (e.g. a wide table) is wider than the
       column — without it, the rail can get pushed off-screen. */
}

/* ---- Mobile ---- */

@media (max-width: 720px) {
    .admin-console {
        grid-template-columns: 1fr;
    }
    .admin-rail {
        position: static;
    }
}

/* === === === announcements (phase 12e an1) === === === */

/* Banner width is governed by content, not by the parent column.
   - fit-content shrinks the banner to its natural content width
   - max-width: 100% caps at parent (main.container, capped at
     Bootstrap's responsive container widths: 540/720/960/1140/1320px)
   - auto side margins center the banner horizontally on the page
   - text-align centers the three stacked rows inside the banner
   - bottom margin comes from the .mb-2 utility class in the partial
   so a short banner is short, a long banner grows until it would
   overflow the container then wraps internally. */
.announcement-banner {
    width: fit-content;
    max-width: 100%;
    margin-left: auto;
    margin-right: auto;
    text-align: center;
}

/* Close button is absolute-positioned by bootstrap's .alert-dismissible
   so it ignores the text-align: center — stays anchored top-right. */

/* === === === fe-an1 admin announcements redesign (phase 12e.1) === === === */

/* Pure UI redesign of /admin/announcements on top of the an1–an6 model.
   Backend unchanged. All rules scoped under .admin-announcements-page so
   nothing leaks into other admin pages.

   Themes via tokens (works in both light + pr6c dark). The "console"
   feel comes from Geist Mono headings + dense spacing + segmented
   controls, not from literal dark colors — in light mode this is a
   light console; in dark mode it's a dark console; both work.

   Subsections (in order below):
   - status bar + page header
   - composer chrome
   - severity segmented control (no emoji; level-halo on active)
   - generic console-style inputs
   - conditional target-param slot (disabled / number / user-picker)
   - user picker — chip-input + autocomplete dropdown
   - flag chips
   - composer footer + publish button
   - faceted filter row (4 selects + result counter + clear)
   - list rows — halos per level, dot, body, badges, ••• menu */

/* ---- status bar ---- */
.admin-announcements-page .ann-status-bar {
    display: flex;
    align-items: center;
    gap: var(--space-5);
    font-family: var(--font-mono);
    font-size: var(--font-size-sm);
    letter-spacing: 0.08em;
    padding: var(--space-2) var(--space-3);
    background: var(--color-bg-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    margin-bottom: var(--space-5);
}

.admin-announcements-page .ann-status-prompt {
    color: var(--color-accent);
    font-weight: var(--font-weight-medium);
}

.admin-announcements-page .ann-status-stat {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    color: var(--color-text-muted);
    text-transform: uppercase;
}

.admin-announcements-page .ann-status-num {
    color: var(--color-text);
    font-weight: var(--font-weight-medium);
    background: var(--color-bg-subtle);
    padding: 2px 8px;
    border-radius: var(--radius-sm);
}

.admin-announcements-page .ann-status-divider {
    width: 1px;
    height: 16px;
    background: var(--color-border);
}

.admin-announcements-page .ann-status-pulse {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--color-success);
    animation: ann-pulse-blink 2s infinite;
}

@keyframes ann-pulse-blink {
    0%, 100% {
        opacity: 1;
        box-shadow: 0 0 0 0 color-mix(in srgb, var(--color-success) 45%, transparent);
    }
    50% {
        opacity: 0.55;
        box-shadow: 0 0 0 4px color-mix(in srgb, var(--color-success) 0%, transparent);
    }
}

/* ---- page header ---- */
.admin-announcements-page .ann-page-eyebrow {
    font-family: var(--font-mono);
    font-size: var(--font-size-body);
    font-weight: var(--font-weight-medium);
    letter-spacing: 0.05em;
    color: var(--color-text-muted);
    text-transform: uppercase;
    margin: 0 0 4px;
}

.admin-announcements-page .ann-page-h2 {
    font-size: 28px;
    font-weight: var(--font-weight-medium);
    letter-spacing: -0.02em;
    margin: 0 0 var(--space-2);
}

.admin-announcements-page .ann-page-lede {
    color: var(--color-text-muted);
    max-width: 64ch;
    margin: 0 0 var(--space-6);
}

/* ---- composer card ---- */
.admin-announcements-page .ann-composer {
    background: var(--color-bg-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    margin-bottom: var(--space-6);
    overflow: hidden;
}

.admin-announcements-page .ann-composer-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--space-3) var(--space-4);
    border-bottom: 1px solid var(--color-border);
    background: var(--color-bg-subtle);
}

.admin-announcements-page .ann-composer-header-title {
    font-family: var(--font-mono);
    font-size: var(--font-size-sm);
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--color-text-muted);
}

.admin-announcements-page .ann-composer-header-title::before {
    content: "$ ";
    color: var(--color-accent);
}

.admin-announcements-page .ann-composer-header-meta {
    font-family: var(--font-mono);
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
    opacity: 0.7;
}

.admin-announcements-page .ann-composer-body {
    padding: var(--space-4);
}

/* ---- severity segmented (no emoji; level-halo on active) ---- */
.admin-announcements-page .ann-sev-row {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin-bottom: var(--space-4);
}

.admin-announcements-page .ann-field-label {
    font-family: var(--font-mono);
    font-size: var(--font-size-sm);
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--color-text-muted);
    margin-bottom: 5px;
    display: block;
}

.admin-announcements-page .ann-sev-row .ann-field-label {
    margin-bottom: 0;
    width: 80px;
}

.admin-announcements-page .ann-sev-segmented {
    display: inline-flex;
    background: var(--color-bg-subtle);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: 3px;
    gap: 2px;
}

.admin-announcements-page .ann-sev-btn {
    background: transparent;
    border: 0;
    padding: 6px 14px;
    border-radius: var(--radius-sm);
    color: var(--color-text-muted);
    font: inherit;
    font-size: var(--font-size-md);
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: var(--space-2);
    transition: background 100ms, color 100ms, box-shadow 150ms;
}

.admin-announcements-page .ann-sev-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
}

/* severity emoji glyph (📣 ⚠️ 🚨) on each segmented sev button. sits to the
   LEFT of the dot + label. softened on inactive buttons so the active
   one's glyph reads as the brightest of the three. */
.admin-announcements-page .ann-sev-glyph {
    font-size: 14px;
    line-height: 1;
    filter: grayscale(0.4);
    opacity: 0.7;
    flex-shrink: 0;
}

.admin-announcements-page .ann-sev-btn[aria-pressed="true"] .ann-sev-glyph {
    filter: none;
    opacity: 1;
}

/* dot color per level — the only color cue on inactive sev buttons */
.admin-announcements-page .ann-sev-btn[data-level="fyi"] .ann-sev-dot       { background: var(--color-info); }
.admin-announcements-page .ann-sev-btn[data-level="heads_up"] .ann-sev-dot  { background: var(--color-warning); }
.admin-announcements-page .ann-sev-btn[data-level="yikes"] .ann-sev-dot     { background: var(--color-danger); }

/* active state — level-colored halo (background tint + box-shadow glow) */
.admin-announcements-page .ann-sev-btn[aria-pressed="true"] {
    color: var(--color-text);
}

.admin-announcements-page .ann-sev-btn[aria-pressed="true"][data-level="fyi"] {
    background: color-mix(in srgb, var(--color-info) 14%, transparent);
    box-shadow:
        0 0 0 1px color-mix(in srgb, var(--color-info) 45%, transparent),
        0 0 12px color-mix(in srgb, var(--color-info) 30%, transparent);
}

.admin-announcements-page .ann-sev-btn[aria-pressed="true"][data-level="heads_up"] {
    background: color-mix(in srgb, var(--color-warning) 14%, transparent);
    box-shadow:
        0 0 0 1px color-mix(in srgb, var(--color-warning) 45%, transparent),
        0 0 12px color-mix(in srgb, var(--color-warning) 30%, transparent);
}

.admin-announcements-page .ann-sev-btn[aria-pressed="true"][data-level="yikes"] {
    background: color-mix(in srgb, var(--color-danger) 14%, transparent);
    box-shadow:
        0 0 0 1px color-mix(in srgb, var(--color-danger) 45%, transparent),
        0 0 12px color-mix(in srgb, var(--color-danger) 30%, transparent);
}

/* ---- generic console inputs ---- */
.admin-announcements-page .ann-text-input,
.admin-announcements-page .ann-text-area,
.admin-announcements-page .ann-text-select {
    width: 100%;
    background: var(--color-bg-page);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: 10px 12px;
    color: var(--color-text);
    font: inherit;
    font-size: var(--font-size-base);
    transition: border-color 100ms, box-shadow 100ms, opacity 150ms;
}

.admin-announcements-page .ann-text-area {
    font-family: var(--font-sans);
    resize: vertical;
    min-height: 90px;
    line-height: 1.55;
}

.admin-announcements-page .ann-text-input:focus,
.admin-announcements-page .ann-text-area:focus,
.admin-announcements-page .ann-text-select:focus {
    outline: none;
    border-color: var(--color-accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent) 22%, transparent);
}

.admin-announcements-page .ann-text-input::placeholder,
.admin-announcements-page .ann-text-area::placeholder {
    color: var(--color-text-muted);
    opacity: 0.7;
}

.admin-announcements-page .ann-text-input::placeholder {
    font-family: var(--font-mono);
}

.admin-announcements-page .ann-text-input:disabled,
.admin-announcements-page .ann-text-select:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

.admin-announcements-page .ann-field {
    margin-bottom: var(--space-3);
}

.admin-announcements-page .ann-field-help {
    font-family: var(--font-mono);
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
    opacity: 0.75;
    margin-top: 4px;
    min-height: 14px;
}

.admin-announcements-page .ann-field-grid-2 {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-3) var(--space-4);
}

/* ---- conditional target-param slot ---- */
.admin-announcements-page .ann-target-compound {
    display: grid;
    grid-template-columns: 1fr 220px;
    gap: var(--space-2);
}

.admin-announcements-page .ann-param-slot {
    position: relative;
}

.admin-announcements-page .ann-param-variant {
    display: none;
}

.admin-announcements-page .ann-param-variant.is-active {
    display: block;
}

/* ---- user picker (single_user variant) ---- */
.admin-announcements-page .ann-user-picker-wrap {
    position: relative;
}

.admin-announcements-page .ann-user-picker {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    background: var(--color-bg-page);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: 6px 10px;
    min-height: 38px;
    cursor: text;
    transition: border-color 100ms, box-shadow 100ms;
}

.admin-announcements-page .ann-user-picker:focus-within {
    border-color: var(--color-accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent) 22%, transparent);
}

.admin-announcements-page .ann-user-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: color-mix(in srgb, var(--color-accent) 18%, transparent);
    color: var(--color-accent);
    border-radius: var(--radius-sm);
    padding: 3px 4px 3px 8px;
    font-family: var(--font-mono);
    font-size: var(--font-size-sm);
}

.admin-announcements-page .ann-user-chip-remove {
    border: 0;
    background: transparent;
    color: inherit;
    cursor: pointer;
    padding: 0 4px;
    font-size: 14px;
    line-height: 1;
}

.admin-announcements-page .ann-user-chip-remove:hover {
    color: var(--color-text);
}

.admin-announcements-page .ann-user-picker-input {
    flex: 1;
    min-width: 80px;
    background: transparent;
    border: 0;
    padding: 4px 0;
    color: var(--color-text);
    font: inherit;
    font-size: var(--font-size-md);
    outline: none;
}

.admin-announcements-page .ann-user-picker-input::placeholder {
    color: var(--color-text-muted);
    font-family: var(--font-mono);
    opacity: 0.7;
}

/* autocomplete dropdown — positioned below the picker shell */
.admin-announcements-page .ann-user-picker-dropdown {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    background: var(--color-bg-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    max-height: 220px;
    overflow-y: auto;
    z-index: 50;
    box-shadow: var(--shadow-md);
}

.admin-announcements-page .ann-user-picker-dropdown[hidden] {
    display: none;
}

.admin-announcements-page .ann-user-suggest {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-2) var(--space-3);
    cursor: pointer;
    border-bottom: 1px solid var(--color-border);
}

.admin-announcements-page .ann-user-suggest:last-child {
    border-bottom: 0;
}

.admin-announcements-page .ann-user-suggest:hover,
.admin-announcements-page .ann-user-suggest.is-highlighted {
    background: var(--color-bg-subtle);
}

.admin-announcements-page .ann-user-suggest-name {
    color: var(--color-text);
    font-weight: var(--font-weight-medium);
    font-size: var(--font-size-md);
}

.admin-announcements-page .ann-user-suggest-handle {
    color: var(--color-text-muted);
    font-family: var(--font-mono);
    font-size: var(--font-size-xs);
}

/* ---- flag chips ---- */
.admin-announcements-page .ann-flag-row {
    display: flex;
    gap: var(--space-2);
    flex-wrap: wrap;
}

.admin-announcements-page .ann-flag-chip {
    border: 1px solid var(--color-border);
    background: var(--color-bg-page);
    border-radius: var(--radius-sm);
    padding: 5px 10px;
    cursor: pointer;
    font-family: var(--font-mono);
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.admin-announcements-page .ann-flag-chip::before {
    content: "";
    width: 6px;
    height: 6px;
    border-radius: 1px;
    background: var(--color-border);
}

.admin-announcements-page .ann-flag-chip[aria-pressed="true"] {
    color: var(--color-text);
    border-color: var(--color-text-muted);
}

.admin-announcements-page .ann-flag-chip[aria-pressed="true"]::before {
    background: var(--color-accent);
}

/* ---- composer footer + publish button ---- */
.admin-announcements-page .ann-composer-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--space-3) var(--space-4);
    border-top: 1px solid var(--color-border);
    background: var(--color-bg-subtle);
}

.admin-announcements-page .ann-composer-footer-hint {
    font-family: var(--font-mono);
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
    opacity: 0.7;
}

.admin-announcements-page .ann-btn-publish {
    background: var(--color-accent);
    color: var(--color-text-on-accent);
    border: 0;
    padding: 7px 16px;
    border-radius: var(--radius-sm);
    font-family: var(--font-mono);
    font-size: var(--font-size-md);
    font-weight: var(--font-weight-medium);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    cursor: pointer;
    transition: background 100ms;
}

.admin-announcements-page .ann-btn-publish:hover {
    background: var(--color-accent-hover);
}

/* ============================================================
   FACETED FILTER ROW
   four <select>s + result counter + clear-all. AND-combined.
   ============================================================ */
.admin-announcements-page .ann-facets {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    flex-wrap: wrap;
    background: var(--color-bg-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: var(--space-3) var(--space-4);
    margin-bottom: var(--space-3);
}

.admin-announcements-page .ann-facet-field {
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

.admin-announcements-page .ann-facet-label {
    font-family: var(--font-mono);
    font-size: var(--font-size-xs);
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--color-text-muted);
}

.admin-announcements-page .ann-facet-select {
    background: var(--color-bg-page);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: 4px 26px 4px 10px;
    color: var(--color-text);
    font-family: var(--font-mono);
    font-size: var(--font-size-sm);
    cursor: pointer;
}

.admin-announcements-page .ann-facet-select:focus {
    outline: none;
    border-color: var(--color-accent);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-accent) 22%, transparent);
}

.admin-announcements-page .ann-result-meta {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin-bottom: var(--space-3);
    padding: 0 2px;
}

.admin-announcements-page .ann-result-count {
    font-family: var(--font-mono);
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
}

.admin-announcements-page .ann-clear-filters {
    background: transparent;
    border: 0;
    color: var(--color-text-muted);
    opacity: 0.7;
    font-family: var(--font-mono);
    font-size: var(--font-size-sm);
    cursor: pointer;
    padding: 0;
    text-decoration: underline;
    text-decoration-style: dotted;
}

.admin-announcements-page .ann-clear-filters:hover {
    opacity: 1;
    color: var(--color-text);
}

/* ============================================================
   LIST ROWS — inbox-style with per-level halos
   the row is a single <a> click target → /admin/announcements/<id>/edit;
   actions (deactivate/delete) live inside the ••• menu.
   ============================================================ */
.admin-announcements-page .ann-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.admin-announcements-page .ann-row {
    display: grid;
    grid-template-columns: 14px 1fr auto;
    gap: var(--space-3);
    padding: 12px 16px;
    align-items: center;
    background: var(--color-bg-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    text-decoration: none;
    color: inherit;
    cursor: pointer;
    position: relative;
    transition: background 100ms, border-color 150ms, box-shadow 200ms, transform 150ms;
}

/* persistent subtle level-tint backdrop — gradient fades to surface */
.admin-announcements-page .ann-row[data-level="fyi"] {
    background:
        linear-gradient(90deg,
            color-mix(in srgb, var(--color-info) 5%, transparent),
            transparent 60%),
        var(--color-bg-surface);
}

.admin-announcements-page .ann-row[data-level="heads_up"] {
    background:
        linear-gradient(90deg,
            color-mix(in srgb, var(--color-warning) 5%, transparent),
            transparent 60%),
        var(--color-bg-surface);
}

.admin-announcements-page .ann-row[data-level="yikes"] {
    background:
        linear-gradient(90deg,
            color-mix(in srgb, var(--color-danger) 7%, transparent),
            transparent 60%),
        var(--color-bg-surface);
}

/* hover halo — level-colored border + glowing box-shadow */
.admin-announcements-page .ann-row:hover {
    transform: translateY(-1px);
}

.admin-announcements-page .ann-row[data-level="fyi"]:hover {
    border-color: var(--color-info);
    box-shadow:
        0 0 0 1px color-mix(in srgb, var(--color-info) 25%, transparent),
        0 8px 24px color-mix(in srgb, var(--color-info) 20%, transparent);
}

.admin-announcements-page .ann-row[data-level="heads_up"]:hover {
    border-color: var(--color-warning);
    box-shadow:
        0 0 0 1px color-mix(in srgb, var(--color-warning) 25%, transparent),
        0 8px 24px color-mix(in srgb, var(--color-warning) 20%, transparent);
}

.admin-announcements-page .ann-row[data-level="yikes"]:hover {
    border-color: var(--color-danger);
    box-shadow:
        0 0 0 1px color-mix(in srgb, var(--color-danger) 25%, transparent),
        0 8px 24px color-mix(in srgb, var(--color-danger) 20%, transparent);
}

.admin-announcements-page .ann-row:hover .ann-chevron {
    opacity: 1;
    transform: translateX(0);
}

/* pulsing dot at the row's left edge — color per level */
.admin-announcements-page .ann-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    justify-self: center;
}

.admin-announcements-page .ann-row[data-level="fyi"] .ann-dot {
    background: var(--color-info);
    animation: ann-glow-fyi 2.4s infinite;
}

.admin-announcements-page .ann-row[data-level="heads_up"] .ann-dot {
    background: var(--color-warning);
    animation: ann-glow-heads 2.4s infinite;
}

.admin-announcements-page .ann-row[data-level="yikes"] .ann-dot {
    background: var(--color-danger);
    animation: ann-glow-yikes 2.4s infinite;
}

@keyframes ann-glow-fyi {
    0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--color-info) 50%, transparent); }
    50%      { box-shadow: 0 0 0 4px color-mix(in srgb, var(--color-info) 0%, transparent); }
}
@keyframes ann-glow-heads {
    0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--color-warning) 50%, transparent); }
    50%      { box-shadow: 0 0 0 4px color-mix(in srgb, var(--color-warning) 0%, transparent); }
}
@keyframes ann-glow-yikes {
    0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--color-danger) 55%, transparent); }
    50%      { box-shadow: 0 0 0 4px color-mix(in srgb, var(--color-danger) 0%, transparent); }
}

/* inactive rows — muted, dot stops pulsing */
.admin-announcements-page .ann-row.is-inactive {
    opacity: 0.6;
}

.admin-announcements-page .ann-row.is-inactive .ann-dot {
    animation: none;
    opacity: 0.5;
}

/* row body: title row + preview */
.admin-announcements-page .ann-body {
    min-width: 0;
}

.admin-announcements-page .ann-top {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
    margin-bottom: 2px;
}

.admin-announcements-page .ann-id {
    font-family: var(--font-mono);
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
    opacity: 0.7;
}

/* severity emoji glyph (📣 ⚠️ 🚨) on each row. visual cue for the severity
   alongside the small left-edge dot — the dot alone reads too quiet at
   a glance across a list. */
.admin-announcements-page .ann-level-glyph {
    font-size: 14px;
    line-height: 1;
    flex-shrink: 0;
}

.admin-announcements-page .ann-title {
    font-weight: var(--font-weight-medium);
    color: var(--color-text);
    font-size: var(--font-size-base);
}

.admin-announcements-page .ann-flag-badge {
    font-family: var(--font-mono);
    font-size: 9px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    background: var(--color-bg-subtle);
    color: var(--color-text-muted);
    border: 1px solid var(--color-border);
    padding: 1px 6px;
    border-radius: 3px;
}

.admin-announcements-page .ann-flag-badge[data-kind="system"] {
    background: color-mix(in srgb, var(--color-accent) 18%, transparent);
    color: var(--color-accent);
    border-color: color-mix(in srgb, var(--color-accent) 45%, transparent);
}

.admin-announcements-page .ann-flag-badge[data-kind="persistent"] {
    border-color: var(--color-text-muted);
}

.admin-announcements-page .ann-preview {
    color: var(--color-text-muted);
    font-size: var(--font-size-md);
    margin: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    line-height: 1.4;
}

/* trailing column: meta + ••• menu + chevron */
.admin-announcements-page .ann-trailing {
    display: flex;
    align-items: center;
    gap: var(--space-3);
}

.admin-announcements-page .ann-meta {
    font-family: var(--font-mono);
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
    opacity: 0.75;
    text-align: right;
    line-height: 1.3;
}

/* each meta line is its own div; keep each one on a single line so the
   target label / timestamp don't break each word onto its own row when
   the column gets tight. flex-wrap on the trailing column already lets
   the cell grow to fit. */
.admin-announcements-page .ann-meta > div {
    white-space: nowrap;
}

.admin-announcements-page .ann-meta .ann-target {
    color: var(--color-text);
    opacity: 0.9;
    margin-right: 8px;
}

.admin-announcements-page .ann-chevron {
    width: 14px;
    height: 14px;
    color: var(--color-text-muted);
    opacity: 0;
    transform: translateX(-4px);
    transition: opacity 100ms, transform 100ms;
    flex-shrink: 0;
}

/* ---- ••• row-actions menu ---- */
.admin-announcements-page .ann-menu-wrap {
    position: relative;
}

.admin-announcements-page .ann-menu-btn {
    border: 0;
    background: transparent;
    width: 24px;
    height: 24px;
    color: var(--color-text-muted);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: var(--font-mono);
    font-size: 14px;
    border-radius: var(--radius-sm);
}

.admin-announcements-page .ann-menu-btn:hover {
    background: var(--color-bg-subtle);
    color: var(--color-text);
}

.admin-announcements-page .ann-menu {
    position: absolute;
    top: calc(100% + 4px);
    right: 0;
    background: var(--color-bg-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    min-width: 160px;
    z-index: 30;
    box-shadow: var(--shadow-md);
    padding: 4px;
    display: none;
}

.admin-announcements-page .ann-menu.is-open {
    display: block;
}

.admin-announcements-page .ann-menu-item {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: 6px 10px;
    border-radius: var(--radius-sm);
    color: var(--color-text);
    text-decoration: none;
    font-size: var(--font-size-md);
    background: transparent;
    border: 0;
    width: 100%;
    text-align: left;
    cursor: pointer;
    font: inherit;
}

.admin-announcements-page .ann-menu-item:hover {
    background: var(--color-bg-subtle);
}

.admin-announcements-page .ann-menu-item.is-danger {
    color: var(--color-danger);
}

.admin-announcements-page .ann-menu-divider {
    height: 1px;
    background: var(--color-border);
    margin: 4px 0;
}

/* embedded <form>s inside the ••• menu are CSRF envelopes only —
   the visible menu-items carry the styling, the forms shouldn't add
   their own margins / padding */
.admin-announcements-page .ann-menu form {
    margin: 0;
}

/* ============================================================
   System status page (pwa5-followup-system-status)
   ============================================================
   Styles for /admin/system. Stacked service rows with status pills.
   Pill colors map to status semantics:
     ok    → success green
     unset → warning yellow (feature off, not broken)
     down  → danger red (service reachable in principle, actually failing)

   All values use design tokens so dark-mode (pr6c) inherits correctly.
   ============================================================ */

.admin-system-rows {
    /* List sits inside a .card; remove the default list-group border-radius
       collision with the card wrapper. Bootstrap's list-group-flush handles
       this already, but the explicit reset documents the intent. */
    border-radius: 0;
}

.admin-system-row {
    /* Flex container so the status pill vertically centers against the
       full content column (label + metrics + actions stacked). Replaces
       the pre-status nested .admin-system-row-main wrapper, which was
       only centering the pill against the label height. */
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
}

.admin-system-row-content {
    /* Vertical stack of label + metrics/helper + action buttons.
       flex:1 fills available width so the pill sits flush right.
       min-width:0 lets long inner content (e.g. SMTP sender address)
       wrap rather than overflow the flex container. */
    flex: 1;
    min-width: 0;
}

.admin-system-row-label {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.admin-system-row-name {
    font-weight: var(--font-weight-medium);
}

.admin-system-row-checktype {
    /* live vs config check — small caveat under the service name */
    font-size: var(--font-size-sm);
}

.admin-system-row-helper {
    /* Helper line only renders when status != ok. Inset to align under
       the service label, leaving the pill column clear. */
    margin-top: var(--space-2);
    font-size: var(--font-size-md);
}

.admin-system-pill {
    /* Pill shape — the row's primary status glyph. Bumped from the
       prior "small tag" sizing to base font + larger padding so the
       status reads at a glance from across the room (or from a
       Railway dashboard tab pinned next to FriendZone). flex-shrink:0
       holds the pill's size when row content is wide. */
    display: inline-block;
    padding: var(--space-2) var(--space-4);
    border-radius: var(--radius-pill);
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-semibold);
    text-transform: lowercase;
    letter-spacing: 0.02em;
    white-space: nowrap;
    flex-shrink: 0;
}

.admin-system-pill--ok {
    background-color: color-mix(in srgb, var(--color-success) 15%, transparent);
    color: var(--color-success);
}

.admin-system-pill--unset {
    background-color: color-mix(in srgb, var(--color-warning) 18%, transparent);
    /* Warning yellow is too light on its own background; darken the text
       a notch for legibility. The mix below is darken-by-30%-toward-black. */
    color: color-mix(in srgb, var(--color-warning) 70%, #000);
}

.admin-system-pill--down {
    background-color: color-mix(in srgb, var(--color-danger) 15%, transparent);
    color: var(--color-danger);
}

/* ----- Hover-activated halo ----- */

/* The template applies .has-halo to each row, but the persistent halo
   from the global rule (~line 398) reads too loud for a quiet status
   page. Suppress it at rest and repaint on hover / focus-within so the
   page reads calm by default and lights up only the row the admin's
   cursor is on. :focus-within is defensive — there's no focusable
   child today, but the test-action buttons added below will be. */
.admin-system-row.has-halo {
    box-shadow: none;
    transition: box-shadow var(--transition-medium);
}

.admin-system-row.has-halo:hover,
.admin-system-row.has-halo:focus-within {
    box-shadow:
        0 0 0 1px rgba(var(--halo-color), 0.5),
        0 0 24px 4px rgba(var(--halo-color), 0.25),
        0 0 60px 8px rgba(var(--halo-color), 0.12);
}

/* ----- Halo color follows status ----- */

/* Each row's --halo-color overrides the .has-halo default (primary
   accent violet). The hover rule above reads --halo-color at the
   row level, so green/yellow/red flows automatically. Specificity is
   doubled via the chained class selector (.admin-system-row.admin-system-row--ok
   = 0,2,0) so this wins regardless of source order against the
   single-class .has-halo declaration (0,1,0). */
.admin-system-row.admin-system-row--ok    { --halo-color: var(--bs-success-rgb); }
.admin-system-row.admin-system-row--unset { --halo-color: var(--bs-warning-rgb); }
.admin-system-row.admin-system-row--down  { --halo-color: var(--bs-danger-rgb); }

/* ----- Action buttons row ----- */

/* Small action buttons sit below the metrics line. The flex layout
   lets future rows stack 2+ buttons horizontally on the same line
   (e.g. b2 row with both "verify" and a hypothetical "head bucket"
   action). Gap matches the row's internal spacing rhythm. */
.admin-system-row-actions {
    margin-top: var(--space-3);
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
}

/* === === === /updates archive (phase 14c up1) === === === */

/* Read-only durable archive of persistent announcements at /updates.
   Visual vocabulary mirrors fe-an1's admin announcements page (status
   bar + halo'd rows + level glyph + chevron) but strips the admin chrome
   (no compose, no ••• menu, no facets, no badges). Tokens throughout so
   light + pr6c dark inherit automatically. All rules scoped under
   .updates-page so nothing leaks. */

/* ---- status bar ---- */
.updates-page .updates-status-bar {
    display: flex;
    align-items: center;
    gap: var(--space-5);
    font-family: var(--font-mono);
    font-size: var(--font-size-sm);
    letter-spacing: 0.08em;
    padding: var(--space-2) var(--space-3);
    background: var(--color-bg-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    margin-bottom: var(--space-5);
}

.updates-page .updates-status-prompt {
    color: var(--color-accent);
    font-weight: var(--font-weight-medium);
}

.updates-page .updates-status-divider {
    width: 1px;
    height: 16px;
    background: var(--color-border);
}

.updates-page .updates-status-stat {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    color: var(--color-text-muted);
    text-transform: uppercase;
}

.updates-page .updates-status-num {
    color: var(--color-text);
    font-weight: var(--font-weight-medium);
    background: var(--color-bg-subtle);
    padding: 2px 8px;
    border-radius: var(--radius-sm);
}

/* ---- page header ---- */
.updates-page .updates-page-eyebrow {
    font-family: var(--font-mono);
    font-size: var(--font-size-body);
    font-weight: var(--font-weight-medium);
    letter-spacing: 0.05em;
    color: var(--color-text-muted);
    margin-bottom: var(--space-1);
}

.updates-page .updates-page-h2 {
    font-size: 1.5rem;
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
    margin-bottom: var(--space-5);
}

/* ---- list + row chrome ---- */
.updates-page .updates-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.updates-page .update-row {
    display: grid;
    grid-template-columns: 14px 1fr auto;
    gap: var(--space-3);
    padding: 12px 16px;
    align-items: start;
    background: var(--color-bg-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    color: inherit;
    cursor: pointer;
    position: relative;
    transition: background 100ms, border-color 150ms, box-shadow 200ms, transform 150ms;
}

/* persistent subtle level-tint backdrop — gradient fades to surface */
.updates-page .update-row[data-level="fyi"] {
    background:
        linear-gradient(90deg,
            color-mix(in srgb, var(--color-info) 5%, transparent),
            transparent 60%),
        var(--color-bg-surface);
}
.updates-page .update-row[data-level="heads_up"] {
    background:
        linear-gradient(90deg,
            color-mix(in srgb, var(--color-warning) 5%, transparent),
            transparent 60%),
        var(--color-bg-surface);
}
.updates-page .update-row[data-level="yikes"] {
    background:
        linear-gradient(90deg,
            color-mix(in srgb, var(--color-danger) 7%, transparent),
            transparent 60%),
        var(--color-bg-surface);
}

/* hover + focus halo — level-colored border + glowing box-shadow.
   :focus-visible matches hover for keyboard parity (role="button"
   on the row makes this load-bearing for a11y). */
.updates-page .update-row:hover,
.updates-page .update-row:focus-visible {
    transform: translateY(-1px);
    outline: none;
}
.updates-page .update-row[data-level="fyi"]:hover,
.updates-page .update-row[data-level="fyi"]:focus-visible {
    border-color: var(--color-info);
    box-shadow:
        0 0 0 1px color-mix(in srgb, var(--color-info) 25%, transparent),
        0 8px 24px color-mix(in srgb, var(--color-info) 20%, transparent);
}
.updates-page .update-row[data-level="heads_up"]:hover,
.updates-page .update-row[data-level="heads_up"]:focus-visible {
    border-color: var(--color-warning);
    box-shadow:
        0 0 0 1px color-mix(in srgb, var(--color-warning) 25%, transparent),
        0 8px 24px color-mix(in srgb, var(--color-warning) 20%, transparent);
}
.updates-page .update-row[data-level="yikes"]:hover,
.updates-page .update-row[data-level="yikes"]:focus-visible {
    border-color: var(--color-danger);
    box-shadow:
        0 0 0 1px color-mix(in srgb, var(--color-danger) 25%, transparent),
        0 8px 24px color-mix(in srgb, var(--color-danger) 20%, transparent);
}

/* ---- dot per level (static — archive isn't "live"; no pulse) ---- */
.updates-page .update-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    justify-self: center;
    margin-top: 6px;
}
.updates-page .update-row[data-level="fyi"] .update-dot     { background: var(--color-info); }
.updates-page .update-row[data-level="heads_up"] .update-dot { background: var(--color-warning); }
.updates-page .update-row[data-level="yikes"] .update-dot   { background: var(--color-danger); }

/* ---- row body ---- */
.updates-page .update-body {
    min-width: 0;
}

.updates-page .update-top {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
    margin-bottom: var(--space-1);
}

.updates-page .update-level-glyph {
    font-size: 14px;
    line-height: 1;
    flex-shrink: 0;
}

.updates-page .update-level-label {
    font-family: var(--font-mono);
    font-size: var(--font-size-xs);
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-text-muted);
}

.updates-page .update-title {
    font-weight: var(--font-weight-medium);
    color: var(--color-text);
    font-size: var(--font-size-base);
}

.updates-page .update-edited {
    font-family: var(--font-mono);
    font-size: 9px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--color-text-muted);
    opacity: 0.7;
}

/* ---- preview vs full body — CSS toggles which is visible ---- */
/* preview: 2-line clamp using -webkit-line-clamp (standard browser support
   in 2026; the -webkit- prefix is the recognized spelling even outside
   webkit). */
.updates-page .update-preview {
    color: var(--color-text-muted);
    font-size: var(--font-size-md);
    margin: 0;
    line-height: 1.4;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* full body — hidden by default; shown when row is .is-expanded. */
.updates-page .update-full {
    display: none;
    color: var(--color-text);
    font-size: var(--font-size-base);
    line-height: 1.5;
    margin: 0;
}

.updates-page .update-row.is-expanded .update-preview {
    display: none;
}
.updates-page .update-row.is-expanded .update-full {
    display: block;
}

/* @mention links inside the body keep their existing styling — no
   overrides here. Clicks on them bypass the row toggle (handled in JS). */

/* ---- trailing column: meta + chevron ---- */
.updates-page .update-trailing {
    display: flex;
    align-items: center;
    gap: var(--space-3);
}

.updates-page .update-meta {
    font-family: var(--font-mono);
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
    opacity: 0.75;
    text-align: right;
    line-height: 1.3;
}

.updates-page .update-meta > div {
    white-space: nowrap;
}

/* chevron — quiet by default, lights up on hover/focus, rotates when
   the row is expanded. */
.updates-page .update-chevron {
    width: 14px;
    height: 14px;
    color: var(--color-text-muted);
    opacity: 0.4;
    transition: opacity 100ms, transform 150ms;
    flex-shrink: 0;
}
.updates-page .update-row:hover .update-chevron,
.updates-page .update-row:focus-visible .update-chevron {
    opacity: 1;
}
.updates-page .update-row.is-expanded .update-chevron {
    transform: rotate(90deg);
    opacity: 1;
}

/* ---- empty state ---- */
.updates-page .updates-empty {
    text-align: center;
    padding: var(--space-6) var(--space-4);
    color: var(--color-text-muted);
    font-size: var(--font-size-body);
}

/* === === === /updates archive: facet form + result meta (phase 14c up2) === === === */

/* up2 adds search + level filter to the /updates page. All new rules
   scoped under .updates-page so nothing leaks. Tokens throughout —
   light + pr6c dark inherit automatically. The form sits between the
   page-h2 and .updates-list; the result-meta line sits between the
   form and .updates-list when filters are active. */

.updates-page .updates-facets {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-bottom: var(--space-3);
    align-items: center;
}

/* search input grows to fill available row width; min-width: 0 lets
   it shrink past its content width inside the flex container so the
   row collapses cleanly on narrow viewports. */
.updates-page .updates-search-input {
    flex: 1 1 220px;
    min-width: 0;
    padding: var(--space-2) var(--space-3);
    background: var(--color-bg-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    color: var(--color-text);
    font-family: var(--font-sans);
    font-size: var(--font-size-base);
}

.updates-page .updates-level-select {
    flex: 0 0 auto;
    padding: var(--space-2) var(--space-3);
    background: var(--color-bg-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    color: var(--color-text);
    font-family: var(--font-sans);
    font-size: var(--font-size-base);
    cursor: pointer;
}

/* focus halo matches the accent glow used elsewhere (post compose
   form, account settings inputs) — visual consistency across forms. */
.updates-page .updates-search-input:focus,
.updates-page .updates-level-select:focus {
    outline: none;
    border-color: var(--color-accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent) 22%, transparent);
}

.updates-page .updates-search-submit {
    flex: 0 0 auto;
    padding: var(--space-2) var(--space-4);
    background: var(--color-accent);
    border: 1px solid var(--color-accent);
    border-radius: var(--radius-md);
    color: var(--color-text-on-accent);
    font-family: var(--font-sans);
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-medium);
    cursor: pointer;
    transition: background 120ms, border-color 120ms;
}

.updates-page .updates-search-submit:hover {
    background: var(--color-accent-hover);
    border-color: var(--color-accent-hover);
}

.updates-page .updates-search-submit:focus-visible {
    outline: none;
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent) 35%, transparent);
}

/* clear link — quiet by default, brightens on hover. plain underlined
   text reads more honestly than a styled button since it's GET-nav. */
.updates-page .updates-clear-filters {
    color: var(--color-text-muted);
    font-size: var(--font-size-md);
    text-decoration: underline;
    cursor: pointer;
    margin-left: var(--space-2);
}

.updates-page .updates-clear-filters:hover {
    color: var(--color-text);
}

/* result meta — mono caps to match the status bar's typographic
   register. small + muted so it sits as supporting context rather
   than competing with the list rows below. */
.updates-page .updates-result-meta {
    font-family: var(--font-mono);
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
    margin-bottom: var(--space-2);
    text-transform: uppercase;
    letter-spacing: 0.08em;
}

/* === === === announcement banner: title-link + see-all footer (phase 14c up3) === === === */

/* up3 adds two new affordances to persistent announcement banners:
   a clickable title (links to /updates/<id>) and a footer "see all →"
   link (to /updates). Both inherit the alert's color so they work
   against the level-tinted backgrounds (info/warning/danger) without
   per-level overrides. */

/* title link — keep the title's visual weight; only the hover state
   communicates clickability. underline-on-hover, not always-on, to
   avoid competing with the body text. */
.announcement-banner .announcement-title-link {
    color: inherit;
    text-decoration: none;
    cursor: pointer;
    transition: text-decoration-color 120ms;
}

.announcement-banner .announcement-title-link:hover,
.announcement-banner .announcement-title-link:focus-visible {
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-underline-offset: 3px;
}

.announcement-banner .announcement-title-link:focus-visible {
    outline: 2px solid currentColor;
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}

/* footer — small top margin so it doesn't crowd the body, right-
   aligned so it reads as supporting nav rather than primary content.
   sits inside the alert; bootstrap's absolute-positioned dismiss
   button overlays cleanly without conflict. */
.announcement-banner .announcement-footer {
    margin-top: var(--space-2);
    text-align: right;
}

/* "see all →" link — small, semi-transparent on top of the alert's
   tinted background. brighter on hover to communicate state without
   shouting. underline always-on since it's clearly supporting text
   rather than competing with the body. */
.announcement-banner .announcement-see-all {
    color: inherit;
    font-size: var(--font-size-sm);
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-underline-offset: 3px;
    opacity: 0.7;
    transition: opacity 120ms;
}

.announcement-banner .announcement-see-all:hover,
.announcement-banner .announcement-see-all:focus-visible {
    opacity: 1;
}

.announcement-banner .announcement-see-all:focus-visible {
    outline: 2px solid currentColor;
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}

/* === === === updates dashboard column body (phase 14c up4) === === === */

/* The Updates column shows recent persistent announcements as compact
   2-line halo'd rows that link to /updates/<id>. Visual treatment
   mirrors the /updates archive (up1, line 8908) — persistent subtle
   level-tint gradient background per row, level-colored border + glow
   on hover/focus, translateY lift — so the two surfaces read as one
   family. Internal layout (top row + body preview) is column-specific
   because the archive's wider grid (dot + body + chevron) doesn't fit
   a narrow column.

   Selector specificity bumped to .dashboard-grid .dashboard-column-body
   a.dashboard-update-row matches the messages-row pattern at line 5548
   and the links-column-tile pattern at line 5927 — needed to beat
   Bootstrap reboot's a:visited rule (0,0,1,1) that would otherwise
   repaint the row purple-and-underlined after a click. :visited /
   :hover / :focus-visible spelled out in the base rule for the same
   reason. */

.dashboard-updates-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

/* row baseline — all link states share the same layout + chrome.
   Per-level background lives in the [data-level] rules below. */
.dashboard-grid .dashboard-column-body a.dashboard-update-row,
.dashboard-grid .dashboard-column-body a.dashboard-update-row:visited,
.dashboard-grid .dashboard-column-body a.dashboard-update-row:hover,
.dashboard-grid .dashboard-column-body a.dashboard-update-row:focus-visible {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    padding: var(--space-2) var(--space-3);
    background: var(--color-bg-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    color: var(--color-text);
    text-decoration: none;
    cursor: pointer;
    position: relative;
    outline: none;
    transition: background 100ms, border-color 150ms, box-shadow 200ms, transform 150ms;
}

/* persistent subtle level-tint backdrop — gradient fades to surface.
   matches the archive's row backdrop at line 8995. percentages match
   the archive's: 5% for fyi/heads_up, 7% for yikes. */
.dashboard-grid .dashboard-column-body a.dashboard-update-row[data-level="fyi"] {
    background:
        linear-gradient(90deg,
            color-mix(in srgb, var(--color-info) 5%, transparent),
            transparent 60%),
        var(--color-bg-surface);
}
.dashboard-grid .dashboard-column-body a.dashboard-update-row[data-level="heads_up"] {
    background:
        linear-gradient(90deg,
            color-mix(in srgb, var(--color-warning) 5%, transparent),
            transparent 60%),
        var(--color-bg-surface);
}
.dashboard-grid .dashboard-column-body a.dashboard-update-row[data-level="yikes"] {
    background:
        linear-gradient(90deg,
            color-mix(in srgb, var(--color-danger) 7%, transparent),
            transparent 60%),
        var(--color-bg-surface);
}

/* hover + focus — lift + level-colored border + glowing box-shadow.
   matches the archive's row halo at line 9020. :focus-visible matches
   hover for keyboard parity. */
.dashboard-grid .dashboard-column-body a.dashboard-update-row:hover,
.dashboard-grid .dashboard-column-body a.dashboard-update-row:focus-visible {
    transform: translateY(-1px);
}
.dashboard-grid .dashboard-column-body a.dashboard-update-row[data-level="fyi"]:hover,
.dashboard-grid .dashboard-column-body a.dashboard-update-row[data-level="fyi"]:focus-visible {
    border-color: var(--color-info);
    box-shadow:
        0 0 0 1px color-mix(in srgb, var(--color-info) 25%, transparent),
        0 8px 24px color-mix(in srgb, var(--color-info) 20%, transparent);
}
.dashboard-grid .dashboard-column-body a.dashboard-update-row[data-level="heads_up"]:hover,
.dashboard-grid .dashboard-column-body a.dashboard-update-row[data-level="heads_up"]:focus-visible {
    border-color: var(--color-warning);
    box-shadow:
        0 0 0 1px color-mix(in srgb, var(--color-warning) 25%, transparent),
        0 8px 24px color-mix(in srgb, var(--color-warning) 20%, transparent);
}
.dashboard-grid .dashboard-column-body a.dashboard-update-row[data-level="yikes"]:hover,
.dashboard-grid .dashboard-column-body a.dashboard-update-row[data-level="yikes"]:focus-visible {
    border-color: var(--color-danger);
    box-shadow:
        0 0 0 1px color-mix(in srgb, var(--color-danger) 25%, transparent),
        0 8px 24px color-mix(in srgb, var(--color-danger) 20%, transparent);
}

/* top row — level glyph + title + timestamp. flex layout pins the
   timestamp to the right while title takes the slack. */
.dashboard-update-row-top {
    display: flex;
    align-items: baseline;
    gap: var(--space-2);
    min-width: 0;
}

.dashboard-update-row-glyph {
    flex: 0 0 auto;
    font-size: var(--font-size-base);
    line-height: 1;
}

/* title — semibold + ellipsis-on-overflow so long titles don't break
   the row layout. min-width: 0 is required for ellipsis in a flex
   child (otherwise it refuses to shrink past content width). */
.dashboard-update-row-title {
    flex: 1 1 auto;
    min-width: 0;
    font-weight: var(--font-weight-semibold);
    font-size: var(--font-size-base);
    color: var(--color-text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.dashboard-update-row-time {
    flex: 0 0 auto;
    font-family: var(--font-mono);
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
    white-space: nowrap;
}

/* body preview — muted, smaller, one-line ellipsis. only rendered
   when the announcement has a title; otherwise the title row IS the
   body preview (top row's title slot takes truncated body content). */
.dashboard-update-row-body {
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    padding-left: calc(var(--font-size-base) + var(--space-2));
}

/* footer — right-aligned "see all →" link. Shared chrome for any
   dashboard column with a "more lives elsewhere" overflow (Updates,
   Events). Selectors are .dashboard-column-* rather than -updates-*
   to reflect cross-column use (ev7-column-polish). Mirrors the
   announcement banner's see-all treatment from up3 for visual
   consistency. */
.dashboard-column-footer {
    margin-top: var(--space-3);
    padding-top: var(--space-2);
    border-top: 1px solid var(--color-border);
    text-align: right;
}

.dashboard-column-see-all {
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-underline-offset: 3px;
    opacity: 0.7;
    transition: opacity 120ms, color 120ms;
}

.dashboard-column-see-all:hover,
.dashboard-column-see-all:focus-visible {
    opacity: 1;
    color: var(--color-text);
}

.dashboard-column-see-all:focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}

/* === announcement markdown bodies (an6) ===
   Announcement bodies now render through render_safe_markdown, which
   wraps content in <p> and may emit <ul>/<ol>/<li>, <strong>, <em>,
   <s>, <code>, <a>. Three render contexts each need slightly different
   handling:

     1. .announcement-body  — banner. Block markdown OK. Just tighten
        first/last margins so the banner stays compact.
     2. .update-preview     — archive list preview, 2-line CSS clamp.
        Markdown blocks would each become their own clamp row; force
        inner content inline so the clamp still reflects "first 2
        lines of text". Bullets/numbering get visually flattened here —
        the expanded view (.update-full) shows them properly.
     3. .update-full / .ann-preview — expanded archive row and admin
        list. Block markdown OK; tighten spacing.

   .ann-preview (admin list) keeps white-space:nowrap + ellipsis for
   one-line truncation, so inner blocks get the same inline-flatten
   treatment as .update-preview. */

.announcement-body > p:first-child,
.update-full > p:first-child,
.announcement-body > ul:first-child,
.update-full > ul:first-child,
.announcement-body > ol:first-child,
.update-full > ol:first-child {
    margin-top: 0;
}

.announcement-body > p:last-child,
.update-full > p:last-child,
.announcement-body > ul:last-child,
.update-full > ul:last-child,
.announcement-body > ol:last-child,
.update-full > ol:last-child {
    margin-bottom: 0;
}

.announcement-body p,
.update-full p {
    margin: 0 0 var(--space-2) 0;
}

.announcement-body ul,
.announcement-body ol,
.update-full ul,
.update-full ol {
    margin: var(--space-1) 0;
    padding-left: var(--space-5);
}

.announcement-body code,
.update-preview code,
.update-full code,
.ann-preview code {
    font-family: var(--font-mono);
    font-size: 0.9em;
    background: var(--color-surface-subtle, rgba(0,0,0,0.05));
    padding: 0 var(--space-1);
    border-radius: var(--radius-sm);
}

/* Preview contexts: force markdown blocks to render inline so the
   line-clamp / nowrap truncation continues to work. List markers are
   hidden in preview — the expanded view is the source of truth for
   list structure. */
.update-preview > *,
.ann-preview > * {
    display: inline;
    margin: 0;
    padding: 0;
}

.update-preview ul,
.update-preview ol,
.ann-preview ul,
.ann-preview ol {
    list-style: none;
}

.update-preview li,
.ann-preview li {
    display: inline;
}

.update-preview li + li::before,
.ann-preview li + li::before {
    content: " · ";
    color: var(--color-text-muted);
}

/* === announcement composer cheat-sheet (an6) ===
   Markdown syntax hints live inline in the existing .ann-field-help
   row under the body field rather than as a separate chip strip. The
   <code> spans inside the help line get monospace for visual
   distinction but inherit the muted parent color and drop any
   background/padding — keeps the help row reading as one quiet line
   instead of competing for attention.

   Bootstrap's default <code> styling is a vibrant pink/magenta
   (#d63384-ish); the color:inherit + background:transparent overrides
   below neutralize that for this specific context. */
.ann-field-help code {
    font-family: var(--font-mono);
    color: inherit;
    background: transparent;
    padding: 0;
    font-size: 0.95em;
}

/* ============================================================
   Announcement banner — terminal-transmission restyle
   (launch1-followup-announcements)
   ============================================================
   Restyles `_announcement_banner.html` to match the launch1
   tutorial popover aesthetic: black bg, phosphor accent, VT323
   title bar, monospace body, keystroke chips for dismiss.

   ANSI tri-color severity scale (mapped from Bootstrap classes
   the model already emits via level_alert_class):
     fyi      → alert-info    → matrix green   #22ff5e
     heads_up → alert-warning → amber          #ffb800
     yikes    → alert-danger  → red phosphor   #ff3b3b

   Visual-only — the model, dismissal persistence, targeting
   dispatcher, markdown body rendering, and the existing
   announcement_dismiss.js click handler are all unchanged.
   Both dismiss chips ([ESC] close + [X]) keep the
   .announcement-dismiss class so either click dismisses.

   Overrides earlier .announcement-banner rules higher up in
   this file via !important + source-order. Bootstrap's
   .alert-info/warning/danger backgrounds + colors are
   neutralized by the !important overrides below; other .alert
   elements on the page are unaffected because the cascade
   selectors require both .announcement-banner AND the alert-*
   class.
   ============================================================ */

/* base shell — same visual world as the tutorial popover */
.announcement-banner {
    width: fit-content !important;
    max-width: 100% !important;
    margin-left: auto;
    margin-right: auto;
    text-align: left !important;
    background: #0a0a0a !important;
    color: #f5f1e8 !important;
    border: 1px solid #22ff5e !important;
    border-radius: 3px !important;
    font-family: var(--font-mono);
    padding: 0 !important;
    overflow: hidden;
    box-shadow:
        0 0 0 3px var(--color-bg-page),
        0 0 24px rgba(34, 255, 94, 0.25);
}

/* level-aware accent vars */
.announcement-banner.alert-info {
    --ann-accent: #22ff5e;
    --ann-accent-rgb: 34, 255, 94;
}
.announcement-banner.alert-warning {
    --ann-accent: #ffb800;
    --ann-accent-rgb: 255, 184, 0;
}
.announcement-banner.alert-danger {
    --ann-accent: #ff3b3b;
    --ann-accent-rgb: 255, 59, 59;
}

.announcement-banner.alert-info,
.announcement-banner.alert-warning,
.announcement-banner.alert-danger {
    border-color: var(--ann-accent) !important;
    box-shadow:
        0 0 0 3px var(--color-bg-page),
        0 0 24px rgba(var(--ann-accent-rgb), 0.3);
}

/* terminal title bar */
.announcement-banner .announcement-bar {
    background: #161616;
    color: #93d5a1;
    padding: 6px 12px;
    font-size: 11px;
    letter-spacing: 0.08em;
    display: flex;
    justify-content: space-between;
    align-items: center;
    border-bottom: 1px solid var(--ann-accent, #22ff5e);
}

.announcement-banner .announcement-bar-path {
    text-transform: lowercase;
    color: var(--ann-accent, #22ff5e);
}

.announcement-banner .announcement-bar-dots {
    display: flex;
    gap: 6px;
}
.announcement-banner .announcement-bar-dots span {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--ann-accent, #22ff5e);
    opacity: 0.4;
}
.announcement-banner .announcement-bar-dots span:first-child {
    opacity: 1;
}

/* body wrap */
.announcement-banner .announcement-body-wrap {
    padding: 14px 14px 12px;
    font-size: 13px;
    line-height: 1.5;
}

/* prompt line — `$ <title>` */
.announcement-banner .announcement-prompt {
    color: #93d5a1;
    font-size: 13px;
    margin-bottom: 10px;
    font-family: var(--font-mono);
}
.announcement-banner .announcement-dollar {
    color: var(--ann-accent, #22ff5e);
    margin-right: 2px;
}

/* title link inside the prompt */
.announcement-banner .announcement-title-link {
    color: #93d5a1 !important;
    text-decoration: none;
    cursor: pointer;
    transition: color 120ms ease, text-decoration-color 120ms;
}
.announcement-banner .announcement-title-link:hover,
.announcement-banner .announcement-title-link:focus-visible {
    color: var(--ann-accent, #22ff5e) !important;
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-underline-offset: 3px;
    outline: none;
}
.announcement-banner .announcement-title-link:focus-visible {
    outline: 1px solid var(--ann-accent, #22ff5e);
    outline-offset: 2px;
    border-radius: 2px;
}

/* body — markdown content. links + bold get phosphor treatment */
.announcement-banner .announcement-body {
    color: #f5f1e8;
    margin: 0 0 4px;
}
.announcement-banner .announcement-body a {
    color: var(--ann-accent, #22ff5e);
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-underline-offset: 2px;
    transition: color 120ms ease;
}
.announcement-banner .announcement-body a:hover,
.announcement-banner .announcement-body a:focus-visible {
    color: #5fff8a;
    outline: none;
}
.announcement-banner .announcement-body strong {
    color: var(--ann-accent, #22ff5e);
    font-weight: 500;
}
.announcement-banner .announcement-body em {
    color: #93d5a1;
}
.announcement-banner .announcement-body code {
    background: rgba(var(--ann-accent-rgb, 34, 255, 94), 0.1);
    color: #f5f1e8;
    padding: 1px 4px;
    border-radius: 2px;
    font-size: 0.9em;
}

/* footer — `› see all` */
.announcement-banner .announcement-footer {
    margin-top: 4px;
    margin-bottom: 10px;
    text-align: right;
}
.announcement-banner .announcement-see-all {
    color: #93d5a1 !important;
    font-size: 11px;
    font-family: var(--font-mono);
    text-decoration: none;
    letter-spacing: 0.04em;
    opacity: 1 !important;
    transition: color 120ms ease;
}
.announcement-banner .announcement-see-all:hover,
.announcement-banner .announcement-see-all:focus-visible {
    color: var(--ann-accent, #22ff5e) !important;
    text-decoration: underline;
    outline: none;
}

/* keystroke dismiss chips */
.announcement-banner .announcement-keys {
    display: flex;
    gap: 8px;
    justify-content: flex-end;
    margin-top: 4px;
}

.announcement-banner .announcement-key {
    background: #0a0a0a;
    color: var(--ann-accent, #22ff5e);
    border: 1px solid var(--ann-accent, #22ff5e);
    padding: 4px 9px;
    border-radius: 2px;
    font-family: var(--font-mono);
    font-size: 11px;
    cursor: pointer;
    letter-spacing: 0.04em;
    transition: background 120ms ease, color 120ms ease;
}
.announcement-banner .announcement-key:hover,
.announcement-banner .announcement-key:focus-visible {
    background: var(--ann-accent, #22ff5e);
    color: #0a0a0a;
    outline: none;
}
