/* Category tab clicks (and every other in-site link) are full page
   navigations, not AJAX — without this the browser just hard-swaps the
   DOM, which reads as a jarring flash/jitter. Opting into cross-document
   view transitions makes Chrome cross-fade between the old and new page
   instead, so switching categories feels smooth rather than jumpy. */
@view-transition {
    navigation: auto;
}

::view-transition-old(root),
::view-transition-new(root) {
    animation-duration: 0.2s;
    animation-timing-function: ease-out;
}

:root {
    --bg-page: #171d29;
    --bg-card: #1f2736;
    --bg-card-hover: #293345;
    --bg-input: #141a24;
    --text-primary: #f2f4f8;
    --text-secondary: #8d97a8;
    --text-muted: #626d80;
    --border-color: rgba(255, 255, 255, 0.07);
    --accent-primary: #00b8ff;
    --accent-primary-rgb: 0, 184, 255;
    --accent-primary-hover: #0093cc;
    --accent-blue: #3b82f6;
    --accent-orange: #f2994a;
    --accent-purple: #9b6dff;
    --accent-red: #e2574c;
    --accent-green: #3ecf72;
    --radius: 14px;
    --radius-sm: 8px;
    --sidebar-width: 165px;
    --topbar-height: 64px;
    --chrome-corner-radius: 20px;
}

* {
    box-sizing: border-box;
}

/* Different categories have different numbers of posts, so page height
   varies a lot between them. Without this, the vertical scrollbar
   appears/disappears depending on whether the new page needs one,
   which shifts the entire fixed-width layout sideways by the
   scrollbar's width on every navigation — that's the jitter.
   scrollbar-gutter: stable used to handle this by reserving a blank
   gutter even on short pages, but a *reserved* gutter isn't the same
   as a *real* scrollbar — .topbar/.sidebar are position: fixed with
   right: 0, and their positioning viewport shrinks to exclude that
   reserved gutter the same way it would a real scrollbar, so on a
   short page (gutter reserved but empty, no actual scrollbar drawn in
   it) the header stopped short of the true window edge, leaving a
   visible gap. Forcing a real, always-present scrollbar instead keeps
   the layout width just as constant (fixing the original jitter) while
   also making sure that space is always genuinely occupied by the
   scrollbar itself, so fixed elements always sit flush against it. */
html {
    overflow-y: scroll;
    /* Firefox (and Firefox-based browsers like Floorp) render an
       always-on scrollbar as a wide, opaque gray track by default —
       exactly the "empty space" that stood out on short pages. Thinning
       it and making the track transparent lets it blend into the page
       background instead, so it reads as a barely-there strip rather
       than a blank gray gutter, whether or not there's actually
       anything to scroll. */
    scrollbar-width: thin;
    scrollbar-color: var(--border-color) transparent;
}

/* Chrome/Safari/Edge equivalent of the Firefox rule above — same
   transparent-track, subtle-thumb treatment, just via the
   WebKit-specific pseudo-elements instead of the standard properties. */
::-webkit-scrollbar {
    width: 10px;
}

::-webkit-scrollbar-track {
    background: transparent;
}

::-webkit-scrollbar-thumb {
    background: var(--border-color);
    border-radius: 999px;
}

::-webkit-scrollbar-thumb:hover {
    background: var(--text-muted);
}

body {
    margin: 0;
    margin-left: var(--sidebar-width);
    background: var(--bg-page);
    color: var(--text-primary);
    font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    padding: calc(var(--topbar-height) + 36px) 44px 36px;
    min-height: 100vh;
    line-height: 1.5;
}

h1, h2, h3 {
    font-weight: 700;
    letter-spacing: -0.01em;
    margin-top: 0;
}

h1 {
    font-size: 26px;
    margin-bottom: 18px;
}

/* A page title with a primary action button pinned to the right —
   used on the forums index so "New Post" always sits top-right
   instead of living in the sidebar. */
.page-header-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    margin-bottom: 18px;
}

.page-header-row h1 {
    margin-bottom: 0;
}

h2 {
    font-size: 19px;
    margin-bottom: 14px;
}

h3 {
    font-size: 16px;
}

a {
    color: var(--accent-blue);
    text-decoration: none;
}

a:hover {
    text-decoration: underline;
}

p {
    color: var(--text-secondary);
}

em {
    color: var(--text-muted);
    font-style: normal;
    font-size: 13px;
}

strong {
    color: var(--text-primary);
}

hr {
    display: none;
}

/* ---------- Sidebar ---------- */

.sidebar {
    position: fixed;
    top: 0;
    left: 0;
    /* Hugs the widest nav label instead of a fixed width, capped so an
       overly long custom-page title or category name can't blow the
       sidebar out — 30ch is a hard ceiling, not a target. The
       resulting rendered width is read back into --sidebar-width via
       JS (see header.php) so the topbar/body/glow layers next to it
       stay flush either way. */
    width: fit-content;
    max-width: 30ch;
    height: 100vh;
    background: var(--bg-card);
    display: flex;
    flex-direction: column;
    padding: 24px 16px;
    overflow-y: auto;
    overflow-x: hidden;
}

.sidebar-logo {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 28px;
    padding: 0 6px;
}

.logo-badge {
    width: 34px;
    height: 34px;
    min-width: 34px;
    border-radius: 50%;
    background: linear-gradient(135deg, var(--accent-blue), var(--accent-primary));
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 800;
    font-size: 13px;
    color: white;
}

.logo-text {
    font-weight: 800;
    font-size: 17px;
    color: var(--text-primary);
    text-decoration: none;
}

/* Replaces the badge+text entirely when a site logo is uploaded.
   Capped to the sidebar's own available width/height so an oddly-sized
   upload can't blow out the sidebar's fixed layout — object-fit:contain
   keeps its aspect ratio inside that box instead of stretching. */
.logo-image {
    max-width: 100%;
    max-height: 64px;
    width: auto;
    height: auto;
    object-fit: contain;
    background: transparent;
}

.logo-preview {
    margin-bottom: 14px;
}

.logo-preview img {
    max-width: 100%;
    max-height: 80px;
    object-fit: contain;
    background: var(--bg-input);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    padding: 10px;
}

/* Favicons are tiny (often 16-32px), so previewing them at the same
   size as a logo would just show a blurry blown-up icon — cap it down
   to something proportionate to what actually shows in a browser tab. */
.favicon-preview img {
    max-width: 48px;
    max-height: 48px;
}

.sidebar-nav {
    display: flex;
    flex-direction: column;
    gap: 3px;
    flex: 1;
}

/* Same layout as .sidebar-nav but doesn't grow to fill space — used for
   the top segment and the bottom-custom-pages segment, so only the
   center segment stretches and pushes the footer down. */
.sidebar-nav-static {
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.nav-item {
    display: block;
    padding: 10px 14px;
    border-radius: var(--radius-sm);
    color: var(--text-secondary);
    text-decoration: none;
    font-size: 14px;
    font-weight: 500;
    border-left: 3px solid transparent;
    transition: background 0.15s ease, color 0.15s ease;
    /* A narrower sidebar means longer page titles won't always fit —
       truncate with an ellipsis instead of wrapping to a second line
       (which would break the nav's fixed row height) or overflowing
       the sidebar's edge. The full title is still available via the
       title="" attribute custom-page links carry. */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.nav-item:hover {
    background: var(--bg-card-hover);
    color: var(--text-primary);
    text-decoration: none;
}

.nav-item.active {
    background: var(--bg-card-hover);
    color: var(--text-primary);
    border-left: 3px solid var(--accent-primary);
    box-shadow: inset 3px 0 8px -2px rgba(var(--accent-primary-rgb), 0.7);
}

/* Fixed-width icon column so item labels line up regardless of how
   wide any individual glyph happens to be. */
.nav-item-icon {
    display: inline-block;
    width: 18px;
    margin-right: 10px;
    text-align: center;
    color: var(--text-muted);
}

.nav-item:hover .nav-item-icon,
.nav-item.active .nav-item-icon {
    color: inherit;
}

/* Blog is now a real accordion (button + collapsible panel) instead of
   a link, so the reveal happens via an actual open/closed transition
   on the panel itself rather than a one-shot entrance animation on
   each subitem. */
.nav-group-trigger {
    background: none;
    border: none;
    border-left: 3px solid transparent;
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-family: inherit;
    cursor: pointer;
}

.nav-group-trigger-label {
    display: flex;
    align-items: center;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.nav-group-caret {
    font-size: 10px;
    color: var(--text-muted);
    transition: transform 0.15s ease;
    margin-left: 8px;
    flex: 0 0 auto;
}

.nav-group.open .nav-group-caret {
    transform: rotate(180deg);
}

.nav-group-trigger:hover .nav-group-caret,
.nav-group-trigger.active .nav-group-caret {
    color: inherit;
}

/* grid-template-rows animates between 0fr (fully collapsed) and 1fr
   (natural content height) without needing JS to measure pixel
   heights — the standard trick for animating to/from "auto" height.
   The inner wrapper's overflow:hidden is what lets the row track
   actually shrink past the content's intrinsic size. */
.nav-group-panel {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows 0.2s ease;
}

.nav-group.open .nav-group-panel {
    grid-template-rows: 1fr;
}

.nav-group-panel-inner {
    overflow: hidden;
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.nav-subitem {
    padding: 7px 14px 7px 26px;
    font-size: 13px;
    font-weight: 400;
    position: relative;
    margin-top: -2px;
}

.nav-subitem::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 1px;
    background: var(--border-color);
}

.nav-subitem-plus {
    display: inline-block;
    margin-right: 8px;
    color: var(--text-muted);
    font-weight: 700;
}

.nav-subitem:hover .nav-subitem-plus,
.nav-subitem.active .nav-subitem-plus {
    color: inherit;
}

/* ---------- Topbar ---------- */

.topbar {
    position: fixed;
    top: 0;
    left: var(--sidebar-width);
    right: 0;
    height: var(--topbar-height);
    background: var(--bg-card);
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 28px;
    z-index: 20;
}

/* Sits flush at the left edge of the topbar, right where the sidebar
   ends. Sized down from the site's default form-field styling (which
   is built for full-width stacked form cards) so it fits the fixed
   64px topbar height. */
/* A single pill-shaped container owns the border/radius/clipping so the
   select, input, and button read as one continuous rounded bar instead
   of three separately-rounded pieces that have to line up by hand. */
.topbar-search {
    display: flex;
    align-items: stretch;
    gap: 0;
    border: 1px solid var(--border-color);
    border-radius: 999px;
    overflow: hidden;
}

.topbar-search select,
.topbar-search input[type="text"],
.topbar-search button {
    margin: 0;
    width: auto;
    font-size: 13px;
    padding: 8px 12px;
    border-radius: 0;
    border: none;
}

.topbar-search select {
    color: var(--text-secondary);
    background: var(--bg-input);
}

.topbar-search input[type="text"] {
    width: 200px;
}

.topbar-search input[type="text"]:focus {
    border: none;
    outline: none;
}

.topbar-search-submit {
    background: var(--bg-input);
    color: var(--text-secondary);
    line-height: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 8px 14px;
}

.topbar-search-submit:hover {
    background: var(--bg-card-hover);
    color: var(--text-primary);
}

/* A glowing accent line that traces the inner edge of the chrome: down
   the sidebar's right side, then along the topbar's bottom, joined by
   a rounded elbow instead of a sharp point. Three pieces, all fixed
   and non-interactive:
     1. a vertical line starting right below the topbar
     2. a horizontal line starting right after the elbow
     3. the elbow itself, a quarter-circle arc
   --chrome-corner-radius controls how big the curve is; the vertical
   and horizontal pieces are offset by that same amount so they meet
   the arc with no gap and no overlap. */

.chrome-glow-vertical {
    position: fixed;
    top: calc(var(--topbar-height) + var(--chrome-corner-radius));
    left: var(--sidebar-width);
    width: 2px;
    height: calc(100vh - var(--topbar-height) - var(--chrome-corner-radius));
    background: var(--accent-primary);
    box-shadow: 0 0 12px 2px rgba(var(--accent-primary-rgb), 0.6);
    z-index: 21;
    pointer-events: none;
}

.chrome-glow-horizontal {
    position: fixed;
    top: var(--topbar-height);
    left: calc(var(--sidebar-width) + var(--chrome-corner-radius));
    right: 0;
    height: 2px;
    background: var(--accent-primary);
    box-shadow: 0 0 12px 2px rgba(var(--accent-primary-rgb), 0.6);
    z-index: 21;
    pointer-events: none;
}

/* The corner box (top: topbar-height, left: sidebar-width, size R×R)
   sits entirely in the page's own content area — neither the sidebar
   nor the topbar actually extend into it. The glow arc below only
   draws a stroke, so its transparent background let the page's
   --bg-page show through everywhere inside that box, including the
   small wedge nearest the sharp top-left corner that's supposed to
   read as chrome. This fill paints just that wedge (the region
   *outside* the arc's radius, i.e. more than chrome-corner-radius
   from the box's bottom-right corner) back to --bg-card; everywhere
   inside the radius stays transparent since content's own background
   already belongs there. Same circle math as the arc's own
   border-radius, so the color boundary and the glow stroke land on
   the same curve with no seam between them. */
.chrome-corner-fill {
    position: fixed;
    top: var(--topbar-height);
    left: var(--sidebar-width);
    width: var(--chrome-corner-radius);
    height: var(--chrome-corner-radius);
    background: radial-gradient(circle at 100% 100%, transparent var(--chrome-corner-radius), var(--bg-card) var(--chrome-corner-radius));
    z-index: 19;
    pointer-events: none;
}

/* The elbow stroke itself: a box with a real border-radius on just its
   top-left corner, colored only on the top and left sides (the two
   sides that continue the straight lines) — right and bottom stay
   borderless. Earlier this used overflow:hidden to clip a circle into
   an arc, but overflow:hidden also clips the glow's blur at that same
   hard rectangular edge, so the glow faded smoothly on the straight
   stretches but cut off abruptly right at the corner. A real
   border-radius shape needs no clipping, so filter: drop-shadow()
   below can blur outward in every direction the same way the straight
   lines do — drop-shadow follows the shape's actual painted pixels
   (the arc), not its invisible bounding box, so the transparent
   right/bottom sides don't produce any shadow of their own. */
.chrome-glow-corner {
    position: fixed;
    top: var(--topbar-height);
    left: var(--sidebar-width);
    width: var(--chrome-corner-radius);
    height: var(--chrome-corner-radius);
    border-top: 2px solid var(--accent-primary);
    border-left: 2px solid var(--accent-primary);
    border-top-left-radius: var(--chrome-corner-radius);
    filter: drop-shadow(0 0 8px rgba(var(--accent-primary-rgb), 0.6));
    z-index: 21;
    pointer-events: none;
}

/* A single comet shape — bright round head plus a fading gradient tail
   — that rides the same L-shaped route as the three static
   chrome-glow-* pieces above (down the sidebar, around the elbow,
   along the topbar) via the CSS motion-path properties. offset-path is
   set in JS (see header.php's updateChromeTracerPath) as a
   there-and-back loop, since two of its endpoints depend on the live
   sidebar width and viewport size, which plain CSS custom properties
   can't read.
   The container is anchored (and pivoted, for rotation) at its own
   right-center edge via offset-anchor — that's the point that actually
   rides the path. offset-rotate: auto then spins the whole box so that
   pinned point always faces the path's current direction of travel,
   which drags the tail (laid out to its *left*, i.e. behind the
   anchor) out behind the head correctly at every point along the
   route, including smoothly around the curved elbow, without any of
   the discrete "beads" a multi-element delay-based trail produced. */
.chrome-tracer {
    position: fixed;
    top: 0;
    left: 0;
    width: 22px;
    height: 4px;
    offset-anchor: 100% 50%;
    offset-rotate: auto;
    pointer-events: none;
    z-index: 22;
    animation: chrome-tracer-move 20s linear infinite;
}

/* Same 2px thickness as the static glow line itself (.chrome-glow-vertical
   / .chrome-glow-horizontal) — this is meant to read as a bright hot
   point traveling along that existing line, not as a separate object
   riding on top of it. */
.chrome-tracer-tail {
    position: absolute;
    top: 50%;
    left: 0;
    right: 2px;
    height: 2px;
    transform: translateY(-50%);
    background: linear-gradient(to right, transparent, rgba(var(--accent-primary-rgb), 0.9));
    filter: blur(0.75px);
}

.chrome-tracer-head {
    position: absolute;
    top: 50%;
    right: 0;
    width: 2px;
    height: 2px;
    transform: translateY(-50%);
    border-radius: 50%;
    background: #ffffff;
    box-shadow:
        0 0 3px 0.5px #ffffff,
        0 0 6px 1.5px rgba(var(--accent-primary-rgb), 0.85),
        0 0 11px 3px rgba(var(--accent-primary-rgb), 0.4);
}

@keyframes chrome-tracer-move {
    0% {
        offset-distance: 0%;
    }
    100% {
        offset-distance: 100%;
    }
}

.topbar-guest-links {
    display: flex;
    gap: 10px;
}

/* Groups the bell and the avatar dropdown into one flex item so
   .topbar's justify-content: space-between (which is built for exactly
   two children — the search bar and "everything else") treats them as
   a single unit pinned to the right, instead of spreading three direct
   children evenly and stranding the bell by itself in the middle. */
.topbar-actions {
    display: flex;
    align-items: center;
    gap: 6px;
}

.user-menu {
    position: relative;
}

.user-menu-trigger {
    display: flex;
    align-items: center;
    gap: 10px;
    background: transparent;
    border: none;
    padding: 6px 10px 6px 6px;
    border-radius: 999px;
    cursor: pointer;
    color: var(--text-primary);
}

.user-menu-trigger:hover {
    background: var(--bg-card-hover);
}

.user-menu-name {
    font-weight: 600;
    font-size: 14px;
}

.user-menu-caret {
    font-size: 10px;
    color: var(--text-muted);
    transition: transform 0.15s ease;
}

.user-menu.open .user-menu-caret {
    transform: rotate(180deg);
}

/* display:none can't be transitioned, so the closed state instead
   fades/slides out via opacity+transform and is kept out of the way
   (unclickable, unfocusable) with visibility + pointer-events. The
   visibility switch is delayed only on close (via the transition's own
   delay), so it opens immediately but waits for the fade-out to finish
   before actually leaving the layout. */
.user-menu-dropdown {
    position: absolute;
    top: calc(100% + 10px);
    right: 0;
    width: 240px;
    background: var(--bg-card);
    border: 1px solid var(--border-color);
    border-radius: var(--radius);
    box-shadow: 0 16px 32px rgba(0, 0, 0, 0.35);
    padding: 16px;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transform: translateY(-8px) scale(0.98);
    transform-origin: top right;
    transition: opacity 0.15s ease, transform 0.15s ease, visibility 0s linear 0.15s;
}

.user-menu.open .user-menu-dropdown {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transform: translateY(0) scale(1);
    transition: opacity 0.15s ease, transform 0.15s ease, visibility 0s linear 0s;
}

.user-menu-avatar {
    display: flex;
    justify-content: center;
    margin-bottom: 14px;
}

.user-menu-stats {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding-bottom: 14px;
    margin-bottom: 10px;
    border-bottom: 1px solid var(--border-color);
}

.user-menu-stat {
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.user-menu-stat .post-user-stat-label {
    font-size: 12px;
}

.user-menu-links {
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.user-menu-link {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 9px 10px;
    border-radius: var(--radius-sm);
    color: var(--text-secondary);
    font-size: 14px;
    font-weight: 500;
}

.user-menu-link:hover {
    background: var(--bg-card-hover);
    color: var(--text-primary);
    text-decoration: none;
}

.user-menu-link.active {
    background: var(--bg-card-hover);
    color: var(--text-primary);
}

.user-menu-logout {
    color: var(--accent-red);
}

.user-menu-logout:hover {
    background: rgba(226, 87, 76, 0.12);
    color: var(--accent-red);
}

/* Logout used to be a plain <a href="/logout.php"> — now a POST form
   (CSRF-protected, see handlers/handle_logout.php) with a <button>
   standing in for it, styled back to look identical to every other
   .user-menu-link row rather than a native button. */
.user-menu-logout-form {
    margin: 0;
}

.user-menu-logout-form .user-menu-logout {
    width: 100%;
    border: none;
    background: none;
    cursor: pointer;
    font-family: inherit;
    text-align: left;
}

/* ---------- Notification bell ---------- */
/* Same component shape as .user-menu just above (trigger button +
   absolutely-positioned dropdown, opacity/transform toggled by an
   .open class on the wrapper) — kept as its own set of classes rather
   than sharing .user-menu's, since the trigger is an icon+badge button
   instead of an avatar+name, and the dropdown is a list of rows instead
   of a stats block. */
.notif-menu {
    position: relative;
}

.notif-menu-trigger {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    background: transparent;
    border: none;
    border-radius: 999px;
    cursor: pointer;
    color: var(--text-secondary);
    font-size: 16px;
}

.notif-menu-trigger:hover {
    background: var(--bg-card-hover);
    color: var(--text-primary);
}

/* #navMessagesBadge carries .badge/.badge-accent too (see header.php),
   which sets its own `display: inline-block` — a plain [hidden]
   attribute alone can't win against that (an author-origin display
   rule always beats the UA stylesheet's built-in [hidden] rule,
   regardless of selector specificity), so live_header_script.php's
   hide/show toggle needs this explicit override to actually take
   effect. #navNotifBadge doesn't strictly need this (nothing else sets
   display on .notif-menu-badge), included anyway for symmetry so both
   badges the live poller touches behave identically. */
#navNotifBadge[hidden],
#navMessagesBadge[hidden] {
    display: none;
}

.notif-menu-badge {
    position: absolute;
    top: 2px;
    right: 2px;
    min-width: 16px;
    height: 16px;
    padding: 0 4px;
    border-radius: 999px;
    background: var(--accent-red);
    color: #fff;
    font-size: 10px;
    font-weight: 700;
    line-height: 16px;
    text-align: center;
}

.notif-menu-dropdown {
    position: absolute;
    top: calc(100% + 10px);
    right: 0;
    width: 320px;
    max-height: 420px;
    overflow-y: auto;
    background: var(--bg-card);
    border: 1px solid var(--border-color);
    border-radius: var(--radius);
    box-shadow: 0 16px 32px rgba(0, 0, 0, 0.35);
    padding: 10px;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transform: translateY(-8px) scale(0.98);
    transform-origin: top right;
    transition: opacity 0.15s ease, transform 0.15s ease, visibility 0s linear 0.15s;
}

.notif-menu.open .notif-menu-dropdown {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transform: translateY(0) scale(1);
    transition: opacity 0.15s ease, transform 0.15s ease, visibility 0s linear 0s;
}

.notif-menu-dropdown-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 6px 8px 10px;
    font-weight: 700;
    font-size: 13px;
    color: var(--text-primary);
    border-bottom: 1px solid var(--border-color);
    margin-bottom: 6px;
}

.notif-menu-viewall {
    font-size: 12px;
    font-weight: 500;
    color: var(--text-secondary);
}

.notif-menu-empty {
    padding: 16px 8px;
    text-align: center;
    color: var(--text-muted);
    font-size: 13px;
    margin: 0;
}

.notif-menu-item {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 8px;
    border-radius: var(--radius-sm);
    color: inherit;
}

.notif-menu-item:hover {
    background: var(--bg-card-hover);
    text-decoration: none;
}

.notif-menu-item-unread {
    background: rgba(var(--accent-primary-rgb), 0.08);
}

.notif-menu-item-text {
    display: block;
    font-size: 13px;
    line-height: 1.45;
    color: var(--text-secondary);
}

.notif-menu-item-text strong {
    color: var(--text-primary);
}

.notif-menu-item-time {
    display: block;
    margin-top: 2px;
    font-size: 11px;
    color: var(--text-muted);
}

/* ---------- @mention autocomplete ---------- */
/* Appended once to <body> by mention_autocomplete_script.php rather
   than living inside any particular textarea's own markup, since it's
   shared across every mention-enabled textarea on the page and needs
   to render above everything else regardless of which card/column that
   textarea happens to sit in — position: absolute here pairs with the
   script computing its top/left from the textarea's own
   getBoundingClientRect() plus the current scroll offset (page
   coordinates), not position: fixed (viewport coordinates). */
.mention-autocomplete-dropdown {
    position: absolute;
    z-index: 500;
    display: none;
    flex-direction: column;
    background: var(--bg-card);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    box-shadow: 0 12px 28px rgba(0, 0, 0, 0.35);
    padding: 4px;
    max-height: 220px;
    overflow-y: auto;
}

.mention-autocomplete-dropdown.open {
    display: flex;
}

.mention-autocomplete-item {
    display: block;
    width: 100%;
    text-align: left;
    background: transparent;
    border: none;
    padding: 7px 10px;
    border-radius: var(--radius-sm);
    color: var(--text-secondary);
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    margin-bottom: 0;
}

.mention-autocomplete-item:hover,
.mention-autocomplete-item-active {
    background: var(--bg-card-hover);
    color: var(--text-primary);
}

/* ---------- Cards ---------- */

.card {
    background: var(--bg-card);
    border: 1px solid var(--border-color);
    border-radius: var(--radius);
    padding: 20px 22px;
    margin-bottom: 14px;
    /* .topbar is position: fixed, so jumping straight to an in-page
       anchor (a comment linked from a notification, see
       #comment-<id> in view_post.php) would otherwise land the target
       right underneath it. scroll-margin-top applies to *any* anchor
       scroll — clicking the link, browser back/forward, the user
       hitting Home/End — not just an initial page load, so it covers
       every way of landing on the target. */
    scroll-margin-top: calc(var(--topbar-height) + 20px);
}

/* Same fixed-.topbar offset as .card above, for anchor targets that
   aren't a .card themselves — e.g. store.php's section <h2>s, which
   index.php's Coin Shop feed deep-links into (#store-badges etc.). */
.anchor-target {
    scroll-margin-top: calc(var(--topbar-height) + 20px);
}

/* Briefly highlights whichever .card the URL fragment currently points
   at (see id="comment-<id>" in view_post.php) — a plain jump-to-anchor
   is easy to miss when it lands you inside a long comment thread that
   looks like every other comment; the glow makes it obvious which one
   you were actually sent to. :target is native CSS (matches whatever
   element's id equals the current #fragment), so this needs no JS and
   updates automatically if the fragment changes via pjax navigation. */
.card:target {
    border-color: var(--accent-primary);
    box-shadow: 0 0 0 1px var(--accent-primary), 0 0 24px rgba(var(--accent-primary-rgb), 0.25);
    animation: card-target-fade 2.5s ease-out;
}

@keyframes card-target-fade {
    0% {
        background: rgba(var(--accent-primary-rgb), 0.12);
    }
    100% {
        background: var(--bg-card);
    }
}

/* The OP-marked best answer's comment card (see comments.is_best_answer
   in db.php, set via handle_mark_best_answer.php) — a permanent tint,
   unlike .card:target's fading glow above, since this should stay
   visually distinct for as long as the mark stands, not just on the
   one visit that lands directly on it. #3ecf72 is --accent-green as a
   literal rgb() triple; there's no --accent-green-rgb variable defined
   sitewide the way --accent-primary-rgb is, so this spells it out
   rather than introducing a new variable for a single use. */
.best-answer-card {
    border-color: var(--accent-green);
    background: rgba(62, 207, 114, 0.07);
}

.best-answer-badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    color: var(--accent-green);
    font-size: 13px;
    font-weight: 700;
    margin: 0 0 10px;
}

/* The compact "jump to" card above the comment list (see
   $bestAnswerComment in view_post.php) — same green accent as
   .best-answer-card so the two read as one feature, but left/border
   treatment rather than a full tint since this card's job is just to
   point at the real one, not to be the highlighted comment itself. */
.best-answer-preview {
    border-left: 3px solid var(--accent-green);
}

.best-answer-preview-label {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    color: var(--accent-green);
    font-size: 13px;
    font-weight: 700;
    margin: 0 0 8px;
}

.best-answer-preview-body {
    color: var(--text-primary);
    margin: 0 0 8px;
}

/* A <form> immediately after text (a paragraph, a title, an inline
   sibling form) tends to sit flush against it otherwise — <form> has
   no margin of its own by default, so it just touches whatever's
   above it. This gives every action button inside a card consistent
   breathing room without needing every page to remember to add its
   own spacing by hand. */
.card form {
    margin-top: 10px;
}

.card form:first-child {
    margin-top: 0;
}

.card-title a {
    color: var(--text-primary);
    font-size: 17px;
    font-weight: 700;
}

.card-meta {
    color: var(--text-muted);
    font-size: 13px;
    margin: 4px 0 10px;
}

/* Live character counter injected by char_count_script.php right after
   any <textarea data-char-count> — pulled up against the textarea's
   own bottom margin (rather than sitting a full 16px below it) and
   right-aligned so it reads as attached to that specific field, not as
   its own separate line of page content. */
.char-count {
    margin: -10px 0 14px;
    font-size: 12px;
    color: var(--text-muted);
    text-align: right;
}

/* Small inline permalink/copy-link icon sitting right in a .card-meta
   timestamp line (see view_post.php) — deliberately NOT styled like a
   normal .btn (the global button rule's own padding/font-size would
   read as a full action button next to a small caption), just a
   quiet icon that lights up on hover and confirms itself briefly via
   .copied when copy_link_script.php's clipboard write succeeds. */
.copy-link-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    padding: 0;
    margin-left: 4px;
    vertical-align: middle;
    background: transparent;
    border: none;
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    font-size: 12px;
    cursor: pointer;
}

.copy-link-btn:hover {
    background: var(--bg-card-hover);
    color: var(--accent-primary);
}

.copy-link-btn.copied {
    color: var(--accent-green);
}

/* Live "Draft saved HH:MM:SS UTC" caption injected by
   autosave_script.php right after any [data-autosave-body] field —
   same pulled-up/right-aligned treatment as .char-count above (the two
   sometimes sit on the same textarea), just its own class since they're
   independent pieces of status text that can both be present at once. */
.autosave-status {
    margin: -10px 0 14px;
    font-size: 12px;
    color: var(--text-muted);
    text-align: right;
    min-height: 15px;
}

/* Restore-draft banner (create_post.php/edit_post.php/edit_comment.php/
   view_post.php's reply form) — sits inside the <form> itself, right
   after the CSRF field, so it's gone (along with the draft it's
   offering) the moment Restore or Discard is clicked. Uses the same
   tinted-accent-primary treatment as the "New Post remembered this
   forum" note below rather than admin-lock-bypass-banner's orange —
   this is routine, expected recovery, not a warning. */
.draft-banner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    background: rgba(var(--accent-primary-rgb), 0.1);
    border: 1px solid rgba(var(--accent-primary-rgb), 0.3);
    border-radius: var(--radius-sm);
    padding: 12px 16px;
    margin-bottom: 16px;
}

.draft-banner p {
    margin: 0;
    font-size: 14px;
    color: var(--text-primary);
}

.draft-banner-actions {
    display: flex;
    gap: 8px;
    flex-shrink: 0;
}

.draft-banner-actions button {
    padding: 6px 14px;
    font-size: 13px;
}

.draft-banner-actions [data-draft-restore] {
    background: var(--accent-primary);
    color: #10160f;
    border: none;
}

.draft-banner-actions [data-draft-restore]:hover {
    background: var(--accent-primary-hover);
}

/* Live "N new replies" banner — includes/thread_poll_script.php shows
   this once poll_thread_updates.php reports new activity on the thread
   currently open. Same visual shape as .draft-banner above (accent-
   tinted card, flex row, [hidden] toggled by JS rather than a class)
   for consistency with the app's one other "quiet notice bar" pattern. */
.new-replies-banner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    background: rgba(var(--accent-primary-rgb), 0.1);
    border: 1px solid rgba(var(--accent-primary-rgb), 0.3);
    border-radius: var(--radius-sm);
    padding: 12px 16px;
    margin-bottom: 16px;
    font-size: 14px;
    color: var(--text-primary);
}

.new-replies-banner[hidden] {
    display: none;
}

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

/* The post listing reuses the exact same row shape as the forum
   listing (.forum-row / .forum-row-main / .forum-row-stats /
   .forum-row-lastpost*) — an avatar, a title + byline, a count
   column, and a "latest" summary panel — for visual consistency
   between the two, and to avoid re-declaring nearly-identical CSS.
   Only the title/byline get their own classes below, since a post's
   "name" is a title+date-stamped byline rather than a forum's
   name+description; everything else is shared verbatim. Dropping the
   description excerpt and tag badges entirely (condensed down to just
   this one row, no second/third line of text) is what actually lets
   more posts fit on screen — this is a fair bit tighter than the old
   multi-line card even beyond the shared row's own compactness. */
.post-row-title {
    display: block;
    font-weight: 600;
    font-size: 16px;
    color: var(--text-primary);
    text-decoration: none;
}

.post-row-title:hover {
    color: var(--accent-primary);
    text-decoration: none;
}

.post-row-meta {
    margin: 4px 0 0;
    font-size: 13px;
    color: var(--text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Thread list indicators (posts.php + includes/thread_reads.php) — a
   thread's default .post-row-title look (bold, primary-color text) IS
   the "unread" look already, so only a "read" modifier is needed here
   rather than two competing states; guests (who have no read/unread
   concept at all) simply never get this class added and see the same
   default styling as always. */
.post-row-title-read {
    font-weight: 500;
    color: var(--text-secondary);
}

.post-row-title-read:hover {
    color: var(--accent-primary);
}

.post-row-unread-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--accent-primary);
    margin-right: 6px;
    vertical-align: middle;
}

.post-row-badge {
    vertical-align: middle;
    margin-left: 2px;
}

.badge-hot {
    background: rgba(242, 153, 74, 0.15);
    color: var(--accent-orange);
}

.badge {
    display: inline-block;
    padding: 2px 9px;
    border-radius: 999px;
    font-size: 12px;
    font-weight: 600;
    background: var(--bg-card-hover);
    color: var(--text-secondary);
}

.badge-accent {
    background: rgba(var(--accent-primary-rgb), 0.15);
    color: var(--accent-primary);
}

a.badge {
    text-decoration: none;
    transition: background 0.15s ease;
}

a.badge:hover {
    background: rgba(var(--accent-primary-rgb), 0.28);
    text-decoration: none;
}

/* ---------- Post / comment user sidebar ---------- */

.post-layout {
    display: flex;
    gap: 20px;
}

.post-user-sidebar {
    flex: 0 0 140px;
    width: 140px;
    padding-right: 20px;
    border-right: 1px solid var(--border-color);
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.post-user-name {
    font-weight: 700;
    color: var(--text-primary);
    font-size: 15px;
    word-break: break-word;
}

/* A member's own free-text line, shown only when their usergroup
   currently allows it (see usergroups.can_set_custom_title) — sits
   between the name and the usergroup badge, italic and a step quieter
   than the name so it reads as a personal flourish rather than another
   official label like the badge underneath it. No margin of its own —
   .post-user-sidebar is a flex column with its own gap between every
   child already, so an extra margin here would just double up the
   spacing on top of that (same mistake as the homepage dashboard's
   card spacing earlier). */
.post-user-title {
    margin: 0;
    font-size: 12px;
    font-style: italic;
    color: var(--text-secondary);
    word-break: break-word;
}

.usergroup-badge {
    width: fit-content;
    background: var(--bg-card-hover);
}

/* Publicly-visible banned state on the postbit (view_post.php, both
   the post author and each comment author) and profile.php's header —
   deliberately not gated behind canModerate(), unlike the staff-only
   Moderation panel that already existed. The name itself still carries
   an inline style="color: <usergroup color>" from its template (same
   markup every other name uses) — !important here is what lets the
   banned treatment actually win over that inline color instead of
   silently losing to it. */
.post-user-name-banned {
    color: var(--accent-red) !important;
    text-decoration: line-through;
    text-decoration-thickness: 2px;
}

/* Swaps in for the normal colored usergroup badge — red instead of
   the usergroup's own color, so "banned" reads as a status change
   rather than just another usergroup label. */
.badge-banned {
    color: var(--accent-red);
    border: 1px solid rgba(226, 87, 76, 0.4);
}

.post-user-banned-date {
    margin: 0;
    font-size: 12px;
    color: var(--accent-red);
}

/* Admin-uploaded per-usergroup banner (usergroups.userbar), shown under
   the usergroup badge in every postbit/profile sidebar. There's no
   server-side resize step (see handle_update_usergroup_userbar.php) —
   this is the entire "automatically resize to fit" behavior: constrain
   to the sidebar's own content width (140px flex-basis minus its 20px
   padding-right, box-sizing: border-box) and let height scale to match,
   so any uploaded resolution/aspect ratio still fits the column. No
   margin needed — .post-user-sidebar is a flex column with its own
   gap: 6px between children already. */
.post-user-userbar {
    display: block;
    /* ~27% larger than the 120px sidebar content width (two successive
       bumps: +10%, then +15% more). Rather than widening the sidebar
       column itself (which would also push out the avatar, name, badge,
       and every stat row), bleed the image alone past the column with
       matching negative margins — it lands symmetrically over the
       card's own padding on both sides, so nothing else in the sidebar
       shifts. Still comfortably clear of both the card's outer border
       and the sidebar's border-right divider at this size. */
    width: 152px;
    max-width: 152px;
    margin-left: -16px;
    margin-right: -16px;
    height: auto;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border-color);
}

.post-user-stat {
    margin-top: 6px;
    display: flex;
    flex-direction: column;
    gap: 1px;
}

.post-user-stat-label {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--text-muted);
}

.post-user-stat-value {
    font-size: 13px;
    color: var(--text-secondary);
}

/* Awards/badges row — "small icons visible at the bottom of the
   post bit", the last thing in .post-user-sidebar (see
   renderUserBadgeRow() in badges.php). "Badges" reuses the exact
   .post-user-stat-label class the Joined/Posts/Comments labels use
   (see style rule above) so its text size/weight/color can never drift
   from theirs — this rule only adds the top margin a lone <p> needs
   that .post-user-stat-label doesn't carry on its own (normally it
   sits inside .post-user-stat, which supplies spacing via flex gap). */
.post-user-badges-label {
    margin: 6px 0 0;
}

/* flex-wrap so a user with a handful of badges wraps onto a second line
   within the ~120px column rather than overflowing it. */
.post-user-badges {
    margin-top: 6px;
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

/* No background/border circle — just the icon itself, sized/centered so
   a run of several still lines up cleanly and the hover lift still has
   room to read as an affordance. */
.post-user-badge-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    transition: transform 0.15s ease;
}

.post-user-badge-icon:hover {
    transform: translateY(-2px);
}

.badge-icon-img {
    display: block;
    object-fit: contain;
    border-radius: 50%;
}

/* profile.php's clickable "Badges" section — same trigger/popup/open
   structure as .rep-widget/.rep-trigger/.rep-popup above (see
   reputation_widget.php), just scoped to badge_history_widget_script.php
   instead of reputation_widget_script.php. Only used on profile.php;
   view_post.php's postbit sticks with the plain, non-clickable
   .post-user-badges-label from renderUserBadgeRow(). */
.badge-history-widget {
    position: relative;
}

/* Resets the <button> defaults (border, background, padding, form-control
   font) back to plain text so this reads exactly like the Joined/Posts/
   Comments labels it sits among — .post-user-stat-label/.post-user-badges-label
   already supply the actual text styling and top margin; this only
   strips away what makes it look like a button and adds the pointer
   cursor + hover cue that says it's clickable. */
.badge-history-trigger {
    display: block;
    width: 100%;
    text-align: left;
    background: none;
    border: none;
    padding: 0;
    font-family: inherit;
    cursor: pointer;
}

.badge-history-trigger:hover {
    color: var(--text-secondary);
}

.badge-history-popup {
    position: absolute;
    top: calc(100% + 8px);
    left: 0;
    width: 360px;
    max-height: 480px;
    overflow-y: auto;
    background: var(--bg-card);
    border: 1px solid var(--border-color);
    border-radius: 20px;
    box-shadow: 0 16px 32px rgba(0, 0, 0, 0.35);
    padding: 18px;
    z-index: 20;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transform: translateY(-6px) scale(0.98);
    transform-origin: top left;
    transition: opacity 0.15s ease, transform 0.15s ease, visibility 0s linear 0.15s;
}

.badge-history-widget.open .badge-history-popup {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transform: translateY(0) scale(1);
    transition: opacity 0.15s ease, transform 0.15s ease, visibility 0s linear 0s;
}

.badge-history-popup-title {
    margin: 0 0 8px;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--text-muted);
}

.badge-history-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.badge-history-entry {
    background: var(--bg-input);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    padding: 14px;
}

.badge-history-entry-top {
    display: flex;
    align-items: center;
    gap: 10px;
}

.badge-history-entry-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 30px;
    height: 30px;
}

.badge-history-entry-body {
    flex: 1;
    min-width: 0;
}

.badge-history-entry-name {
    margin: 0;
    font-size: 13px;
    font-weight: 700;
    color: var(--text-primary);
}

.badge-history-entry-desc {
    margin: 2px 0 0;
    font-size: 12px;
    color: var(--text-secondary);
}

.badge-history-entry-date {
    flex-shrink: 0;
    font-size: 11px;
    color: var(--text-muted);
    white-space: nowrap;
}

.badge-history-sep {
    margin: 10px 0;
    border: none;
    border-top: 1px solid var(--border-color);
}

.badge-history-entry-reason,
.badge-history-entry-by {
    margin: 0;
    font-size: 13px;
    color: var(--text-secondary);
    word-break: break-word;
}

.badge-history-entry-by {
    margin-top: 4px;
}

.rep-positive {
    color: var(--accent-green);
}

.rep-negative {
    color: var(--accent-red);
}

.rep-neutral {
    color: var(--text-secondary);
}

/* Same open/close mechanic as .user-menu/.user-menu-dropdown (opacity +
   transform + a delayed visibility so it's not clickable while
   fading out), just anchored top-left off a small pill trigger
   instead of the topbar's full avatar+name button. */
.rep-widget {
    position: relative;
    display: inline-block;
}

.rep-trigger {
    background: var(--bg-input);
    border: 1px solid var(--border-color);
    border-radius: 999px;
    padding: 2px 10px;
    font-size: 13px;
    font-weight: 700;
    cursor: pointer;
    line-height: 1.6;
}

.rep-trigger:hover {
    border-color: rgba(var(--accent-primary-rgb), 0.4);
}

.rep-popup {
    position: absolute;
    top: calc(100% + 8px);
    left: 0;
    width: 400px;
    max-height: 520px;
    overflow-y: auto;
    background: var(--bg-card);
    border: 1px solid var(--border-color);
    border-radius: 20px;
    box-shadow: 0 16px 32px rgba(0, 0, 0, 0.35);
    padding: 18px;
    z-index: 20;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transform: translateY(-6px) scale(0.98);
    transform-origin: top left;
    transition: opacity 0.15s ease, transform 0.15s ease, visibility 0s linear 0.15s;
}

.rep-widget.open .rep-popup {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transform: translateY(0) scale(1);
    transition: opacity 0.15s ease, transform 0.15s ease, visibility 0s linear 0s;
}

.rep-vote-form {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.rep-comment-input {
    width: 100%;
    resize: vertical;
    min-height: 44px;
    font-size: 13px;
    padding: 8px 10px;
    margin-bottom: 0;
}

.rep-vote-buttons {
    display: flex;
    gap: 6px;
}

.rep-vote-btn {
    flex: 1;
    background: var(--bg-input);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    padding: 10px 0;
    font-size: 15px;
    line-height: 1;
    color: var(--text-secondary);
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

.rep-vote-btn:hover {
    background: var(--bg-card-hover);
    color: var(--text-primary);
}

.rep-vote-up.active {
    background: rgba(62, 207, 114, 0.15);
    border-color: var(--accent-green);
    color: var(--accent-green);
}

.rep-vote-down.active {
    background: rgba(226, 87, 76, 0.15);
    border-color: var(--accent-red);
    color: var(--accent-red);
}

.rep-vote-neutral.active {
    background: var(--bg-card-hover);
    border-color: var(--text-muted);
    color: var(--text-primary);
}

.rep-popup-note {
    margin: 0;
    font-size: 12px;
    color: var(--text-muted);
}

.rep-admin-form {
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px solid var(--border-color);
}

.rep-admin-form label {
    display: block;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--text-muted);
    margin-bottom: 4px;
}

.rep-admin-form-row {
    display: flex;
    gap: 6px;
}

.rep-admin-form-row input {
    width: 0;
    flex: 1;
    margin-bottom: 0;
}

.rep-admin-form-row button {
    flex-shrink: 0;
    padding: 8px 14px;
    font-size: 13px;
}

.rep-admin-comment {
    margin-top: 8px;
    margin-bottom: 0;
    font-size: 13px;
    padding: 8px 10px;
}

/* Combined vote + admin-adjustment log for this user, newest first —
   scrolls within the popup's own max-height rather than growing the
   popup unboundedly for a heavily-rated user. */
.rep-history {
    margin-top: 12px;
    padding-top: 12px;
    border-top: 1px solid var(--border-color);
}

.rep-history-title {
    margin: 0 0 8px;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--text-muted);
}

.rep-history-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

/* Each entry is its own little card: amount top-left / date top-right
   (with a vertical divider between them), the voter's name underneath,
   a horizontal divider, then the reason. Laid out top-to-bottom rather
   than the old single-row version now that the popup has the room for
   it — see reputation_widget.php for the matching markup. */
.rep-history-entry {
    background: var(--bg-input);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    padding: 14px;
}

.rep-history-entry-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
}

.rep-history-amount {
    font-weight: 700;
    font-size: 15px;
}

/* Date + the admin remove button are grouped as normal flex siblings
   here (not absolutely positioned over the row) specifically so they
   can never overlap — flexbox reserves real space for both instead of
   guessing at padding to leave clear. */
.rep-history-entry-right {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-shrink: 0;
}

.rep-history-date {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 11px;
    color: var(--text-muted);
    white-space: nowrap;
}

.rep-history-date-sep {
    width: 1px;
    height: 14px;
    background: var(--border-color);
}

.rep-history-actor {
    margin: 4px 0 0;
    font-size: 13px;
    font-weight: 600;
    color: var(--text-primary);
    display: flex;
    align-items: center;
    gap: 6px;
}

.rep-history-sep {
    margin: 10px 0;
    border: none;
    border-top: 1px solid var(--border-color);
}

.rep-history-comment {
    margin: 0;
    font-size: 13px;
    color: var(--text-secondary);
    word-break: break-word;
}

.rep-history-remove-btn {
    background: transparent;
    border: none;
    padding: 2px 6px;
    color: var(--text-muted);
    cursor: pointer;
    border-radius: var(--radius-sm);
}

.rep-history-remove-btn:hover {
    background: rgba(226, 87, 76, 0.15);
    color: var(--accent-red);
}

.post-main {
    flex: 1;
    min-width: 0;
}

/* "Add a comment" heading (or the locked notice) paired with the
   admin-only Lock/Unlock Comments toggle, right where it's relevant
   instead of bundled in with the post's own Edit/Delete actions. */
.comment-box-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: 14px;
}

/* Tags, sitting above the post card instead of inside it. */
.post-tags-top {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-bottom: 10px;
}

/* Edit/Delete/Lock/History actions, sitting below the post or comment
   card instead of inside it. Now always nested inside .post-footer-row
   (see below), which owns the outer top/bottom spacing that used to
   live here directly — .post-actions itself is just the left-hand
   button cluster within that shared row. */
.post-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

/* Shared row for a post's/comment's action buttons (left) and its
   like/dislike reactions (right) — previously two separate stacked
   blocks, which read as competing/cluttered once reactions existed
   alongside Edit/Delete/Pin/Watch/Report. .reaction-bar's margin-left:
   auto is what actually pins it to the right edge of this row, whether
   or not .post-actions is even present (it isn't for a logged-out
   visitor, who still sees reaction counts on their own). */
.post-footer-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px 16px;
    margin-top: -6px;
    margin-bottom: 14px;
}

@media (max-width: 640px) {
    .post-layout {
        flex-direction: column;
        gap: 12px;
    }

    .post-user-sidebar {
        flex: none;
        width: auto;
        padding-right: 0;
        padding-bottom: 12px;
        border-right: none;
        border-bottom: 1px solid var(--border-color);
        flex-direction: row;
        flex-wrap: wrap;
        align-items: center;
        gap: 10px;
    }

    .post-user-stat {
        margin-top: 0;
        flex-direction: row;
        gap: 4px;
    }
}

/* ---------- BBCode output ---------- */

.post-body {
    color: var(--text-secondary);
    line-height: 1.6;
    word-wrap: break-word;
}

/* ---------- Threaded comment replies (view_post.php) ----------
   One level of nesting only (see comments.parent_comment_id in
   db.php) — a lighter-weight layout than the top-level .card comment
   above it, no full postbit sidebar, just enough to tell replies apart
   at a glance while the left border keeps them visually grouped under
   their parent. */
.comment-replies {
    margin: 4px 0 20px;
    padding-left: 16px;
    border-left: 2px solid rgba(var(--accent-primary-rgb), 0.25);
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.comment-reply {
    padding-top: 2px;
}

.comment-reply-header {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin-bottom: 6px;
}

.comment-reply-username {
    font-weight: 600;
    font-size: 13px;
    text-decoration: none;
}

.comment-reply-username:hover {
    text-decoration: underline;
}

.comment-reply-body {
    font-size: 14px;
    margin-bottom: 8px;
}

/* "Replying to X" indicator above the comment textarea, shown/cleared
   by reply_trigger_script.php once a Reply button's been clicked. */
.reply-indicator {
    display: flex;
    align-items: center;
    gap: 8px;
    background: rgba(var(--accent-primary-rgb), 0.1);
    border: 1px solid rgba(var(--accent-primary-rgb), 0.3);
    border-radius: var(--radius-sm);
    padding: 8px 12px;
    margin-bottom: 12px;
    font-size: 13px;
    color: var(--text-primary);
}

.reply-indicator button {
    margin-left: auto;
    padding: 0 6px;
    background: transparent;
    border: none;
    color: var(--text-muted);
    font-size: 16px;
    line-height: 1;
    cursor: pointer;
}

.reply-indicator button:hover {
    color: var(--text-primary);
    background: transparent;
}

.post-signature {
    margin-top: 14px;
    padding-top: 12px;
    border-top: 1px dashed var(--border-color);
    color: var(--text-muted);
    font-size: 13px;
    line-height: 1.5;
    word-wrap: break-word;
}

.post-signature .bbcode-h1,
.post-signature .bbcode-h2,
.post-signature .bbcode-h3 {
    font-size: 15px;
    margin: 4px 0;
}

.bbcode-h1 {
    font-size: 28px;
    font-weight: 800;
    color: var(--text-primary);
    line-height: 1.25;
    margin: 16px 0 8px;
}

.bbcode-h2 {
    font-size: 21px;
    font-weight: 700;
    color: var(--text-primary);
    line-height: 1.3;
    margin: 14px 0 6px;
}

.bbcode-h3 {
    font-size: 17px;
    font-weight: 700;
    color: var(--text-primary);
    margin: 12px 0 4px;
}

.bbcode-u {
    text-decoration: underline;
}

/* @mentions, linkified server-side by bbcode.php's linkifyMentions()
   only when they match a real username. Pill-shaped so a mention reads
   as a distinct "handle" rather than a regular inline link, echoing how
   usergroup-badge already uses a pill for identity-ish things. Text
   color is set inline per-mention to the mentioned user's usergroup
   color (same convention as .post-user-name, usergroup-badge, etc.) so
   a mention always matches how that user's name is colored everywhere
   else on the site — the color here is just a fallback for the
   unlikely case a mention somehow renders without it. */
.mention {
    display: inline-block;
    padding: 1px 6px;
    border-radius: 999px;
    background: rgba(var(--accent-primary-rgb), 0.12);
    color: var(--accent-primary);
    font-weight: 600;
    text-decoration: none;
}

.mention:hover {
    background: rgba(var(--accent-primary-rgb), 0.2);
    text-decoration: none;
}

.bbcode-quote {
    border-left: 3px solid var(--accent-blue);
    background: var(--bg-card-hover);
    padding: 10px 14px;
    margin: 10px 0;
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
    color: var(--text-secondary);
}

.bbcode-quote cite {
    display: block;
    font-style: normal;
    font-weight: 600;
    font-size: 13px;
    color: var(--text-primary);
    margin-bottom: 4px;
}

.bbcode-code {
    display: block;
    background: var(--bg-input);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    padding: 12px 14px;
    margin: 10px 0;
    overflow-x: auto;
    font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
    font-size: 13px;
    white-space: pre-wrap;
    word-break: break-word;
    color: var(--text-primary);
}

/* [img]...[/img] output — capped to the post body's own width no
   matter what the source image's real dimensions are (a poster pasting
   a 4000px-wide photo shouldn't be able to blow out the whole page
   layout), with a little breathing room and rounded corners to match
   the rest of the card-based visual language. display:block so it
   doesn't inherit inline-image's baseline-alignment gap underneath it. */
.bbcode-img {
    display: block;
    max-width: 100%;
    height: auto;
    border-radius: var(--radius-sm);
    margin: 10px 0;
}

/* [list]/[list=1] output. Padding-left rather than the browser default
   margin so the bullets/numbers line up under the post body's own left
   edge instead of floating further right. */
.bbcode-list {
    margin: 10px 0;
    padding-left: 22px;
    color: var(--text-primary);
}

.bbcode-list li {
    margin: 4px 0;
}

/* [center]/[left]/[right] output. */
.bbcode-align-center { text-align: center; }
.bbcode-align-left { text-align: left; }
.bbcode-align-right { text-align: right; }

/* [spoiler]/[spoiler=Title] output — a native <details>/<summary>, so
   the click-to-reveal behavior needs no JS. Styled like .bbcode-code's
   block (same input background/border) rather than .bbcode-quote's
   accent-striped one, so a spoiler doesn't visually compete with an
   actual quoted reply for attention. */
.bbcode-spoiler {
    background: var(--bg-input);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    padding: 10px 14px;
    margin: 10px 0;
}

.bbcode-spoiler summary {
    cursor: pointer;
    font-weight: 600;
    color: var(--text-secondary);
}

.bbcode-spoiler summary:hover {
    color: var(--accent-primary);
}

/* Content is only ever visible once the <details> is opened, so this
   just adds a little breathing room above it rather than needing any
   show/hide logic of its own. */
.bbcode-spoiler[open] summary {
    margin-bottom: 8px;
}

/* [mark]...[/mark] output — overrides the browser default (black text
   on yellow), which reads badly against this site's dark cards. Tinted
   with the site's own accent color instead of a fixed yellow so it
   still looks intentional if an admin changes the accent via
   admin_theme.php. */
.bbcode-mark {
    background: rgba(var(--accent-primary-rgb), 0.35);
    color: var(--text-primary);
    padding: 0 3px;
    border-radius: 3px;
}

/* Song Bio card — a small "now playing"-style strip (see
   renderSongBioCard() in includes/song_bio.php), styled as one of
   this site's own dark cards rather than embedding a foreign
   Spotify/Apple Music iframe widget, so it always matches the theme's
   own colors even if an admin has customized the accent/background
   settings. Deliberately compact (fixed 56px artwork) since this
   shows up twice on a post (under the signature) and once on a
   profile, never as its own full-page feature. */
.song-bio-card {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: 10px;
    padding: 8px 10px;
    background: var(--bg-page);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    max-width: 320px;
}

.song-bio-art {
    width: 48px;
    height: 48px;
    border-radius: var(--radius-sm);
    object-fit: cover;
    flex-shrink: 0;
}

.song-bio-info {
    min-width: 0;
}

.song-bio-title {
    margin: 0;
    font-weight: 700;
    font-size: 13px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.song-bio-artist,
.song-bio-album {
    margin: 1px 0 0;
    font-size: 12px;
    color: var(--text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.song-bio-provider-link {
    display: inline-block;
    margin-top: 3px;
    font-size: 11px;
    font-weight: 600;
}

.bbcode-size-1 { font-size: 12px; }
.bbcode-size-2 { font-size: 14px; }
.bbcode-size-3 { font-size: 16px; }
.bbcode-size-4 { font-size: 20px; }
.bbcode-size-5 { font-size: 26px; }

/* ---------- Styling Reference widget ---------- */

.styling-reference {
    margin-top: 14px;
}

/* When it's nested inside another .card (e.g. right inside the
   Signature panel in the control panel) rather than standing alone
   after one, drop its own background/border/padding so it reads as
   part of that panel instead of a card stacked inside a card. */
.card .styling-reference {
    background: none;
    border: none;
    padding: 0;
    margin-top: 16px;
    margin-bottom: 0;
}

.styling-reference summary {
    cursor: pointer;
    font-weight: 600;
    color: var(--text-primary);
    font-size: 14px;
}

.styling-reference summary:hover {
    color: var(--accent-primary);
}

.styling-reference-body {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-top: 14px;
    padding-top: 14px;
    border-top: 1px solid var(--border-color);
}

.styling-reference-row {
    display: flex;
    align-items: center;
    gap: 14px;
    flex-wrap: wrap;
    font-size: 13px;
}

.styling-reference-row code {
    flex: 0 0 auto;
    min-width: 220px;
    background: var(--bg-input);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    padding: 3px 8px;
    font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
    font-size: 12px;
    color: var(--text-secondary);
}

.styling-reference-row span {
    color: var(--text-secondary);
}

/* ---------- Theme color picker ---------- */
/* Per-field preset swatches (.theme-swatch/.theme-swatches) were
   removed when admin_theme.php moved to whole-theme presets
   (THEME_PRESETS, .theme-preset-btn below) instead — kept as
   individual color-picker/hex-field pairs only. */

.theme-color-inputs {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 16px;
}

.theme-color-inputs input[type="color"] {
    width: 44px;
    height: 38px;
    min-width: 44px;
    padding: 2px;
    background: var(--bg-input);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    cursor: pointer;
}

/* ---------- Icon swatch picker ---------- */

.icon-swatches {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 10px;
}

.icon-swatch {
    width: 38px;
    height: 38px;
    min-width: 38px;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border-color);
    background: var(--bg-input);
    color: var(--text-secondary);
    cursor: pointer;
    font-size: 15px;
}

.icon-swatch:hover {
    border-color: var(--text-primary);
    color: var(--text-primary);
}

.icon-swatch.selected {
    border-color: var(--accent-primary);
    background: rgba(var(--accent-primary-rgb), 0.15);
    color: var(--accent-primary);
}

.theme-color-inputs input[type="text"] {
    margin-bottom: 0;
    font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
    text-transform: lowercase;
}

/* admin_theme.php's preset picker (THEME_PRESETS in includes/theme.php)
   — a grid of buttons rather than a <select>, since each preset shows
   a 3-color swatch strip so an admin can eyeball roughly what they're
   about to fill in before clicking. */
.theme-preset-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
    gap: 10px;
}

.theme-preset-btn {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 14px;
    background: var(--bg-input);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    color: var(--text-primary);
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    text-align: left;
}

.theme-preset-btn:hover {
    border-color: var(--accent-primary);
}

.theme-preset-swatches {
    display: flex;
    flex-shrink: 0;
}

.theme-preset-swatches span {
    display: block;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    border: 1px solid rgba(0, 0, 0, 0.25);
    margin-left: -6px;
}

.theme-preset-swatches span:first-child {
    margin-left: 0;
}

/* admin_theme.php's Advanced/Custom CSS section — same collapsed-by-
   default <details> pattern the Styling Reference widget and the UCP's
   2FA regenerate/disable controls already use, so opening it is a
   deliberate action rather than something visible by default on a
   settings page an admin might just be glancing at. */
.theme-advanced-details {
    margin-top: 24px;
    padding: 20px 22px;
}

.theme-advanced-details summary {
    cursor: pointer;
    font-weight: 600;
    font-size: 15px;
    color: var(--text-primary);
}

.theme-advanced-details textarea {
    width: 100%;
}

.theme-advanced-badge {
    display: inline-block;
    background: rgba(var(--accent-primary-rgb), 0.15);
    color: var(--accent-orange);
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    padding: 2px 8px;
    border-radius: 999px;
    margin-left: 6px;
    vertical-align: middle;
}

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

.form-card {
    max-width: 480px;
}

/* Compact label+input grid for settings pages with lots of small
   numeric/toggle fields (rate limits, escalation/aging hours,
   moderation thresholds, the admin lock toggle, theme colors) — two
   columns (label, control) instead of the old stacked label/input/
   description-paragraph blocks, so a page with a dozen small variables
   reads as a scannable table instead of a long scroll. Meant to keep
   scaling as more admin-configurable variables get added over time,
   rather than each new one costing another three lines of vertical
   space.
   .settings-row uses display:contents so its label+control children
   land directly in the parent .settings-grid's two columns, while
   still letting the markup group them together semantically (and, for
   .settings-hint below, insert a full-width note between two rows
   without breaking the column alignment of the rows around it). */
.settings-grid {
    display: grid;
    grid-template-columns: minmax(170px, 260px) 1fr;
    column-gap: 16px;
    row-gap: 8px;
    align-items: center;
    margin-bottom: 8px;
}

.settings-row {
    display: contents;
}

.settings-grid label {
    margin-bottom: 0;
}

/* A field sitting directly in a .settings-row (not wrapped in
   .settings-control) — e.g. a plain text input or textarea that's the
   row's whole second column — still needs its base margin-bottom
   zeroed out, same reason .settings-control's own inputs do, so
   .settings-grid's own row-gap is the only spacing between rows. */
.settings-row > input,
.settings-row > textarea {
    margin-bottom: 0;
}

.settings-control {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.settings-control input[type="number"],
.settings-control input[type="text"],
.settings-control input[type="password"] {
    width: auto;
    max-width: 140px;
    margin-bottom: 0;
}

.settings-control input[type="color"] {
    width: 44px;
    height: 38px;
    padding: 2px;
    margin-bottom: 0;
    cursor: pointer;
}

.settings-unit {
    font-size: 13px;
    color: var(--text-secondary);
    white-space: nowrap;
}

/* Spans both columns — a short explanatory note sitting under its own
   row, before the next one starts. */
.settings-hint {
    grid-column: 1 / -1;
    font-size: 12px;
    color: var(--text-muted);
    margin: -2px 0 4px;
}

/* A checkbox-driven row (e.g. "Enable rate limiting", "Require a
   password for the Admin Panel") reads better as one full-width line
   than split across the label/control columns. */
.settings-checkbox-row {
    grid-column: 1 / -1;
    display: flex;
    align-items: center;
    gap: 8px;
    font-weight: 600;
}

.settings-checkbox-row input[type="checkbox"] {
    width: auto;
    margin-bottom: 0;
}

.settings-divider {
    grid-column: 1 / -1;
    border: none;
    border-top: 1px solid var(--border-color);
    margin: 6px 0;
}

/* Generic escape hatch for any control that needs to span both columns
   of a .settings-grid instead of sitting in the label/control split
   (e.g. the theme page's swatch-picker rows). */
.settings-full-row {
    grid-column: 1 / -1;
}

/* Inside a .settings-grid the row-gap already provides spacing between
   the color-inputs row and whatever comes next, so the standalone
   16px margin .theme-color-inputs normally carries would just stack
   on top of it. */
.settings-grid .theme-color-inputs {
    margin-bottom: 0;
}

.settings-group-label {
    grid-column: 1 / -1;
    font-size: 12px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--text-muted);
    margin: 10px 0 -2px;
}

.settings-group-label:first-child {
    margin-top: 0;
}

/* Admin Panel password lock (admin/admin_lock.php, and the status line
   + checkbox on admin_settings.php). */
.admin-lock-card {
    max-width: 420px;
}

.admin-lock-error {
    background: rgba(226, 87, 76, 0.12);
    border: 1px solid rgba(226, 87, 76, 0.35);
    border-radius: var(--radius-sm);
    color: var(--accent-red);
    font-size: 13px;
    font-weight: 600;
    padding: 10px 14px;
    margin: -6px 0 18px;
}

.admin-lock-status {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 13px;
    font-weight: 600;
    margin: -6px 0 18px;
}

.admin-lock-status-on {
    color: var(--accent-green);
}

.admin-lock-status-off {
    color: var(--text-muted);
}

.admin-lock-status-warning {
    color: var(--accent-orange);
}

/* Two-factor authentication — ucp.php's status card, ucp_2fa_setup.php,
   2fa_verify.php, ucp_2fa_backup_codes.php. .two-fa-status mirrors
   .admin-lock-status above (same on/off color convention, same
   "readable-at-a-glance" shape) rather than inventing a new one. */
.two-fa-status {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 13px;
    font-weight: 600;
    margin: -6px 0 10px;
}

.two-fa-status-on {
    color: var(--accent-green);
}

.two-fa-status-off {
    color: var(--text-muted);
}

.two-fa-regenerate-details,
.two-fa-disable-details {
    margin-top: 16px;
    padding-top: 14px;
    border-top: 1px solid var(--border-color);
    font-size: 13px;
}

.two-fa-regenerate-details summary,
.two-fa-disable-details summary {
    cursor: pointer;
    font-weight: 600;
    color: var(--text-primary);
}

.two-fa-qr-wrap {
    display: flex;
    justify-content: center;
    background: #fff;
    border-radius: var(--radius);
    padding: 16px;
    margin: 14px 0;
    /* Turnstile-style widgets aside, a Base32/QR code has no dark-mode
       equivalent worth building — every authenticator app's scanner
       expects normal black-on-white contrast, so this is the one place
       in the whole app that deliberately breaks from the dark theme
       rather than trying to invert the QR image itself (which would
       just make it unscannable). */
    max-width: 252px;
}

.two-fa-manual-key {
    font-family: monospace;
    font-size: 16px;
    letter-spacing: 1px;
    background: rgba(var(--accent-primary-rgb), 0.08);
    border-radius: var(--radius);
    padding: 10px 14px;
    display: inline-block;
    word-break: break-all;
}

.two-fa-backup-codes-card {
    max-width: 480px;
}

.two-fa-backup-code-list {
    list-style: none;
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 10px;
    margin: 16px 0;
    padding: 0;
}

.two-fa-backup-code-list li {
    font-family: monospace;
    font-size: 15px;
    letter-spacing: 0.5px;
    background: var(--bg-page);
    border: 1px solid var(--border-color);
    border-radius: var(--radius);
    padding: 8px 10px;
    text-align: center;
}

/* Shown on admin.php/admin_settings.php whenever the admin/UNLOCK
   failsafe file is bypassing an otherwise-active lock — deliberately
   as loud as the escalated-ticket/report styling elsewhere, since this
   state existing longer than it needs to quietly defeats the whole
   point of the lock. */
.admin-lock-bypass-banner {
    background: rgba(242, 153, 74, 0.12);
    border: 1px solid rgba(242, 153, 74, 0.4);
    color: var(--text-primary);
    margin-bottom: 20px;
}

.admin-lock-bypass-banner code {
    background: rgba(var(--accent-primary-rgb), 0.12);
    border-radius: 4px;
    padding: 1px 5px;
}

/* Confirms which forum "New Post" remembered from the page you came
   from, so a preselected dropdown value never goes unnoticed. */
.new-post-context {
    display: flex;
    align-items: center;
    gap: 8px;
    background: rgba(var(--accent-primary-rgb), 0.1);
    border: 1px solid rgba(var(--accent-primary-rgb), 0.3);
    border-radius: var(--radius-sm);
    padding: 10px 14px;
    margin-bottom: 16px;
    font-size: 13px;
    color: var(--text-secondary);
}

.new-post-context i {
    color: var(--accent-primary);
}

.new-post-context strong {
    color: var(--text-primary);
}

/* Paired sections in the control panel (Bio+Signature, Email+Password)
   sit side by side. align-items: stretch makes both columns match the
   height of the taller one; each column is itself a flex column with
   its .card set to flex: 1, so the card's box actually grows to fill
   that height rather than just the column div around it. Stacks back
   to one column on narrow screens. */
.ucp-columns {
    display: flex;
    align-items: stretch;
    gap: 20px;
}

.ucp-columns > div {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-width: 0;
}

/* Signature gets more of the row than Bio — same 2:3 split as before,
   just now paired with equal height instead of equal width. */
.ucp-column-bio {
    flex: 2;
}

.ucp-column-signature {
    flex: 3;
}

.ucp-columns .form-card {
    max-width: none;
    flex: 1;
    display: flex;
    flex-direction: column;
}

@media (max-width: 700px) {
    .ucp-columns {
        flex-direction: column;
    }
}

label {
    display: block;
    font-size: 13px;
    font-weight: 600;
    color: var(--text-secondary);
    margin-bottom: 6px;
}

input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"],
textarea {
    width: 100%;
    background: var(--bg-input);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    color: var(--text-primary);
    padding: 10px 12px;
    font-family: inherit;
    font-size: 14px;
    margin-bottom: 16px;
}

input:focus,
textarea:focus {
    outline: none;
    border-color: var(--accent-blue);
}

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

button,
.btn {
    display: inline-block;
    background: var(--bg-card-hover);
    color: var(--text-primary);
    border: 1px solid var(--border-color);
    border-radius: 999px;
    padding: 9px 20px;
    font-family: inherit;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
}

button:hover,
.btn:hover {
    background: #333e53;
    text-decoration: none;
}

form button[type="submit"] {
    background: var(--accent-primary);
    color: #10160f;
    border: none;
}

form button[type="submit"]:hover {
    background: var(--accent-primary-hover);
}

.btn-danger,
form button[type="submit"].btn-danger {
    background: rgba(226, 87, 76, 0.15);
    color: var(--accent-red);
    border: 1px solid rgba(226, 87, 76, 0.3);
}

.btn-danger:hover,
form button[type="submit"].btn-danger:hover {
    background: rgba(226, 87, 76, 0.25);
}

/* ---------- Reactions (view_post.php) ----------
   Lives inside .post-footer-row, right-aligned via margin-left: auto —
   see that rule's comment above for why (used to be its own stacked
   row, which read as competing with the action buttons next to it).
   Deliberately plain buttons rather than a popup like the reputation
   widget — a like/dislike is a one-click action with nothing else to
   configure (no comment field, no history to browse), so the extra
   popup machinery would just be friction here. */
.reaction-bar {
    margin-left: auto;
}

.reaction-form {
    display: flex;
    gap: 8px;
}

.reaction-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: var(--bg-input);
    color: var(--text-secondary);
    border: 1px solid var(--border-color);
    border-radius: 999px;
    padding: 5px 12px;
    font-family: inherit;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

.reaction-btn:hover {
    background: var(--bg-card-hover);
    color: var(--text-primary);
}

.reaction-btn-like.active {
    background: rgba(var(--accent-primary-rgb), 0.15);
    color: var(--accent-primary);
    border-color: rgba(var(--accent-primary-rgb), 0.4);
}

.reaction-btn-dislike.active {
    background: rgba(226, 87, 76, 0.15);
    color: var(--accent-red);
    border-color: rgba(226, 87, 76, 0.3);
}

/* Read-only version — shown instead of the clickable buttons for the
   content's own author (can't react to your own post/comment, same
   rule as reputation) and for logged-out visitors, so the counts are
   still visible to everyone even when voting isn't. Styled as the same
   pill shape as .reaction-btn (background/border/radius/padding all
   match) rather than plain text, so a post you own and a comment you
   don't own show visually consistent reaction pills instead of one
   looking like a real control and the other looking unstyled — the
   only difference is no cursor/hover, since there's nothing to click. */
.reaction-counts {
    display: flex;
    gap: 8px;
}

.reaction-count-static {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: var(--bg-input);
    color: var(--text-muted);
    border: 1px solid var(--border-color);
    border-radius: 999px;
    padding: 5px 12px;
    font-size: 13px;
    font-weight: 600;
}

/* ---------- Poll builder (create_post.php) ----------
   Same collapsible-optional-section language as .styling-reference —
   native <details>, no JS needed to open/close it, consistent with
   every other optional extra section in this app's forms. */
.poll-builder {
    margin-top: 16px;
}

.poll-builder summary {
    cursor: pointer;
    font-weight: 600;
    color: var(--text-primary);
    font-size: 14px;
}

.poll-builder summary:hover {
    color: var(--accent-primary);
}

/* Gift Coins — the collapsible form on profile.php that sends a
   coin_gifts row via handle_gift_coins.php. Same native
   <details>/<summary>, no-JS-needed shape as .styling-reference/
   .poll-builder above, kept out of the "card" class entirely (unlike
   .styling-reference's standalone case) since this one always sits
   nested inside profile.php's outer .card — stacking a second
   bordered/padded card inside that would double up the border look
   .card .styling-reference has to specifically undo. */
.gift-coins-details {
    margin-top: 14px;
}

.gift-coins-details summary {
    cursor: pointer;
    font-weight: 600;
    color: var(--text-primary);
    font-size: 14px;
}

.gift-coins-details summary:hover {
    color: var(--accent-primary);
}

.gift-coins-form {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin-top: 12px;
    max-width: 320px;
}

.poll-builder-body {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin-top: 14px;
    padding-top: 14px;
    border-top: 1px solid var(--border-color);
}

.poll-options-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-bottom: 10px;
}

.poll-option-input {
    margin-bottom: 0;
}

#pollAddOptionBtn {
    align-self: flex-start;
    margin-bottom: 16px;
}

.poll-builder-checkbox-row {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 13px;
    color: var(--text-secondary);
    margin-bottom: 14px;
}

.poll-builder-checkbox-row input {
    width: auto;
    margin: 0;
}

.poll-builder-note {
    font-size: 12px;
    color: var(--text-muted);
    margin: 4px 0 0;
}

/* ---------- Poll widget (view_post.php) ---------- */

.poll-widget {
    margin: 14px 0;
}

.poll-question {
    margin: 0 0 14px;
    font-size: 15px;
    font-weight: 700;
    color: var(--text-primary);
}

.poll-vote-form {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.poll-option-choice {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 14px;
    color: var(--text-secondary);
    cursor: pointer;
}

.poll-option-choice input {
    width: auto;
    margin: 0;
}

.poll-vote-form button {
    align-self: flex-start;
    margin-top: 4px;
}

.poll-results {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.poll-result-row {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.poll-result-label {
    display: flex;
    justify-content: space-between;
    gap: 10px;
    font-size: 13px;
    color: var(--text-secondary);
}

.poll-result-check {
    color: var(--accent-primary);
    margin-left: 4px;
}

.poll-result-bar-track {
    height: 8px;
    background: var(--bg-input);
    border: 1px solid var(--border-color);
    border-radius: 999px;
    overflow: hidden;
}

.poll-result-bar-fill {
    height: 100%;
    background: rgba(var(--accent-primary-rgb), 0.5);
    border-radius: 999px;
}

.poll-result-bar-fill-chosen {
    background: var(--accent-primary);
}

.poll-meta {
    margin: 12px 0 0;
    font-size: 12px;
    color: var(--text-muted);
}

/* ---------- Stats & online users ---------- */

.stats-grid {
    display: flex;
    gap: 14px;
    flex-wrap: wrap;
    margin-bottom: 14px;
}

.stat-card {
    flex: 1;
    min-width: 140px;
}

.stat-number {
    font-size: 30px;
    font-weight: 800;
    color: var(--text-primary);
    line-height: 1.2;
}

.stat-label {
    font-size: 13px;
    color: var(--text-muted);
}

.status-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    margin-right: 8px;
}

.online-user-row {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 6px 0;
    font-size: 14px;
}

/* ---------- Avatars ---------- */

.avatar {
    display: block;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
    box-sizing: border-box;
}

.avatar-fallback {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--bg-card-hover);
    color: var(--text-secondary);
    font-weight: 700;
    text-align: center;
}

/* ---------- Avatar frames (includes/avatar_frame.php) ----------
   Each class reads --avatar-frame-color/-2 and --avatar-frame-width,
   custom properties renderAvatar() sets inline per element (see
   avatarFrameAttributes()) rather than baking a fixed color into the
   class itself — one class per STYLE, reused by every frame option
   that picks it, however many colors an admin has created. .avatar
   already has box-sizing: border-box (see above), so .avatar-frame-ring/
   -dashed sizing via `border` shrinks to fit the same way the old plain
   ring did; the others use box-shadow instead so a double/two-tone ring
   can draw outside the image without needing a second element. */

.avatar-frame-ring {
    border: var(--avatar-frame-width, 2px) solid var(--avatar-frame-color, transparent);
}

.avatar-frame-dashed {
    border: var(--avatar-frame-width, 2px) dashed var(--avatar-frame-color, transparent);
}

.avatar-frame-double-ring {
    box-shadow:
        0 0 0 var(--avatar-frame-width, 2px) var(--bg-card),
        0 0 0 calc(var(--avatar-frame-width, 2px) * 2) var(--avatar-frame-color, transparent);
}

.avatar-frame-two-tone {
    box-shadow:
        0 0 0 var(--avatar-frame-width, 2px) var(--avatar-frame-color, transparent),
        0 0 0 calc(var(--avatar-frame-width, 2px) * 2) var(--avatar-frame-color-2, var(--avatar-frame-color, transparent));
}

.avatar-frame-glow-pulse {
    box-shadow: 0 0 0 var(--avatar-frame-width, 2px) var(--avatar-frame-color, transparent);
    animation: avatar-frame-pulse 1.8s ease-in-out infinite;
}

@keyframes avatar-frame-pulse {
    0%, 100% {
        box-shadow:
            0 0 0 var(--avatar-frame-width, 2px) var(--avatar-frame-color, transparent),
            0 0 6px 0 var(--avatar-frame-color, transparent);
    }
    50% {
        box-shadow:
            0 0 0 var(--avatar-frame-width, 2px) var(--avatar-frame-color, transparent),
            0 0 16px 4px var(--avatar-frame-color, transparent);
    }
}

@media (prefers-reduced-motion: reduce) {
    .avatar-frame-glow-pulse {
        animation: none !important;
        /* Freezes on the same static ring .avatar-frame-ring uses
           rather than the pulse's mid-animation glow, same "fall back
           to a plain non-moving version" rule the username animations
           use above. */
        box-shadow: 0 0 0 var(--avatar-frame-width, 2px) var(--avatar-frame-color, transparent);
    }
}

.online-user-row .avatar {
    margin-right: 2px;
}

a.post-user-name {
    display: block;
    text-decoration: none;
}

a.post-user-name:hover {
    text-decoration: underline;
}

.online-user-row + .online-user-row {
    border-top: 1px solid var(--border-color);
}

#pageContent {
    transition: opacity 0.12s ease;
}

#pageContent.pjax-loading {
    opacity: 0.4;
}

/* ---------- Blog breadcrumb (Blog / Category / Forum) ---------- */

.blog-breadcrumb {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 14px;
    color: var(--text-secondary);
    margin: -6px 0 16px;
}

.blog-breadcrumb a {
    color: var(--text-secondary);
}

.blog-breadcrumb a:hover {
    color: var(--accent-primary);
}

.blog-breadcrumb-sep {
    color: var(--text-muted);
}

.blog-breadcrumb > span:last-child {
    color: var(--text-primary);
    font-weight: 600;
}

/* ---------- Forum info rows ---------- */
/* A classic forum-index row — icon, name + description, a post-count
   column, and a "last post" summary column — rather than a plain
   icon grid, so a forum communicates what it's for and how active
   it is at a glance instead of just being a clickable folder icon. */

.forum-list {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-bottom: 20px;
}

/* A sub-category header sitting above the forums filed under it —
   deliberately quieter than h1/h2 (no bold color, just a thin accent
   rule) since it's a grouping label, not a page heading. */
.forum-group-header {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 13px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
    margin: 22px 0 10px;
}

.forum-group-header:first-child {
    margin-top: 0;
}

.forum-group-header::after {
    content: '';
    flex: 1;
    height: 1px;
    background: linear-gradient(to right, rgba(var(--accent-primary-rgb), 0.4), transparent);
}

/* A curtain-drop reveal for the whole forum listing — every group
   header, row, and the "not in a forum yet" link — as ONE continuous
   sweep rather than each row animating on its own. clip-path unrolls
   the entire .forum-listing wrapper from fully hidden (clipped away at
   the bottom, so it doesn't distort layout the way height/scale would)
   down to fully shown, like a single curtain falling over the whole
   section at once. Since pjax swaps this markup in fresh on every
   category click, it's a brand-new DOM node each time, so the
   animation just naturally replays on its own — no JS needed. */
@keyframes forum-curtain-drop {
    from {
        clip-path: inset(0 0 100% 0);
        opacity: 0;
    }
    to {
        clip-path: inset(0 0 0% 0);
        opacity: 1;
    }
}

.forum-listing {
    animation: forum-curtain-drop 0.9s cubic-bezier(0.16, 1, 0.3, 1) both;
}

.forum-row {
    display: flex;
    align-items: center;
    gap: 18px;
    background: var(--bg-card);
    border: 1px solid var(--border-color);
    border-radius: var(--radius);
    padding: 16px 20px;
    transition: background 0.15s ease, border-color 0.15s ease;
}

.forum-row:hover {
    background: var(--bg-card-hover);
    border-color: rgba(var(--accent-primary-rgb), 0.4);
}

/* Pinned/sticky threads (posts.php's flat listing, view_post.php's own
   badge) — same accent left-border language `.nav-item.active` already
   uses elsewhere in the sidebar, reused here so "this is pinned" reads
   as a consistent visual signal across the app rather than inventing a
   new one just for this. A little brighter on hover than a regular
   .forum-row so the pinned block still feels distinct even mid-hover. */
.post-row-pinned {
    border-left: 3px solid var(--accent-primary);
    background: rgba(var(--accent-primary-rgb), 0.05);
}

.post-row-pinned:hover {
    background: rgba(var(--accent-primary-rgb), 0.1);
}

.post-row-pin-icon {
    color: var(--accent-primary);
    font-size: 13px;
}

/* Poll indicator on posts.php's thread rows (both the pinned block and
   the regular paginated list) — same small-inline-icon treatment as
   the pin icon above, just a different color so the two never get
   confused for each other when a thread happens to be both. */
.post-row-poll-icon {
    color: var(--accent-blue);
    font-size: 13px;
}

/* A second stat block next to the existing comment-count one — reuses
   .forum-row-stats' column layout via the same base class, but holds
   two compact icon+number rows (like, dislike) instead of one big
   number-over-label pair, since neither reaction count needs the room
   a "99 comments" label does. Sits to the left of the comment count
   (registered before it in the DOM) so the reading order is
   "engagement, then comments" left to right. */
.forum-row-stats-reactions {
    width: 52px;
    gap: 2px;
}

.forum-row-reaction {
    font-size: 12px;
    font-weight: 600;
    color: var(--text-muted);
    display: flex;
    align-items: center;
    gap: 4px;
}

.forum-row-reaction i {
    font-size: 11px;
}

.pinned-group-header i {
    color: var(--accent-primary);
}

.forum-row-icon {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 46px;
    height: 46px;
    border-radius: 50%;
    background: rgba(var(--accent-primary-rgb), 0.12);
    color: var(--accent-primary);
    font-size: 18px;
    text-decoration: none;
}

.forum-row-icon:hover {
    background: rgba(var(--accent-primary-rgb), 0.22);
    text-decoration: none;
}

.forum-row-main {
    flex: 1;
    min-width: 0;
}

.forum-row-name {
    display: block;
    font-weight: 600;
    font-size: 16px;
    color: var(--text-primary);
    text-decoration: none;
}

.forum-row-name:hover {
    color: var(--accent-primary);
    text-decoration: none;
}

.forum-row-desc {
    margin: 4px 0 0;
    font-size: 13px;
    color: var(--text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.forum-row-stats {
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    width: 64px;
    text-align: center;
}

.forum-row-stat-number {
    font-size: 18px;
    font-weight: 700;
    color: var(--text-primary);
}

.forum-row-stat-label {
    font-size: 11px;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

.forum-row-lastpost {
    flex-shrink: 0;
    width: 220px;
    padding-left: 18px;
    border-left: 1px solid var(--border-color);
    font-size: 13px;
}

.forum-row-lastpost-title {
    display: block;
    color: var(--text-primary);
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    text-decoration: none;
}

.forum-row-lastpost-title:hover {
    color: var(--accent-primary);
    text-decoration: none;
}

.forum-row-lastpost-meta {
    margin: 4px 0 0;
    color: var(--text-secondary);
    line-height: 1.4;
}

.forum-row-lastpost-empty {
    color: var(--text-muted);
    font-size: 13px;
}

/* ---------- Homepage dashboard ---------- */

.home-tagline {
    margin: 4px 0 0;
    color: var(--text-secondary);
}

/* Two-column dashboard: a narrow, fixed-width sidebar (welcome card +
   community stats + who's online) alongside a wide main column (recent
   posts) with room to grow — more widgets can be added to either
   column later without needing another layout pass. */
.home-layout {
    display: grid;
    grid-template-columns: 280px 1fr;
    align-items: start;
    gap: 20px;
}

/* Deliberately plain block flow, not a flex column with its own gap —
   every .card already carries its own margin-bottom: 14px (see the
   base .card rule), so a flex gap on top of that would just double up
   the spacing between cards. Relying on the cards' own margin here
   (instead of a second spacing mechanism) is also what makes the
   top-alignment trick below exact rather than a pixel guess: it puts
   the sidebar and the main column's "space before the first real box"
   on the *same* mechanism — plain margin-bottom collapsing against the
   next element's margin-top — so the two totals are structurally
   guaranteed to match at any font size, zoom level, or browser, not
   just coincidentally equal at one. */
.home-sidebar {
    min-width: 0;
}

.home-main {
    min-width: 0;
}

/* The sidebar's first real box (.home-user-card) needs to start at the
   same height as the main column's first real box (the first
   .forum-row post), which sits below its own "Recent Posts" label. An
   invisible label-shaped spacer — same .home-panel-title class, so its
   box (font-size, line-height, margin-bottom) is byte-for-byte
   identical to the real label's, not just visually close — reserves
   that exact same space above the user card. No pixel math: both sides
   now end in the same state (a .home-panel-title's 12px margin-bottom
   collapsing against a plain element with no margin-top), so they
   match natively regardless of resolution or browser. */
.home-panel-title-spacer {
    visibility: hidden;
}

.home-panel-title {
    margin: 0 0 12px;
    font-size: 13px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--text-muted);
}

.home-user-card-top {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 14px;
}

.home-user-card-identity {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
}

.home-user-card-name {
    font-weight: 700;
    font-size: 15px;
    color: var(--text-primary);
    text-decoration: none;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.home-user-card-name:hover {
    color: var(--accent-primary);
    text-decoration: none;
}

/* Same big-number-tile-in-a-row look as the original full-size
   .stats-grid/.stat-card on the old homepage, just scaled down to fit
   the narrower 280px sidebar column — used for both the user's own
   stats and the community totals, so the two cards read as the same
   family of widget. */
.home-stat-tiles {
    display: flex;
    gap: 8px;
}

.home-stat-tile {
    flex: 1;
    min-width: 0;
    text-align: center;
    background: var(--bg-input);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    padding: 10px 4px;
}

.home-stat-tile-number {
    font-size: 19px;
    font-weight: 800;
    color: var(--text-primary);
    line-height: 1.2;
}

.home-stat-tile-label {
    font-size: 10px;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.02em;
    margin-top: 2px;
}

.home-user-card .home-user-login {
    margin-top: 12px;
}

.home-user-login-detail {
    margin: 4px 0 0;
    font-size: 12px;
    color: var(--text-secondary);
    line-height: 1.5;
}

.home-user-login-ip {
    margin-left: 4px;
    padding: 1px 6px;
    border-radius: 4px;
    background: var(--bg-input);
    color: var(--text-muted);
    font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
    font-size: 11px;
}

/* Caps how tall the online list can grow before it starts scrolling,
   so a busy site can't stretch the sidebar column (and drag the
   overall page height) far past the main column's own content. */
.home-online-list {
    max-height: 260px;
    overflow-y: auto;
}

@media (max-width: 900px) {
    .home-layout {
        grid-template-columns: 1fr;
    }
}

/* Same capped-height-then-scroll treatment as .home-online-list, for
   the Coin Shop feed card just above Online Now in the sidebar. */
.shop-feed-list {
    /* Capped at 4 items (see index.php's getRecentShopAdditions() call)
       specifically so this never needs to scroll — no max-height/
       overflow-y here on purpose. */
}

/* Whole-row link (unlike .online-user-row, which is a plain div with
   an <a> just around the username) — every row here goes to the same
   place (a specific store.php section), so there's no reason to make
   only the name clickable. Two stacked lines rather than a single row
   of fixed-width columns: the actual preview markup reused from
   store.php's own catalog cards (badgeIconHtml()/
   avatarFrameOptionPreviewHtml()/usernameStyleOptionPreviewHtml())
   comes in three very differently-shaped forms — an icon, an avatar
   swatch + style label, or a colored/animated text label — so rather
   than fighting to cram all three into one fixed-size icon slot, the
   preview just renders at its own natural size on its own subtitle
   line under the name. */
.shop-feed-row {
    display: block;
    padding: 6px 0;
    color: inherit;
    text-decoration: none;
}

/* Explicit :hover override, not just the resting-state rule above —
   the sitewide `a:hover { text-decoration: underline; }` (see top of
   this file) has higher specificity than a bare .shop-feed-row
   selector, so without this it wins on hover and underlines the whole
   two-line row (name, cost pill, preview, label and all) — a dated
   whole-block-underline look, not the subtle color-shift hover this
   site otherwise uses. The name already gets its own accent-color
   hover feedback just below. */
.shop-feed-row:hover {
    text-decoration: none;
}

.shop-feed-row:hover .shop-feed-name {
    color: var(--accent-primary);
}

.shop-feed-name-line {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}

.shop-feed-name {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 13px;
    font-weight: 600;
    color: var(--text-primary);
}

.shop-feed-cost {
    flex-shrink: 0;
    font-size: 12px;
    font-weight: 700;
    color: var(--text-secondary);
    background: var(--bg-input);
    border-radius: var(--radius-sm);
    padding: 2px 7px;
}

.shop-feed-preview-line {
    display: flex;
    align-items: center;
    gap: 4px;
    margin-top: 3px;
    font-size: 12px;
}

.shop-feed-preview {
    display: inline-flex;
    align-items: center;
}

.shop-feed-label {
    color: var(--text-muted);
}

.shop-feed-visit {
    margin: 10px 0 0;
    font-size: 13px;
}

/* Full-width, below the two-column .home-layout rather than squeezed
   into the narrow 280px sidebar — four leaderboard categories at 5 rows
   each need more horizontal room than the sidebar column has to spare.
   Same 4-across-collapsing-down responsive shape as most grids in this
   file, bottoming out at one column per row on narrow screens. */
.home-leaderboard-section {
    margin-top: 20px;
}

.home-leaderboard-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 14px;
}

.leaderboard-card {
    min-width: 0;
}

.leaderboard-card-title {
    margin: 0 0 10px;
    font-size: 13px;
    font-weight: 700;
    color: var(--text-secondary);
    display: flex;
    align-items: center;
    gap: 6px;
}

.leaderboard-card-title i {
    color: var(--accent-primary);
}

.leaderboard-empty {
    margin: 0;
    font-size: 13px;
    color: var(--text-muted);
}

/* Same avatar + name + right-aligned-value shape as .online-user-row,
   just with a rank number prepended and the value pinned to the far
   right via margin-left: auto rather than trailing inline — a
   leaderboard reads top-to-bottom by rank, so the rank needs to be the
   first thing the eye hits on each row. */
.leaderboard-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 0;
}

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

.leaderboard-rank {
    flex-shrink: 0;
    width: 20px;
    font-size: 12px;
    font-weight: 700;
    color: var(--text-muted);
}

.leaderboard-name {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: 600;
    font-size: 13px;
    text-decoration: none;
}

.leaderboard-name:hover {
    text-decoration: underline;
}

.leaderboard-score {
    flex-shrink: 0;
    margin-left: auto;
    font-size: 12px;
    font-weight: 700;
    color: var(--text-secondary);
    background: var(--bg-input);
    border-radius: var(--radius-sm);
    padding: 2px 7px;
}

@media (max-width: 900px) {
    .home-leaderboard-grid {
        grid-template-columns: repeat(2, 1fr);
    }
}

@media (max-width: 480px) {
    .home-leaderboard-grid {
        grid-template-columns: 1fr;
    }
}

/* ---------- Category tabs ---------- */

.category-tabs {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin-bottom: 16px;
}

/* Lets a conic-gradient's own start angle (and, below, its brightness)
   be animated directly, rather than animating a whole element — which
   is what makes the border tracer follow the pill's actual
   rounded/oval outline, and lets it fade out on its own without
   touching the pill's text or fill underneath. */
@property --tab-race-angle {
    syntax: '<angle>';
    inherits: false;
    initial-value: 0deg;
}

@property --tab-race-fade {
    syntax: '<percentage>';
    inherits: false;
    initial-value: 100%;
}

.category-tab {
    display: inline-block;
    padding: 7px 17px;
    border-radius: 999px;
    background: var(--bg-card);
    color: var(--text-secondary);
    font-size: 14px;
    font-weight: 600;
    border: 2px solid var(--border-color);
    text-decoration: none;
    transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.category-tab:hover {
    background: var(--bg-card-hover);
    color: var(--text-primary);
    text-decoration: none;
    border-color: rgba(var(--accent-primary-rgb), 0.4);
}

/* The border carries the tracer via three stacked background layers
   (topmost first): an opaque interior fill (padding-box — MUST be
   fully opaque, since any alpha there would let the conic-gradient
   layer below show through the whole face instead of staying confined
   to the border strip, which is what caused the earlier "radar sweep"
   look), the racing comet itself (border-box conic-gradient, only
   visible in the 2px ring the layer above doesn't cover), and a plain
   solid ring color underneath that (also border-box) as the resting
   state. Because the ring is the element's real border rather than a
   separately-sized overlay, it always traces the pill's exact
   rounded/oval edge at any width.
   Two animated custom properties drive it: --tab-race-angle spins the
   comet two laps around, and --tab-race-fade — mixed into each of the
   comet's color stops via color-mix() — fades the comet from fully
   visible to fully transparent over the animation's final stretch.
   Once it's gone, the plain solid ring layer underneath is all that's
   left showing, so the tracer eases away into a normal highlighted
   border instead of snapping to a frozen comet shape when the
   animation ends. animation-fill-mode: forwards keeps that faded,
   settled end state rather than reverting to the (fully lit) initial
   keyframe values. */
.category-tab.active {
    --tab-active-fill: color-mix(in srgb, rgb(var(--accent-primary-rgb)) 15%, var(--bg-card));
    color: var(--accent-primary);
    border-color: transparent;
    background:
        linear-gradient(var(--tab-active-fill), var(--tab-active-fill)) padding-box,
        conic-gradient(
            from var(--tab-race-angle),
            transparent 0%,
            transparent 78%,
            color-mix(in srgb, rgba(var(--accent-primary-rgb), 0.15) var(--tab-race-fade), transparent) 86%,
            color-mix(in srgb, rgba(var(--accent-primary-rgb), 0.95) var(--tab-race-fade), transparent) 96%,
            color-mix(in srgb, #ffffff var(--tab-race-fade), transparent) 99%,
            transparent 100%
        ) border-box,
        linear-gradient(rgba(var(--accent-primary-rgb), 0.6), rgba(var(--accent-primary-rgb), 0.6)) border-box;
    animation: category-tab-race 4s linear forwards;
}

/* --tab-race-angle is only given values at 0% and 100% (nothing at the
   55% checkpoint below), so it interpolates smoothly across the WHOLE
   duration at one constant speed — the comet never stops moving, even
   while --tab-race-fade and the glow are winding down over the last
   45%. Giving --tab-race-angle its own value at every checkpoint (as
   the fade properties do) is what previously pinned it in place for
   that last stretch, so the comet visibly stopped dead before fading
   instead of fading while still in motion. */
@keyframes category-tab-race {
    0% {
        --tab-race-angle: 0deg;
        --tab-race-fade: 100%;
        filter: drop-shadow(0 0 4px rgba(var(--accent-primary-rgb), 0.65));
    }
    55% {
        --tab-race-fade: 100%;
        filter: drop-shadow(0 0 4px rgba(var(--accent-primary-rgb), 0.65));
    }
    100% {
        --tab-race-angle: 450deg;
        --tab-race-fade: 0%;
        filter: drop-shadow(0 0 0 rgba(var(--accent-primary-rgb), 0));
    }
}

/* Admin dashboard's "Manage" section (admin.php only) — one row of
   category tabs; clicking one drops that category's link list open in
   a single shared panel directly beneath the tabs, instead of showing
   every category's links at once (the old always-visible card grid)
   or opening them in a separate floating dropdown (what every other
   admin page's includes/admin_nav.php still does) — either of which
   would just show the same 15 links twice on one screen here, since
   this panel already doubles as this page's only nav. Reuses
   .category-tabs/.category-tab for the row/triggers, same as the
   shared admin nav and posts.php's Category picker — this block has
   to come after that section (not just after .dash-manage's own
   earlier position) so its overrides (margin-bottom: 0, display:
   inline-flex, etc.) actually win the cascade against .category-tabs/
   .category-tab's own conflicting values instead of losing to them by
   sheer source order. */
.dash-manage {
    margin-bottom: 20px;
}

.dash-tabs {
    margin-bottom: 0;
    position: relative;
    z-index: 2;
}

.dash-tab {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    cursor: pointer;
    font: inherit;
}

.dash-tab-caret {
    font-size: 10px;
    opacity: 0.65;
    transition: transform 0.15s ease;
}

.dash-tab.active .dash-tab-caret {
    transform: rotate(180deg);
}

/* The open tab keeps a flat, "already settled" highlighted look — the
   same fill/border/text color .category-tab.active normally eases
   into once its own racing-border animation finishes — but never runs
   that animation itself. The animated tracer now runs once around the
   panel it opens instead (see .dash-manage.open .dash-panel-inner
   below), which actually traces that element's own real border rather
   than a separate shape drawn to approximate it.
   Its bottom border is dropped entirely (transparent, not just
   flattened corners) and it overlaps the panel by 3px — combined with
   the z-index above, the tab's own flat fill physically paints over
   the panel's top edge in that stretch, so there's no border line at
   all where the two meet, just one fill running into the other. */
.dash-tab.active {
    animation: none;
    background: color-mix(in srgb, rgb(var(--accent-primary-rgb)) 15%, var(--bg-card));
    border-color: rgba(var(--accent-primary-rgb), 0.6);
    border-bottom-color: transparent;
    color: var(--accent-primary);
    border-bottom-left-radius: 0;
    border-bottom-right-radius: 0;
    margin-bottom: -3px;
}

/* Height-animates via the grid-template-rows 0fr -> 1fr trick instead
   of max-height (which needs a guessed-too-large fixed value and so
   never times its transition evenly against the content's real
   height) — this settles smoothly open/closed no matter how many
   links a given category's panel holds. A slower, eased-both-ends
   curve (rather than plain "ease") reads as a proper drop-down/pull-up
   instead of a mechanical linear-ish resize. */
.dash-panel-track {
    position: relative;
    z-index: 1;
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows 0.32s cubic-bezier(0.4, 0, 0.2, 1);
}

.dash-manage.open .dash-panel-track {
    grid-template-rows: 1fr;
}

.dash-panel-body {
    overflow: hidden;
    min-height: 0;
}

/* Fades and lifts the panel's own content alongside the height
   transition above, instead of the text just snapping into full view
   the instant the grid track has any height at all — softens the
   drop-down further since the content arrives gradually rather than
   being fully rendered but clipped/unclipped by the growing box. */
.dash-panel-inner {
    background: var(--bg-card);
    border: 1px solid var(--border-color);
    border-radius: 0 0 var(--radius) var(--radius);
    padding: 18px 20px 4px;
    opacity: 0;
    transform: translateY(-6px);
    transition: opacity 0.22s ease, transform 0.22s ease;
}

.dash-manage.open .dash-panel-inner {
    opacity: 1;
    transform: translateY(0);
}

/* Same animated "racing border" comet .category-tab.active runs around
   a single pill (see that rule's own comment for how the layered-
   background technique works), applied directly to this element's own
   real border instead of a separate pseudo-element shape — so the
   tracer actually hugs this panel's real (flat-top, rounded-bottom)
   outline exactly, rather than approximating it with an independently
   drawn shape that doesn't quite match. Runs once whenever a category
   opens, not continuously. */
.dash-manage.open .dash-panel-inner {
    border: 2px solid transparent;
    background:
        linear-gradient(var(--bg-card), var(--bg-card)) padding-box,
        conic-gradient(
            from var(--tab-race-angle),
            transparent 0%,
            transparent 78%,
            color-mix(in srgb, rgba(var(--accent-primary-rgb), 0.15) var(--tab-race-fade), transparent) 86%,
            color-mix(in srgb, rgba(var(--accent-primary-rgb), 0.95) var(--tab-race-fade), transparent) 96%,
            color-mix(in srgb, #ffffff var(--tab-race-fade), transparent) 99%,
            transparent 100%
        ) border-box,
        linear-gradient(rgba(var(--accent-primary-rgb), 0.6), rgba(var(--accent-primary-rgb), 0.6)) border-box;
    animation: category-tab-race 4s linear forwards;
}

.dash-panel-inner p:last-child {
    margin-bottom: 14px;
}

/* ---------- Pagination ---------- */

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

.pagination-ellipsis {
    color: var(--text-muted);
    padding: 0 4px;
}

.btn.active-page {
    background: var(--accent-primary);
    color: #10160f;
    border-color: var(--accent-primary);
}

/* Placed after the generic button hover rule above so it wins the
   cascade at equal specificity — a plain .btn-accent:hover declared
   earlier in the file would otherwise lose to button:hover / .btn:hover. */
.btn-accent {
    background: var(--accent-primary);
    color: #10160f;
    border-color: var(--accent-primary);
}

.btn-accent:hover {
    background: var(--accent-primary-hover);
    border-color: var(--accent-primary-hover);
}

.pagination-jump {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-left: auto;
}

.pagination-jump label {
    margin-bottom: 0;
}

.pagination-jump input[type="number"] {
    width: 70px;
    margin-bottom: 0;
}

/* ---------- Notifications page ---------- */
/* Same row-as-a-linked-card idea as .conversation-row below, just with
   a plain unread dot instead of a count badge (there's nothing to
   count — a notification is either read or not) — and the whole list
   arrives already all-marked-read (see notifications.php), so
   .notif-row-unread only ever reflects the one-render snapshot of
   what was unread when the page loaded. */
.notif-row {
    display: flex;
    align-items: center;
    gap: 14px;
    color: inherit;
    text-decoration: none;
    transition: background 0.15s ease, border-color 0.15s ease;
}

.notif-row:hover {
    background: var(--bg-card-hover);
    text-decoration: none;
}

.notif-row-unread {
    border-color: rgba(var(--accent-primary-rgb), 0.5);
}

.notif-row-main {
    flex: 1;
    min-width: 0;
}

.notif-row-text {
    margin: 0 0 2px;
    font-size: 14px;
    color: var(--text-secondary);
}

.notif-row-text strong {
    color: var(--text-primary);
}

.notif-row-dot {
    flex-shrink: 0;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--accent-primary);
}

/* A comment search result on search.php — a .card made clickable as a
   whole (linking straight to the parent post, since there's no
   standalone comment page to send it to), same "reset the <a>'s own
   color/underline so it reads as a plain card, not a link" idea as
   .conversation-row just below. */
.search-comment-row {
    display: block;
    color: inherit;
    text-decoration: none;
}

.search-comment-row:hover {
    text-decoration: none;
    border-color: rgba(var(--accent-primary-rgb), 0.4);
}

/* ---------- Messaging ---------- */

.conversation-row {
    display: flex;
    align-items: center;
    gap: 14px;
    color: inherit;
    text-decoration: none;
    transition: background 0.15s ease, border-color 0.15s ease;
}

.conversation-row:hover {
    background: var(--bg-card-hover);
    text-decoration: none;
}

.conversation-unread {
    border-color: rgba(var(--accent-primary-rgb), 0.5);
}

.conversation-row-main {
    flex: 1;
    min-width: 0;
}

.conversation-row-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
}

.conversation-unread .post-user-name {
    color: var(--text-primary);
}

.conversation-row-preview {
    margin: 2px 0 0;
    color: var(--text-secondary);
    font-size: 14px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.conversation-unread .conversation-row-preview {
    color: var(--text-primary);
    font-weight: 600;
}

.thread-heading {
    display: flex;
    align-items: center;
    gap: 10px;
}

.message-thread {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-bottom: 20px;
}

.message-bubble-row {
    display: flex;
    justify-content: flex-start;
}

.message-bubble-row-own {
    justify-content: flex-end;
}

.message-bubble {
    max-width: 65%;
    background: var(--bg-card);
    border: 1px solid var(--border-color);
    border-radius: var(--radius);
    padding: 10px 14px;
}

.message-bubble-own {
    background: rgba(var(--accent-primary-rgb), 0.15);
    border-color: rgba(var(--accent-primary-rgb), 0.4);
}

.message-bubble-body {
    font-size: 14px;
    word-wrap: break-word;
}

.message-bubble-body p {
    margin: 0;
}

.message-bubble-meta {
    margin-top: 6px;
    font-size: 11px;
    color: var(--text-muted);
}

/* Only used by ticket.php — a two-person message_thread.php bubble
   never needs an author label (it's always "me" vs. "the one other
   person," visually distinguished by side alone), but a ticket can have
   the submitter plus any number of different staff replying, so each
   non-obvious bubble needs to say whose it is. */
.message-bubble-author {
    margin: 0 0 4px;
    font-size: 12px;
    font-weight: 700;
}

/* ---------- Support tickets ---------- */

/* Small unread-style count on the sidebar's Support nav item (see
   header.php) — .nav-item is display: block, not flex, so this floats
   right within it rather than needing to restructure that shared rule
   just for one item. */
.nav-item-badge {
    float: right;
    background: var(--accent-primary);
    color: #fff;
    font-size: 11px;
    font-weight: 700;
    line-height: 1.6;
    border-radius: 999px;
    padding: 0 7px;
}

.ticket-tabs {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    margin-bottom: 14px;
}

.ticket-tab {
    padding: 7px 14px;
    border-radius: 999px;
    background: var(--bg-input);
    border: 1px solid var(--border-color);
    color: var(--text-secondary);
    font-size: 13px;
    font-weight: 600;
    text-decoration: none;
}

.ticket-tab:hover {
    color: var(--text-primary);
    border-color: rgba(var(--accent-primary-rgb), 0.4);
    text-decoration: none;
}

.ticket-tab.active {
    background: rgba(var(--accent-primary-rgb), 0.15);
    border-color: rgba(var(--accent-primary-rgb), 0.4);
    color: var(--text-primary);
}

/* Shared admin/*.php navigation (includes/admin_nav.php) — one row of
   category buttons, each opening an animated dropdown of that
   category's pages. Reuses the site's existing visual language rather
   than inventing a third style just for the admin area: the row itself
   is .category-tabs (same pill spacing posts.php uses for its Category
   picker) and each trigger is a .category-tab, including its animated
   "racing border" .active effect; the dropdown panel and its rows reuse
   the exact trigger+panel+.open-class mechanic and .user-menu-link row
   style that the account/notification dropdowns in the topbar already
   use (see header.php). This block only adds what's actually new: the
   dropdown's own positioning/animation and the divider under the whole
   nav row. */
.admin-nav {
    margin-bottom: 20px;
    padding-bottom: 16px;
    border-bottom: 1px solid var(--border-color);
}

.admin-nav-bar {
    margin-bottom: 0;
}

.admin-nav-dropdown {
    position: relative;
    display: inline-block;
}

.admin-nav-trigger {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    cursor: pointer;
    font: inherit;
}

.admin-nav-caret {
    font-size: 10px;
    opacity: 0.65;
    transition: transform 0.15s ease;
}

.admin-nav-dropdown.open .admin-nav-caret {
    transform: rotate(180deg);
}

/* Same open/close mechanic as .user-menu/.user-menu-dropdown just
   above (opacity + transform + a delayed visibility switch, toggled by
   an .open class on the wrapper) — see that block's own comment for
   why the visibility delay is only on the closing transition. */
.admin-nav-menu {
    position: absolute;
    top: calc(100% + 10px);
    left: 0;
    min-width: 200px;
    background: var(--bg-card);
    border: 1px solid var(--border-color);
    border-radius: var(--radius);
    box-shadow: 0 16px 32px rgba(0, 0, 0, 0.35);
    padding: 8px;
    margin: 0;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transform: translateY(-8px) scale(0.98);
    transform-origin: top left;
    transition: opacity 0.15s ease, transform 0.15s ease, visibility 0s linear 0.15s;
    z-index: 20;
}

.admin-nav-dropdown.open .admin-nav-menu {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transform: translateY(0) scale(1);
    transition: opacity 0.15s ease, transform 0.15s ease, visibility 0s linear 0s;
}

/* A ticket row on support.php — same "whole .card is the link" pattern
   as .search-comment-row/.conversation-row. */
.ticket-row {
    display: block;
    text-decoration: none;
    color: inherit;
}

.ticket-row:hover {
    border-color: rgba(var(--accent-primary-rgb), 0.4);
    text-decoration: none;
}

/* Left-border priority accent on the whole row card, from
   ticketRowPriorityClass() in support_helpers.php. Deliberately just
   border-left-color — the row keeps .card's normal border on the other
   three sides, so this reads as an accent stripe rather than
   recoloring the card. Same color mapping as the .ticket-priority-*
   pill badges above (low=primary, medium=blue, high=orange,
   urgent=red) so the two cues never disagree. */
.ticket-row {
    border-left: 3px solid transparent;
}

.ticket-row-priority-low {
    border-left-color: var(--accent-primary);
}

.ticket-row-priority-medium {
    border-left-color: var(--accent-blue);
}

.ticket-row-priority-high {
    border-left-color: var(--accent-orange);
}

.ticket-row-priority-urgent {
    border-left-color: var(--accent-red);
}

/* Ticket aging badge (support_aging_hours setting) — a softer, earlier
   nudge than the escalation badges below; amber to sit between the
   neutral status pills and the hotter escalation colors without being
   mistaken for either. */
.ticket-badge-aging {
    background: rgba(242, 153, 74, 0.12);
    color: var(--accent-orange);
    border: 1px solid rgba(242, 153, 74, 0.35);
}

.ticket-row-top {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.ticket-row-subject {
    font-weight: 700;
    color: var(--text-primary);
    font-size: 15px;
}

.ticket-row-meta {
    margin: 6px 0 0;
    font-size: 12px;
    color: var(--text-muted);
}

/* Priority/status pills — small pill badges in their own color families
   so a queue full of rows is scannable at a glance without reading every
   label. Colors reuse the same --accent-* tokens the rest of the app
   already has rather than introducing new ones. */
.ticket-badge {
    display: inline-block;
    padding: 2px 9px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

.ticket-priority-low {
    background: rgba(var(--accent-primary-rgb), 0.12);
    color: var(--accent-primary);
}

.ticket-priority-medium {
    background: rgba(59, 130, 246, 0.15);
    color: var(--accent-blue);
}

.ticket-priority-high {
    background: rgba(242, 153, 74, 0.15);
    color: var(--accent-orange);
}

.ticket-priority-urgent {
    background: rgba(226, 87, 76, 0.15);
    color: var(--accent-red);
}

.ticket-status-open {
    background: rgba(59, 130, 246, 0.15);
    color: var(--accent-blue);
}

.ticket-status-in_progress {
    background: rgba(242, 153, 74, 0.15);
    color: var(--accent-orange);
}

.ticket-status-escalated {
    background: rgba(226, 87, 76, 0.15);
    color: var(--accent-red);
}

.ticket-status-closed {
    background: var(--bg-card-hover);
    color: var(--text-muted);
}

/* ---------- Mass email job status pills ---------- */
/* Reuses .ticket-badge's pill shape (padding/radius/uppercase), just
   with its own color set — see massEmailStatusClass() in
   includes/mass_email.php. */
.mass-email-status-pending {
    background: rgba(59, 130, 246, 0.15);
    color: var(--accent-blue);
}

.mass-email-status-sending {
    background: rgba(242, 153, 74, 0.15);
    color: var(--accent-orange);
}

.mass-email-status-completed {
    background: rgba(var(--accent-primary-rgb), 0.12);
    color: var(--accent-primary);
}

.mass-email-status-cancelled {
    background: var(--bg-card-hover);
    color: var(--text-muted);
}

/* Progress bar on admin_mass_email_progress.php — a plain track+fill,
   same rounded/flat visual language as the rest of the admin UI rather
   than a native <progress> element (hard to theme consistently across
   browsers). */
.mass-email-progress-track {
    width: 100%;
    height: 10px;
    border-radius: 999px;
    background: var(--bg-card-hover);
    overflow: hidden;
    margin: 10px 0;
}

.mass-email-progress-fill {
    height: 100%;
    background: var(--accent-primary);
    border-radius: 999px;
    transition: width 0.3s ease;
}

.ticket-controls-title {
    margin: 0 0 10px;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--text-muted);
}

.ticket-controls-row {
    display: flex;
    gap: 10px;
    align-items: center;
    flex-wrap: wrap;
}

.ticket-controls-row + .ticket-controls-row {
    margin-top: 10px;
}

.ticket-controls-row form {
    margin-top: 0;
}

.ticket-controls-inline {
    display: flex;
    gap: 8px;
    align-items: center;
}

.ticket-controls-inline select {
    margin-bottom: 0;
    width: auto;
}

/* ---------- Reports / moderation ----------
   Report link in the postbit (view_post.php) sits alongside Edit/Delete
   inside .post-actions — its own muted/orange treatment (rather than
   reusing plain .btn) so it doesn't read as equally weighted with
   Edit, and doesn't read as destructive the way .btn-danger's red does
   (reporting something isn't itself a destructive action, unlike
   Delete). */
.btn-flag {
    background: rgba(242, 153, 74, 0.12);
    color: var(--accent-orange);
    border: 1px solid rgba(242, 153, 74, 0.3);
}

.btn-flag:hover {
    background: rgba(242, 153, 74, 0.22);
}

/* Read-only excerpt of whatever's being reported, shown above the
   report_content.php form so the reporter can confirm they picked the
   right thing before filling out a reason — plain text, not rendered
   BBCode, since this is just an identifying preview and the raw source
   could otherwise look broken half-cut-off at the 200-char truncation. */
.report-target-preview {
    margin: 8px 0 18px;
    padding: 10px 14px;
    border-left: 3px solid var(--border-color);
    color: var(--text-secondary);
    font-size: 13px;
    font-style: normal;
}

/* ---------- Forum permissions grid (admin_forum_permissions.php) ----------
   The first real <table> in the app — everything else so far is built
   from cards/divs, so this needs its own from-scratch dark-theme
   styling rather than reusing something existing. */

.forum-permissions-table {
    width: 100%;
    border-collapse: collapse;
    margin: 10px 0 14px;
    font-size: 13px;
}

.forum-permissions-table th {
    text-align: left;
    padding: 6px 10px;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--text-muted);
    border-bottom: 1px solid var(--border-color);
}

.forum-permissions-table td {
    padding: 8px 10px;
    border-bottom: 1px solid var(--border-color);
    color: var(--text-secondary);
}

.forum-permissions-table tr:last-child td {
    border-bottom: none;
}

.forum-permissions-table th:not(:first-child),
.forum-permissions-table td:not(:first-child) {
    text-align: center;
}

.forum-permissions-table input[type="checkbox"] {
    width: auto;
    margin: 0;
}

.usergroup-swatch {
    display: inline-block;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    margin-right: 6px;
    vertical-align: middle;
}

/* Generic collapsed-by-default permissions dropdown, shared by
   admin_forum_permissions.php (one per forum, wrapping the view/post
   table) and admin_usergroups.php (one per group, wrapping its
   checkboxes, plus the create-group form). Native <details>/<summary>
   rather than a JS accordion — same reasoning as .styling-reference:
   no JS needed, and it works even before executeScripts() re-runs
   anything after a pjax swap.
   The summary is styled as its own button-like row (background, border,
   radius) with the marker replaced by a rotating chevron — a bare
   <summary> renders as plain text with the browser's own default
   triangle glyph, which doesn't match anything else's affordance
   elsewhere in the UI and is what actually read as "awful" here. The
   badge in <summary> is what earns back the "condensed" page goal — an
   admin can tell what's already restricted/enabled without opening
   every single row. */
.permissions-details {
    margin-top: 14px;
}

.permissions-details summary {
    cursor: pointer;
    font-weight: 600;
    font-size: 13px;
    color: var(--text-secondary);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 9px 12px;
    background: var(--bg-input);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    list-style: none;
    transition: border-color 0.15s ease, color 0.15s ease;
}

.permissions-details summary::-webkit-details-marker {
    display: none;
}

.permissions-details summary:hover {
    color: var(--text-primary);
    border-color: rgba(var(--accent-primary-rgb), 0.4);
}

.permissions-details-summary-label {
    display: flex;
    align-items: center;
    gap: 8px;
}

.permissions-details-caret {
    font-size: 11px;
    color: var(--text-muted);
    transition: transform 0.15s ease;
    flex-shrink: 0;
}

.permissions-details[open] .permissions-details-caret {
    transform: rotate(180deg);
}

/* The panel that drops down — a contained box of its own (not just a
   border-top divider) so it reads as "this is what's inside the
   toggle" rather than more content loosely trailing after it. */
.permissions-details > summary ~ * {
    margin-top: 8px;
    padding: 14px;
    background: var(--bg-input);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
}

.permissions-details-checkboxes {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 10px 20px;
}

/* Preview box for a usergroup's uploaded userbar on admin_usergroups.php.
   Capped well below the panel's full width — these are meant to be read
   at postbit size (see .post-user-userbar, ~120px wide), so previewing
   them huge would be misleading about how they'll actually look. The
   checkered/muted background makes transparent-background uploads
   (PNG/WebP/GIF) visible against the dark theme instead of blending in. */
.userbar-preview {
    max-width: 320px;
    padding: 8px;
    background: var(--bg-card-hover);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
}

.userbar-preview img {
    display: block;
    max-width: 100%;
    height: auto;
}

/* admin_badges.php — each badge got a wall-of-inline-flex card that read
   as "compacted": header/meta/edit-form/criteria/members/delete all
   touching with no visual grouping. These give the same card real
   sections — a header with an icon preview, a form laid out in clearly
   separated rows, a divider before the collapsible Criteria/Members
   panels, and a divider before the destructive Delete action — instead
   of everything just being adjacent elements with small inline gaps. */
.badge-admin-header {
    display: flex;
    align-items: center;
    gap: 14px;
    margin-bottom: 4px;
}

.badge-admin-icon-preview {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    background: var(--bg-input);
    border: 1px solid var(--border-color);
    flex-shrink: 0;
}

.badge-admin-title-group {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}

.badge-admin-title {
    margin: 0;
    font-size: 16px;
    font-weight: 700;
    color: var(--text-primary);
}

/* Vertical rhythm for the edit form itself — each row (name/description,
   icon controls, the earnable checkbox, the Save button) gets consistent
   air between it and the next, rather than relying on whatever margin an
   individual input happened to carry. */
.badge-admin-form-section {
    display: flex;
    flex-direction: column;
    gap: 16px;
    margin-top: 16px;
}

.badge-admin-form-row {
    display: flex;
    gap: 16px;
    flex-wrap: wrap;
}

.badge-admin-form-row > div {
    flex: 1;
    min-width: 200px;
}

.badge-admin-form-row label {
    margin-bottom: 6px;
}

.badge-admin-form-row input {
    margin-bottom: 0;
}

.badge-admin-checkbox-row {
    display: flex;
    align-items: center;
    gap: 8px;
    font-weight: 400;
}

.badge-admin-checkbox-row input {
    width: auto;
    margin-bottom: 0;
}

/* A plain rule rather than another bordered/padded box (like
   .permissions-details gets) — this just needs to separate "editing the
   badge" from "managing who has it," not look like its own panel. */
.badge-admin-divider {
    border: none;
    border-top: 1px solid var(--border-color);
    margin: 20px 0;
}

.badge-admin-footer {
    display: flex;
    justify-content: flex-end;
    margin-top: 20px;
}

/* ---------- Coin store (store.php) ---------- */

.store-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 14px;
    margin-bottom: 20px;
}

.store-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 4px;
}

.store-item-icon {
    margin-bottom: 4px;
}

.store-item-name {
    font-weight: 700;
    margin: 0;
}

.store-item-desc {
    font-size: 13px;
    color: var(--text-secondary);
    margin: 0;
}

.store-item-cost {
    font-size: 13px;
    font-weight: 700;
    color: var(--accent-primary);
    margin: 4px 0 10px;
}

.store-item form {
    width: 100%;
    margin: 0;
}

.store-item .btn,
.store-item button {
    width: 100%;
}

.store-item .btn[disabled],
.store-item button[disabled] {
    opacity: 0.5;
    cursor: not-allowed;
}

/* A subtler border/background than the not-affordable case above — this
   one isn't a blocker, it's a "you already did this" confirmation, same
   spirit as .store-item's own not-affordable/disabled state but not
   trying to look identical to it. */
.store-item-owned {
    border-color: var(--accent-primary);
}

/* Criteria and holder rows inside the Criteria/Members panels — same
   "padding + border-top on the next sibling" divider pattern as
   .online-user-row, so a list of several rows reads as a list instead
   of a stack of near-touching mini-forms. */
.badge-criterion-row,
.badge-holder-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 9px 2px;
    margin: 0;
}

.badge-criterion-row + .badge-criterion-row,
.badge-holder-row + .badge-holder-row {
    border-top: 1px solid var(--border-color);
}

.badge-holder-info {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.badge-admin-add-row {
    display: flex;
    gap: 12px;
    align-items: flex-end;
    flex-wrap: wrap;
    margin-top: 14px;
    padding-top: 14px;
    border-top: 1px solid var(--border-color);
}

.badge-admin-add-row > div {
    display: flex;
    flex-direction: column;
}

.badge-admin-add-row label {
    font-size: 12px;
    margin-bottom: 4px;
}

.badge-admin-add-row select,
.badge-admin-add-row input {
    margin-bottom: 0;
}

/* ---------- Username styling (includes/username_style.php) ----------
   Animation classes only — color and font are applied inline (a raw
   hex value / font-family string), since those vary per option and
   don't need a stylesheet entry each. Animation is class-based instead
   because @keyframes can't be expressed inline. Every animation is
   wrapped in a prefers-reduced-motion override further down so a
   member who's asked their OS for less motion never gets one forced
   on them regardless of what they or their usergroup picked. */

.username-anim-pulse {
    display: inline-block;
    animation: username-pulse 1.6s ease-in-out infinite;
}

@keyframes username-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.55; }
}

.username-anim-glow {
    display: inline-block;
    animation: username-glow 2s ease-in-out infinite;
}

@keyframes username-glow {
    0%, 100% { text-shadow: 0 0 2px currentColor; }
    50% { text-shadow: 0 0 8px currentColor, 0 0 14px currentColor; }
}

.username-anim-rainbow {
    display: inline-block;
    animation: username-rainbow 4s linear infinite;
}

@keyframes username-rainbow {
    0%   { color: #ff5555; }
    17%  { color: #ffb347; }
    33%  { color: #f4e04d; }
    50%  { color: #4dd672; }
    67%  { color: #4da6ff; }
    83%  { color: #b47cff; }
    100% { color: #ff5555; }
}

.username-anim-shimmer {
    display: inline-block;
    background: linear-gradient(90deg, currentColor 0%, #ffffff 50%, currentColor 100%);
    background-size: 200% auto;
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    animation: username-shimmer 2.5s linear infinite;
}

@keyframes username-shimmer {
    0%   { background-position: 200% center; }
    100% { background-position: -200% center; }
}

@media (prefers-reduced-motion: reduce) {
    .username-anim-pulse,
    .username-anim-glow,
    .username-anim-rainbow,
    .username-anim-shimmer {
        animation: none !important;
    }

    /* .username-anim-rainbow/.username-anim-shimmer replace the
       inline color entirely while animating (cycling hues / clipping
       text to a gradient) — with the animation turned off, fall back
       to inheriting whatever color the element's own style="..."
       already set (the member's actual chosen or usergroup color)
       rather than being left on the animation's first freeze-frame
       or shimmer's fully-transparent fill. */
    .username-anim-rainbow {
        color: inherit;
    }

    .username-anim-shimmer {
        background: none;
        -webkit-text-fill-color: currentColor;
    }
}

/* Effect classes — an independent 4th slot alongside color/font/
   animation (see USERNAME_EFFECTS in includes/username_style.php).
   These are all static (text-shadow/-webkit-text-stroke only, no
   @keyframes), so unlike the animation classes above they need no
   prefers-reduced-motion override — nothing here moves. Every effect
   uses currentColor so it always matches whatever color a member (or
   their usergroup) has equipped, including a gradient's fallback
   color. Animation and effect classes are combined (space-joined),
   not swapped — see getUsernameStyleAttributes(). */

.username-effect-glow {
    text-shadow: 0 0 4px currentColor, 0 0 9px currentColor;
}

.username-effect-outline {
    -webkit-text-stroke: 1px currentColor;
    text-shadow: none;
}

.username-effect-shadow {
    text-shadow: 2px 2px 3px rgba(0, 0, 0, 0.55);
}

.username-effect-neon {
    text-shadow: 0 0 4px currentColor, 0 0 10px currentColor, 0 0 20px currentColor, 0 0 38px currentColor;
}

/* Knowledge base */

.kb-category-card {
    margin-bottom: 16px;
}

.kb-article-list {
    list-style: none;
    margin: 8px 0 0;
    padding: 0;
}

.kb-article-list li {
    padding: 6px 0;
    border-top: 1px solid var(--border-color);
}

.kb-article-list li:first-child {
    border-top: none;
}

.kb-internal-badge {
    background: rgba(155, 109, 255, 0.15);
    color: var(--accent-purple);
}

.kb-article-body {
    margin-top: 12px;
}

.kb-manage-row {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
}

.kb-sort-active {
    font-weight: 600;
    color: var(--text-primary);
}

.kb-article-views {
    margin-left: 8px;
}

.kb-helpful {
    margin-top: 16px;
    padding-top: 12px;
    border-top: 1px solid var(--border-color);
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}

.kb-helpful-form {
    display: flex;
    gap: 8px;
}

.kb-helpful-btn {
    background: var(--bg-input);
    border: 1px solid var(--border-color);
    color: var(--text-secondary);
    border-radius: 6px;
    padding: 4px 12px;
    cursor: pointer;
    font-size: 0.9em;
}

.kb-helpful-btn.active {
    border-color: var(--accent-primary);
    color: var(--accent-primary);
}

/* Installer */

.install-wrap {
    max-width: 560px;
}

.install-steps {
    display: flex;
    gap: 6px;
    margin-bottom: 16px;
    flex-wrap: wrap;
}

.install-step {
    font-size: 0.85em;
    color: var(--text-muted);
    padding: 4px 10px;
    border-radius: var(--radius-sm);
    background: var(--bg-card);
    border: 1px solid var(--border-color);
}

.install-step.active {
    color: var(--accent-primary);
    border-color: var(--accent-primary);
}

.install-card label {
    display: block;
    margin-top: 14px;
    margin-bottom: 4px;
}

.install-error-card {
    margin-bottom: 16px;
}

.install-check-list {
    list-style: none;
    margin: 12px 0;
    padding: 0;
}

.install-check-list li {
    padding: 6px 0;
}

.install-check {
    display: flex;
    align-items: center;
    gap: 8px;
}

.install-check-ok {
    color: var(--accent-green);
}

.install-check-warn {
    color: var(--accent-orange);
}

.install-check-fail {
    color: var(--accent-red);
}
