/* ═══════════════════════════════════════════
   KIXSIE — kixsie.css
   ═══════════════════════════════════════════ */

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

/* ═══════════════════════════════════════════
   CSS VARIABLES - GLOBAL
   ═══════════════════════════════════════════ */
:root {
    /* ── Palette ── */
    --primary: #e94560;
    --primary-dark: #c73652;
    --primary-dim: rgba(233,69,96,0.1);
    --primary-border: rgba(233,69,96,0.3);
    --bg-dark: #1a1a2e;
    --bg-card: rgba(0,0,0,0.5);
    --bg-input: #1a1a2e;
    --bg-dropdown: #000;
    --text: #ffffff;
    --text-muted: #cccccc;
    --text-dim: #aaaaaa;
    --text-faint: #555555;
    --border: #333333;
    --border-dark: #222222;
    --border-width: 1px;
    --border-style: solid;
    --input-border: #333333;
    --online: #00ff88;
    --offline: #888899;
    --success: #2ecc71;
    --steam-blue: #66c0f4;
    --steam-dark: #1b2838;
    --steam-border: #2a475e;
    --radius: 6px;
    --radius-lg: 8px;
    --radius-pill: 20px;
    --btn-radius: 20px;
    --text-shadow: none;
    --scrollbar-width: 8px;
    --scrollbar-color: #e94560;
    --scrollbar-track: #1a1a2e;
    --navbar-h: 50px;

    /* ═══════════════════════════════════════════
       GLOBAL CARD (.gc-container) — 9-zone theme variables
       Tiers: Basic = container · Advanced = 3 rows · Expert = 9 zones
       ═══════════════════════════════════════════ */

    /* ── Container (Basic) ── */
    --gc-bg-color: var(--bg-card);
    --gc-bg-image: none;
    --gc-bg-size: cover;
    --gc-border-color: var(--primary);
    --gc-border-width: var(--border-width, 1px);
    --gc-border-style: var(--border-style, solid);
    --gc-radius: var(--radius);
    --gc-transition: 0.2s;
    --gc-shadow: none;
    --gc-hover-transform: none;
    --gc-hover-shadow: none;

    /* ── Rows (Advanced) ── */
    --gc-header-bg: transparent;
    --gc-header-image: none;
    --gc-header-bg-size: cover;
    --gc-header-height: 8px;
    --gc-header-text: var(--text);

    --gc-body-bg: transparent;
    --gc-body-image: none;
    --gc-body-bg-size: cover;

    --gc-footer-bg: transparent;
    --gc-footer-image: none;
    --gc-footer-bg-size: cover;
    --gc-footer-height: 0px;

    /* ── Zone widths ── */
    --gc-header-left-width: 8px;
    --gc-header-right-width: 8px;
    --gc-body-left-width: 8px;
    --gc-body-right-width: 8px;
    --gc-footer-left-width: 8px;
    --gc-footer-right-width: 8px;

    /* ── Zone heights ── */
    --gc-header-left-height: 8px;
    --gc-header-center-height: 8px;
    --gc-header-right-height: 8px;
    --gc-footer-left-height: 8px;
    --gc-footer-center-height: 8px;
    --gc-footer-right-height: 8px;

    /* ── Body-center padding ── */
    --gc-body-center-pad-x: 8px;
    --gc-body-center-pad-top: 8px;
    --gc-body-center-pad-bottom: 8px;

    /* ── Body-center padding TIERS ── */
    --gc-pad-tight:  8px 12px;
    --gc-pad-narrow: 12px 0;
    --gc-pad-row:    16px 0;
    --gc-pad-roomy: 40px 32px;

    /* ── Zones (Expert) ── */
    --gc-header-left-bg: transparent;    --gc-header-left-image: none;    --gc-header-left-bg-size: cover;
    --gc-header-center-bg: transparent;  --gc-header-center-image: none;  --gc-header-center-bg-size: cover;
    --gc-header-right-bg: transparent;   --gc-header-right-image: none;   --gc-header-right-bg-size: cover;
    --gc-body-left-bg: transparent;      --gc-body-left-image: none;      --gc-body-left-bg-size: cover;
    --gc-body-center-bg: transparent;    --gc-body-center-image: none;    --gc-body-center-bg-size: cover;
    --gc-body-right-bg: transparent;     --gc-body-right-image: none;     --gc-body-right-bg-size: cover;
    --gc-footer-left-bg: transparent;    --gc-footer-left-image: none;    --gc-footer-left-bg-size: cover;
    --gc-footer-center-bg: transparent;  --gc-footer-center-image: none;  --gc-footer-center-bg-size: cover;
    --gc-footer-right-bg: transparent;   --gc-footer-right-image: none;   --gc-footer-right-bg-size: cover;

    /* ═══════════════════════════════════════════
       GLOBAL SHELL (.gmc-container) — 9-zone theme variables
       ═══════════════════════════════════════════ */

    /* ── Rows ── */
    --gmc-header-bg: transparent;
    --gmc-header-image: none;
    --gmc-header-bg-size: cover;
    --gmc-header-height: auto;
    --gmc-header-text: var(--text);

    --gmc-body-bg: transparent;
    --gmc-body-image: none;
    --gmc-body-bg-size: cover;

    --gmc-footer-bg: transparent;
    --gmc-footer-image: none;
    --gmc-footer-bg-size: cover;
    --gmc-footer-height: 0px;

    --gmc-header-left-width: 0px;
    --gmc-header-right-width: 0px;
    --gmc-body-left-width: 0px;
    --gmc-body-right-width: 0px;
    --gmc-footer-left-width: 0px;
    --gmc-footer-right-width: 0px;

    /* ── Zones (Expert) ── */
    --gmc-header-left-bg: transparent;    --gmc-header-left-image: none;    --gmc-header-left-bg-size: cover;
    --gmc-header-center-bg: transparent;  --gmc-header-center-image: none;  --gmc-header-center-bg-size: cover;
    --gmc-header-right-bg: transparent;   --gmc-header-right-image: none;   --gmc-header-right-bg-size: cover;
    --gmc-body-left-bg: transparent;      --gmc-body-left-image: none;      --gmc-body-left-bg-size: cover;
    --gmc-body-center-bg: transparent;    --gmc-body-center-image: none;    --gmc-body-center-bg-size: cover;
    --gmc-body-right-bg: transparent;     --gmc-body-right-image: none;     --gmc-body-right-bg-size: cover;
    --gmc-footer-left-bg: transparent;    --gmc-footer-left-image: none;    --gmc-footer-left-bg-size: cover;
    --gmc-footer-center-bg: transparent;  --gmc-footer-center-image: none;  --gmc-footer-center-bg-size: cover;
    --gmc-footer-right-bg: transparent;   --gmc-footer-right-image: none;   --gmc-footer-right-bg-size: cover;
}

/* ═══════════════════════════════════════════
   GLOBAL CARD — .gc-container (9-zone structure)
   ═══════════════════════════════════════════ */

.gc-container {
    background-color: var(--gc-bg-color);
    background-image: var(--gc-bg-image);
    background-size: var(--gc-bg-size);
    background-position: center;
    border: var(--gc-border-width) var(--gc-border-style) var(--gc-border-color);
    border-radius: var(--gc-radius);
    padding: 0;
    cursor: default;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    box-shadow: var(--gc-shadow);
    transition: transform var(--gc-transition), box-shadow var(--gc-transition);
}
.gc-container:hover {
    transform: var(--gc-hover-transform);
    box-shadow: var(--gc-hover-shadow, var(--gc-shadow));
}

/* ═══════════════════════════════════════════
   GC WIDTH TIERS
   ═══════════════════════════════════════════ */
.gc-w-narrow   { max-width: 480px;  width: calc(100% - 32px); margin: 24px auto; }
.gc-w-standard { max-width: 720px;  width: calc(100% - 32px); margin: 16px auto; }
.gc-w-wide     { max-width: 1200px; width: calc(100% - 32px); margin: 16px auto; }


/* ── Header row ── */
.gc-header {
    display: flex;
    flex-direction: row;
    align-items: center;
    height: var(--gc-header-height, 8px);
    background-color: var(--gc-header-bg);
    background-image: var(--gc-header-image);
    background-size: var(--gc-header-bg-size);
    background-position: center;
    color: var(--gc-header-text);
    flex-shrink: 0;
    width: 100%;
}
.gc-header-left {
    flex-shrink: 0;
    overflow: hidden;
    background-color: var(--gc-header-left-bg);
    background-image: var(--gc-header-left-image);
    background-size: var(--gc-header-left-bg-size);
    background-position: center;
    width: var(--gc-header-left-width, 8px);
    height: var(--gc-header-left-height, 8px);
}
.gc-header-center {
    flex: 1;
    min-width: 0;
    text-align: center;
    background-color: var(--gc-header-center-bg);
    background-image: var(--gc-header-center-image);
    background-size: var(--gc-header-center-bg-size);
    background-position: center;
    height: var(--gc-header-center-height, 8px);
    width: 100%;
}
.gc-header-right {
    flex-shrink: 0;
    overflow: hidden;
    background-color: var(--gc-header-right-bg);
    background-image: var(--gc-header-right-image);
    background-size: var(--gc-header-right-bg-size);
    background-position: center;
    width: var(--gc-header-right-width, 8px);
    height: var(--gc-header-right-height, 8px);
}

/* ── Body row ── */
.gc-body {
    display: flex;
    flex-direction: row;
    flex: 1;
    min-width: 0;
    min-height: 0;
    background-color: var(--gc-body-bg);
    background-image: var(--gc-body-image);
    background-size: var(--gc-body-bg-size);
    background-position: top;
}
.gc-body-left {
    flex-shrink: 0;
    width: var(--gc-body-left-width, 12px);
    overflow: hidden;
    background-color: var(--gc-body-left-bg);
    background-image: var(--gc-body-left-image);
    background-size: var(--gc-body-left-bg-size);
    background-position: center;
}
.gc-body-center {
    flex: 1;
    min-width: 0;
    padding: var(--gc-body-center-pad-top) var(--gc-body-center-pad-x) var(--gc-body-center-pad-bottom);
    background-color: var(--gc-body-center-bg);
    background-image: var(--gc-body-center-image);
    background-size: var(--gc-body-center-bg-size);
    background-position: center;
}

.gc-body-center.gc-flush      { padding: 6px; }
.gc-body-center.gc-pad-tight  { padding: var(--gc-pad-tight); }
.gc-body-center.gc-pad-narrow { padding: var(--gc-pad-narrow); }
.gc-body-center.gc-pad-row    { padding: var(--gc-pad-row); }
.gc-body-center.gc-pad-roomy  { padding: var(--gc-pad-roomy); }

.gc-body-right {
    flex-shrink: 0;
    width: var(--gc-body-right-width, 12px);
    overflow: hidden;
    background-color: var(--gc-body-right-bg);
    background-image: var(--gc-body-right-image);
    background-size: var(--gc-body-right-bg-size);
    background-position: center;
}

/* ── Footer row ── */
.gc-footer {
    display: flex;
    flex-direction: row;
    min-height: var(--gc-footer-height);
    background-color: var(--gc-footer-bg);
    background-image: var(--gc-footer-image);
    background-size: var(--gc-footer-bg-size);
    background-position: center;
    flex-shrink: 0;
	width: 100%;
}
.gc-footer-left {
    flex-shrink: 0;
    width: var(--gc-footer-left-width, 12px);
    height: var(--gc-footer-left-height, 12px);
    overflow: hidden;
    background-color: var(--gc-footer-left-bg);
    background-image: var(--gc-footer-left-image);
    background-size: var(--gc-footer-left-bg-size);
    background-position: center;
}
.gc-footer-center {
    flex: 1;
    min-width: 0;
    background-color: var(--gc-footer-center-bg);
    background-image: var(--gc-footer-center-image);
    background-size: var(--gc-footer-center-bg-size);
    background-position: center;
    height: var(--gc-footer-center-height, 0px);
	width:100%;
}
.gc-footer-right {
    flex-shrink: 0;
    width: var(--gc-footer-right-width, 12px);
    height: var(--gc-footer-right-height, 12px);
    overflow: hidden;
    background-color: var(--gc-footer-right-bg);
    background-image: var(--gc-footer-right-image);
    background-size: var(--gc-footer-right-bg-size);
    background-position: center;
}


/* .div -- the manual spacer -- is GONE (2026-08-08). It worked, but it meant
   remembering to paste an empty element after every <h2>, in ~29 templates
   still to convert, forever. The gap now comes from .gc-body-center itself;
   see the rule beside `.gc-body-center h2`. Al: "no margins, no invisible div.
   just a horizontal gap." */
/* ═══════════════════════════════════════════
   FOLD 
   ═══════════════════════════════════════════ */
.fold > .gc-header,
.fold > .pc-header {cursor: pointer;user-select: none;}
.fold > .gc-body,
.fold > .gc-footer,
.fold > .pc-body,
.fold > .pc-footer { display: none; }
.fold.open > .gc-body,
.fold.open > .pc-body { display: flex; }
.fold.open > .gc-footer,
.fold.open > .pc-footer { display: flex; }
.fold-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    width: 100%;
}
.fold-caret {
    flex-shrink: 0;
    font-size: 0.7em;
    line-height: 1;
    opacity: 0.7;
    transition: transform 0.18s ease;
}
.fold.open .fold-caret { transform: rotate(180deg); }
.fold > .gc-header:hover .fold-caret { opacity: 1; }

/* ── .fold-inner — the fold for empty-header cards ────────────────────────
   Once a card's label moves out of .gc-header and into .gc-body-center as an
   <h2>, the plain .fold above eats itself: it collapses .gc-body, so closing
   the row hides the label AND the caret, and all that is left to click is an
   8px transparent strip. Twelve of those stacked is an empty page.

   So .fold-inner keeps the body open and collapses only what is marked
   [data-fold-target] inside it. Pair it with [data-fold-toggle] on the h2
   (accordion.js reads that as the click target).

   *** ORDER IS LOAD-BEARING: these must stay BELOW the .fold rules. *** Both
   `.fold > .gc-body` and `.fold-inner > .gc-body` are specificity (0,2,0), so
   the only thing letting the override win is that it comes later in the file.
   Move this block up and every .fold-inner card collapses its whole body again.

   :not(.open) rather than a display value on the open state, so the target
   keeps whatever display its OWN rule gives it -- .badge-group-cards is grid,
   .bpr-split is a flex row, and a shared rule has no business guessing. */
.fold-inner > .gc-body,
.fold-inner > .gc-footer { display: flex; }
.fold-inner:not(.open) [data-fold-target] { display: none; }

.fold-inner [data-fold-toggle] { cursor: pointer; user-select: none; }
.fold-inner [data-fold-toggle]:hover .fold-caret { opacity: 1; }

/* ── Helpers ── */
.gc-inner { padding: 6px 12px; }

/* Friend/follow grid tile — widens the header rails so a pin checkbox and
   delete control have room to sit as real header-left/right zones instead
   of position:absolute hacks. */
/* *** DO NOT PUT A HEADER-HEIGHT OVERRIDE HERE. *** Tried and reverted
   2026-08-07. .gc-friend-tile is worn by TWO tiles with opposite needs:

     _follower_tile.html   gc-container gc-friend-tile          <- header HAS content
     _badge_tile.html      gc-container gc-friend-tile badge-card  <- header is EMPTY

   The follower tile wants --gc-header-height:auto so its 14px checkbox and X
   stop being clipped by the sitewide 8px row. Setting it here gave the badge
   tiles `auto` on three empty divs, which is 0px -- so every --gc-header-*
   value on a badge card rendered nothing at all, silently. It only surfaced on
   the pinned strip because the ladder rows default to collapsed, making those
   eight the only badge tiles on screen.
   The follower tile's clipping is real and still open; fix it on something
   only that tile wears, not on the class both share. */
.gc-friend-tile {
    --gc-header-left-width: auto;
    --gc-header-right-width: auto;
}
.gc-friend-tile a { text-decoration: none; }

/* Pin/favorite checkbox */
.gc-friend-tile .tile-checkbox {
    cursor: pointer; accent-color: var(--primary); width: 14px; height: 14px;
}

/* Remove / delete / unfollow "X" control — hover is pure CSS */
.gc-friend-tile .tile-x-link {
    font-size: 0.85em; color: #e94560; opacity: 0.5;
    transition: opacity 0.2s; font-weight: bolder; line-height: 1;
}
.gc-friend-tile .tile-x-link:hover { opacity: 1; }

/* Online/offline row — lives under the identity block in body-center */
.gc-friend-tile .tile-status-row {
    font-size: 0.72em; margin: 6px 0 0;
    display: flex; align-items: center; justify-content: center; gap: 5px;
}

/* Footer zone: one consistent shape, whatever it holds */
.gc-friend-tile { height: auto; padding: 0 0 10px; text-align: center; }

/* Shared action-link geometry (Message / Follow) — fill-state (.follow-btn/.following)
   stays as-is, this just unifies width/padding/font-size/radius/hover across both */
.gc-friend-tile .tile-action-link {
    display: block; width: calc(100% - 10px); margin: 0 auto; text-align: center;
    padding: 4px 12px; border: 1px solid var(--primary); color: var(--primary);
    border-radius: 20px; font-size: 0.72em; transition: all 0.2s; box-sizing: border-box;
}
.gc-friend-tile .tile-action-link:hover { background: var(--primary-dark); color: #fff; }

/* ── PLACEHOLDER / DRAG GHOST ─────────────────────────────────────────────
   ONE look for every "this box is empty or about to be filled" surface on the
   site: Sortable's drop placeholder (`.drag-ghost`, the ghostClass on all NINE
   Sortable instances -- badge_strip, igdb, tadb, tmdb, pinned_list x2,
   sortable.js x3, profile.html) and the empty slots in the pinned badge strip
   (`.badge-slot`). Al, 2026-08-08: *"We'll use this anywhere anything is
   sorted. that's the new drag-ghost."*

   *** IT WAS DECLARED TWICE, BYTE-IDENTICALLY -- HERE AND profile.css:1027.
   *** profile.css loads second so its copy won, and because they matched
   nothing ever looked wrong. That is exactly how two copies survive long
   enough to drift; the badge CSS did the same thing across two templates a
   few days ago. ONE definition now, and it lives HERE because a drag
   placeholder is a sitewide component and profile.css is not the sitewide
   file. Do not re-add a copy to profile.css.

   Was `2px dashed var(--primary)` at `opacity: 0.4`. */
.drag-ghost,
.badge-slot {
    border: 1px dashed var(--border);
    border-radius: var(--radius, 6px);
    background: rgb(0 0 0 / 20%);
}

/* *** THE !important IS LOAD-BEARING, NOT LAZINESS. *** A drop placeholder has
   to beat whatever the dragged element is already wearing, and those rules win
   on order even at equal specificity, because profile.css loads after this
   file. Two of them:
       profile.css:1978  .badge-card        { border: 1px solid var(--border) }
       profile.css:1982  .badge-card.earned { border-color: var(--primary) }
   Without this, a badge card being dragged keeps its own border while every
   other sortable on the site goes grey -- the precise inconsistency this rule
   exists to kill. Specificity alone cannot fix it: matching (0,2,0) still
   loses on source order, so it would take a three-class selector to win.

   *** USE THE `border` SHORTHAND, NOT border-color. *** First cut of this rule
   set `border-color` and `background` only, and Al caught it immediately: the
   ghost rendered SOLID while the console showed dashed. `.badge-card`'s
   `border: 1px solid` above was still supplying border-STYLE, and nothing here
   contested it. The shorthand covers width, style and colour together so the
   same gap cannot reopen. It also only showed up on the drag ghost, never on a
   .badge-slot -- the slot is not a .badge-card, so it had nothing to lose to.

   THE GLOW IS DELIBERATE NOW. It was arriving by accident from
   `.badge-card.earned:hover` (:1920) purely because the cursor sits on the
   card you are dragging -- so it vanished the moment the pointer left, and it
   never appeared on any other sortable. Al liked it, so it belongs to the
   ghost itself: same geometry and weight as that hover shadow, but via
   color-mix so it tracks a theme's --primary instead of hardcoding #e94560
   the way the hover rule does. */
.drag-ghost {
    border: 1px dashed var(--border) !important;
    background: rgb(0 0 0 / 20%) !important;
    box-shadow: 0 0 8px color-mix(in srgb, var(--primary) 20%, transparent);
}

/* Content hidden, not faded. A drop target should read as an empty outline of
   where the card will land -- the same shape as .badge-slot -- rather than a
   translucent copy of the card competing with the real one under the cursor.
   `visibility` not `display`, so the placeholder keeps the card's exact size.
   DELETE THIS ONE RULE to go back to a see-through card. */
.drag-ghost > * { visibility: hidden; }

/* ═══════════════════════════════════════════
   RELATIONS / SEARCH GRID CONTENT
   (moved from profile.css — used by the sitewide relations
   pages and the search results page, not profile-only)
   ═══════════════════════════════════════════ */
.results-wrap {
    max-width: 1200px;
    margin: 16px auto;
    padding: 0 24px;
}

.results-grid {
    display: grid;
    grid-template-columns: repeat(8, 1fr);
    gap: 16px;
    margin-bottom: 16px;
}

.results-grid .follow-avatar { width: 96px; height: 96px; }
.results-grid .search-icon-avatar { font-size: 2.6em; }
.results-grid .follow-username { font-size: 1em; }
.results-grid .follow-handle { font-size: 0.85em; }
.results-grid .search-result-meta { font-size: 0.78em; }

@media (max-width: 900px) {
    .results-grid { grid-template-columns: repeat(5, 1fr); }
}
@media (max-width: 600px) {
    .results-grid { grid-template-columns: repeat(3, 1fr); gap: 12px; }
    .results-grid .follow-avatar { width: 76px; height: 76px; }
}
@media (max-width: 400px) {
    .results-grid { grid-template-columns: repeat(2, 1fr); }
}

/* ══════════════════════════════════════════
   FOLLOW
   ═══════════════════════════════════════════ */
.follow-card {
    border-radius: var(--radius-lg);
    padding: 16px 10px;
    text-align: center;
    transition: border-color 0.2s;
}
.follow-card a { text-decoration: none; }

.follow-avatar {
    position: relative;
    width: 70px;
    height: 70px;
    border-radius: var(--avatar-radius, 0px);
    border-width: var(--avatar-border-width, 0px);
    border-style: solid;
    border-color: var(--avatar-border, var(--primary));
    background: transparent;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 2em;
    overflow: hidden;
    margin: 0 auto 10px auto;
    transition: border-color 0.2s;
}
.follow-avatar img { width: 100%; height: 100%; object-fit: cover; }

.follow-username { color: var(--text); font-size: 0.85em; font-weight: bold; margin-bottom: 2px; }
.follow-handle { color: var(--text-faint); font-size: 0.75em; margin-bottom: 10px; }

/* ═══════════════════════════════════════════
   TAB NAV (shared across admin pages and the friends/followers/
   following/blocked relations pages)
   ═══════════════════════════════════════════ */
.admin-nav {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 24px;
}
.admin-nav a {
    padding: 7px 14px;
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    color: var(--text);
    text-decoration: none;
    font-size: 0.82em;
    font-family: Arial, sans-serif;
    transition: background 0.12s;
}
.admin-nav a:hover { background: var(--primary); color: #fff; border-color: var(--primary); }
.admin-nav a.active { background: var(--primary); color: #fff; border-color: var(--primary); }

.gc-body-center h2 {
	text-transform: uppercase;
	color: var(--text);
    font-size: 1em;
    letter-spacing: 2px;
    display: flex;
    align-items: center;
    justify-content: space-between;
}

/* ── The gap under every card title ───────────────────────────────────────
   *** THE SPACING IS THE CONTAINER'S JOB, NOT THE TITLE'S. *** Al, 2026-08-08:
   "no margins, no invisible div. just a horizontal gap." Both of the obvious
   answers were rejected for the same reason -- they need remembering. A
   `margin-bottom` on the h2 has to be re-justified every time something else
   sits under it, and the `<div class="div">` spacer it replaced had to be
   pasted in by hand after every title in ~29 templates still to convert.
   `gap` needs no author action at all: put anything in a body-center and it
   is spaced.

   *** :has(> h2) IS THE BLAST RADIUS, AND IT IS DELIBERATE. *** flex and grid
   BLOCKIFY their direct children, so turning every .gc-body-center on the site
   into a flex column would stack loose inline content vertically -- two spans
   side by side become two rows, silently, on any of ~50 pages. Gating on a
   direct-child h2 limits this to cards that carry a title, which are exactly
   the ones being converted and looked at anyway.
   If a card needs the gap without an h2, give it the class explicitly rather
   than widening this selector. */
.gc-body-center:has(> h2) {
    display: flex;
    flex-direction: column;
    gap: 12px;
}
.gc-container p { color: var(--text-muted); font-size: 0.9em; line-height: 1.5; margin-bottom: 16px; }
.hidden { display: none; }
.text-center { text-align: center; }
.entrance-icon { font-size: 2.4em; margin-bottom: 14px; }
.error-icon {
    width: 72px;
    height: 72px;
    margin: 0 auto 20px;
    border-radius: 50%;
    background: var(--primary-dim);
    border: 2px solid var(--primary);
    color: var(--primary);
    display: flex;
    align-items: center;
    justify-content: center;
}
.error-icon svg { width: 34px; height: 34px; }

/* ═══════════════════════════════════════════
   GLOBAL SHELL — .gmc-container (9-zone structure)
   base.html's page shell on every non-profile page.
   ═══════════════════════════════════════════ */

.gmc-container {
    display: flex;
    flex-direction: column;
    height: 100dvh;
    overflow: hidden;
}

.gmc-header-left, .gmc-header-right,
.gmc-body-left,   .gmc-body-right,
.gmc-footer-left, .gmc-footer-right {
    align-self: stretch;
}

/* ── Header row ── */
.gmc-header {
    display: flex;
    flex-direction: row;
    align-items: center;
    width: 100%;
    height: var(--gmc-header-height);
    position: relative;
    z-index: 999;
    background-color: var(--gmc-header-bg);
    background-image: var(--gmc-header-image);
    background-size: var(--gmc-header-bg-size);
    background-position: center;
    color: var(--gmc-header-text);
    flex-shrink: 0;
    overflow: visible;
}
.gmc-header-left {
    flex-shrink: 0;
    width: var(--gmc-header-left-width);
    overflow: hidden;
    background-color: var(--gmc-header-left-bg);
    background-image: var(--gmc-header-left-image);
    background-size: var(--gmc-header-left-bg-size);
    background-position: center;
}
.gmc-header-center {
    flex: 1;
    min-width: 0;
    height: 100%;
    background-color: var(--gmc-header-center-bg);
    background-image: var(--gmc-header-center-image);
    background-size: var(--gmc-header-center-bg-size);
    background-position: center;
}
.gmc-header-right {
    flex-shrink: 0;
    width: var(--gmc-header-right-width);
    overflow: hidden;
    background-color: var(--gmc-header-right-bg);
    background-image: var(--gmc-header-right-image);
    background-size: var(--gmc-header-right-bg-size);
    background-position: center;
}

/* ── Body row ── */
.gmc-body {
    display: flex;
    flex-direction: row;
    flex: 1;
    min-height: 0;
    background-color: var(--gmc-body-bg);
    background-image: var(--gmc-body-image);
    background-size: var(--gmc-body-bg-size);
    background-position: top;
}
.gmc-body-left {
    flex-shrink: 0;
    width: var(--gmc-body-left-width);
    overflow: hidden;
    background-color: var(--gmc-body-left-bg);
    background-image: var(--gmc-body-left-image);
    background-size: var(--gmc-body-left-bg-size);
    background-position: center;
}
.gmc-body-center {
    flex: 1;
    min-width: 0;
    min-height: 0;
    overflow-y: auto;
    position: relative;
    isolation: isolate;
    background-color: var(--gmc-body-center-bg);
    background-image: var(--gmc-body-center-image);
    background-size: var(--gmc-body-center-bg-size);
    background-position: center;
}
.gmc-body-right {
    flex-shrink: 0;
    width: var(--gmc-body-right-width);
    overflow: hidden;
    background-color: var(--gmc-body-right-bg);
    background-image: var(--gmc-body-right-image);
    background-size: var(--gmc-body-right-bg-size);
    background-position: center;
}

/* ── Footer row ── */
.gmc-footer {
    display: flex;
    flex-direction: row;
    height: var(--gmc-footer-height);
    background-color: var(--gmc-footer-bg);
    background-image: var(--gmc-footer-image);
    background-size: var(--gmc-footer-bg-size);
    background-position: center;
    flex-shrink: 0;
    overflow: visible;
}
/* ═══════════════════════════════════════════
   TASKBAR MODE — a theme moves the nav to the foot of the page (2026-08-01)

   *** A THEME CAN ONLY WRITE CUSTOM PROPERTIES. *** base.html renders a
   theme as `:root{ --key:value; }` -- it cannot add a class, cannot touch
   markup, cannot reorder the DOM. So every part of "put the nav at the
   bottom" has to be reachable from a variable, or it is not reachable by a
   theme at all.

   The nav stays in gmc-header in the DOM. The shell is a flex COLUMN, so
   `order` moves the whole header row below the body visually without any
   markup change -- which is the only reason this works from CSS alone.

   A taskbar theme sets FOUR keys and gets a full bottom bar:

       --gmc-header-order:  3        (header row renders last)
       --gmc-footer-order:  1
       --nav-dd-top:        auto     (menus stop anchoring downward...)
       --nav-dd-bottom:     36px     (...and open upward instead)
       --nav-border-top:    1px solid var(--primary)
       --nav-border-bottom: none

   *** THE DROPDOWNS ARE NOT OPTIONAL AND ARE THE EASY THING TO FORGET. ***
   Reorder the rows and leave the menus anchored with `top:` and they open
   downward off the bottom of the screen -- exactly the failure the bottom
   taskbar had in reverse, and exactly as silent. Anything that ships a
   taskbar theme ships all six keys together.

   HEIGHTS DO NOT CHANGE. The nav lives in the header either way, so the
   header keeps its 50px and the footer keeps 0 -- only the visual order
   moves. Do not "fix" this by swapping the height variables too; that
   reserves a 50px band in the wrong row and collapses the one holding the
   nav.

   The default values below are exactly what ships today, so this block is
   inert until a theme opts in.
   ═══════════════════════════════════════════ */
.gmc-header { order: var(--gmc-header-order, 1); }
.gmc-body   { order: var(--gmc-body-order, 2); }
.gmc-footer { order: var(--gmc-footer-order, 3); }

.gmc-footer-left {
    flex-shrink: 0;
    width: var(--gmc-footer-left-width);
    overflow: hidden;
    background-color: var(--gmc-footer-left-bg);
    background-image: var(--gmc-footer-left-image);
    background-size: var(--gmc-footer-left-bg-size);
    background-position: center;
}
.gmc-footer-center {
    flex: 1;
    min-width: 0;
    background-color: var(--gmc-footer-center-bg);
    background-image: var(--gmc-footer-center-image);
    background-size: var(--gmc-footer-center-bg-size);
    background-position: center;
}
.gmc-footer-right {
    flex-shrink: 0;
    width: var(--gmc-footer-right-width);
    overflow: hidden;
    background-color: var(--gmc-footer-right-bg);
    background-image: var(--gmc-footer-right-image);
    background-size: var(--gmc-footer-right-bg-size);
    background-position: center;
}

/* ═══════════════════════════════════════════
   STANDALONE ENTRANCE PAGES — login, register, logout, resets, social
   Scoped to <body class="entrance"> so themed in-app pages never inherit it.
   ═══════════════════════════════════════════ */
body.entrance {
    background: var(--bg-dark);
    font-family: 'DM Sans', sans-serif;
    color: var(--text);
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px 16px;
    position: relative;
    overflow-x: hidden;
}
body.entrance::before {
    content: '';
    position: fixed;
    inset: 0;
    background-image:
        linear-gradient(rgba(233,69,96,0.04) 1px, transparent 1px),
        linear-gradient(90deg, rgba(233,69,96,0.04) 1px, transparent 1px);
    background-size: 40px 40px;
    pointer-events: none;
    z-index: 0;
}
body.entrance::after {
    content: '';
    position: fixed;
    top: -200px;
    left: 50%;
    transform: translateX(-50%);
    width: 600px;
    height: 600px;
    background: radial-gradient(circle, rgba(233,69,96,0.10) 0%, transparent 70%);
    pointer-events: none;
    z-index: 0;
}

/* Brand wrap */
.wrap {
    position: relative;
    z-index: 1;
    width: 100%;
    max-width: 440px;
}
.wrap-wide { max-width: 720px; }
.wrap-inpage { margin: 40px auto; padding: 0 16px; }
.brand { text-align: center; margin-bottom: 28px; }
.brand-name {
    font-family: 'Tourney', sans-serif;
    font-size: 2.8em;
    color: var(--primary);
    letter-spacing: 2px;
    text-shadow: 0 0 30px rgba(233,69,96,0.5);
}
.brand-sub {
    color: var(--text-muted);
    font-size: 0.85em;
    margin-top: 4px;
}

/* Card title — lives in gc-header-center */
.gc-title {
    font-size: 0.95em;
    font-weight: 500;
    color: var(--text-muted);
    text-align: center;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

/* OAuth buttons */
.oauth-buttons { display: flex; flex-direction: column; gap: 10px; }
.btn-oauth {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    width: 100%;
    padding: 11px 14px;
    background: rgba(255,255,255,0.04);
    border: 1px solid rgba(255,255,255,0.10);
    border-radius: var(--radius);
    color: var(--text);
    font-family: 'DM Sans', sans-serif;
    font-size: 0.95em;
    font-weight: 500;
    text-decoration: none;
    transition: background 0.2s, border-color 0.2s;
}
.btn-oauth:hover { background: rgba(255,255,255,0.08); }
.oauth-divider {
    display: flex;
    align-items: center;
    gap: 12px;
    margin: 20px 0;
    color: var(--text-muted);
    font-size: 0.78em;
    text-transform: uppercase;
    letter-spacing: 1px;
}
.oauth-divider::before,
.oauth-divider::after {
    content: '';
    flex: 1;
    height: 1px;
    background: rgba(255,255,255,0.06);
}

/* Form */
.form-group { margin-bottom: 18px; }
.form-group label {
    display: block;
    color: var(--primary);
    font-size: 0.7em;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 2px;
    margin-bottom: 7px;
}
.form-group input {
    width: 100%;
    padding: 11px 14px;
    background: rgba(0,0,0,0.35);
    border: 1px solid rgba(255,255,255,0.08);
    border-radius: var(--radius);
    color: var(--text);
    font-family: 'DM Sans', sans-serif;
    font-size: 0.95em;
    transition: border-color 0.2s, box-shadow 0.2s;
    outline: none;
}
.form-group input:focus {
    border-color: var(--primary);
    box-shadow: 0 0 0 3px rgba(233,69,96,0.18);
}

/* Errors */
.errors {
    background: rgba(233,69,96,0.1);
    border: 1px solid rgba(233,69,96,0.3);
    border-radius: var(--radius);
    padding: 11px 14px;
    margin-bottom: 20px;
    font-size: 0.85em;
    color: var(--primary);
}

/* Submit */
.btn-submit {
    width: 100%;
    padding: 11px 14px;
    background: var(--primary);
    color: #fff;
    border: none;
    border-radius: var(--radius);
    cursor: pointer;
    font-family: 'DM Sans', sans-serif;
    font-size: 0.95em;
    font-weight: 500;
    letter-spacing: 0.5px;
    transition: background 0.2s, transform 0.1s, box-shadow 0.2s;
    box-shadow: 0 4px 20px rgba(233,69,96,0.3);
}
.btn-submit:hover {
    background: var(--primary-dark);
    box-shadow: 0 4px 28px rgba(233,69,96,0.45);
}
.btn-submit:active { transform: scale(0.99); }
.btn-submit.btn-inline { width: auto; display: inline-block; padding: 11px 28px; text-decoration: none; }

/* Side-by-side button row (e.g. logout confirm/cancel) */
.btn-row { display: flex; gap: 10px; justify-content: center; margin-top: 6px; }
.btn-cancel {
    padding: 0 12px;
    background: rgba(255,255,255,0.06);
    color: var(--text-muted);
    border: 1px solid rgba(255,255,255,0.10);
    border-radius: var(--radius);
    cursor: pointer;
    font-family: 'DM Sans', sans-serif;
    font-size: 0.95em;
    font-weight: 500;
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    transition: background 0.2s, color 0.2s;
}
.btn-cancel:hover {
    background: rgba(255,255,255,0.10);
    color: var(--text);
}

/* Auth switch link (join / sign in) */
.auth-link {
    text-align: center;
    margin-top: 14px;
    color: var(--text-muted);
    font-size: 0.85em;
}
.auth-link a { color: var(--primary); text-decoration: none; font-weight: 500; }
.auth-link a:hover { text-decoration: underline; }

/* ═══════════════════════════════════════════
   ENTRANCE PAGES — extra elements not shared by every page
   ═══════════════════════════════════════════ */
.closed-msg { text-align: center; padding: 20px 0; }

.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 18px; }
.form-row .form-group { margin-bottom: 0; }

.label-optional {
    color: var(--text-muted);
    font-size: 0.85em;
    text-transform: none;
    letter-spacing: 0;
    font-weight: 400;
}

.input-wrap { position: relative; }
.form-group input.has-toggle,
.input-wrap input[type="password"] { padding-right: 44px; }

.toggle-pw {
    position: absolute;
    right: 12px;
    top: 50%;
    transform: translateY(-50%);
    background: none;
    border: none;
    cursor: pointer;
    color: var(--text-muted);
    padding: 4px;
    display: flex;
    align-items: center;
    transition: color 0.2s;
}
.toggle-pw:hover, .toggle-pw.active { color: var(--primary); }

.availability { font-size: 0.75em; margin-top: 5px; min-height: 16px; }
.availability.checking { color: var(--text-muted); }
.availability.available { color: var(--success); }
.availability.taken { color: var(--primary); }

.pw-strength { margin-top: 8px; }
.pw-strength-bar {
    height: 3px;
    background: rgba(255,255,255,0.08);
    border-radius: 2px;
    overflow: hidden;
    margin-bottom: 5px;
}
.pw-strength-fill {
    height: 100%;
    width: 0%;
    border-radius: 2px;
    transition: width 0.3s, background 0.3s;
}
.pw-strength-label { font-size: 0.72em; color: var(--text-muted); }

.field-error { color: var(--primary); font-size: 0.75em; margin-top: 5px; }
.field-hint { color: var(--text-muted); font-size: 0.72em; margin-top: 5px; }

.divider { border: none; border-top: 1px solid rgba(255,255,255,0.06); margin: 14px 0; }

.check-group { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 10px; }
.check-group input[type="checkbox"] {
    width: 16px;
    height: 16px;
    min-width: 16px;
    accent-color: var(--primary);
    margin-top: 2px;
    cursor: pointer;
}
.check-group label { font-size: 0.82em; color: var(--text-muted); line-height: 1.5; cursor: pointer; }
.check-group label a { color: var(--primary); text-decoration: none; }
.check-group label a:hover { text-decoration: underline; }

/* ═══════════════════════════════════════════
   LEGAL DOCUMENT PAGES — terms, privacy
   ═══════════════════════════════════════════ */
.legal-title { font-size: 1.25em; font-weight: 600; color: var(--text); }
.legal-meta { font-size: 0.78em; color: var(--text-muted); margin-top: 6px; line-height: 1.6; }

.scroll-body {
    max-height: calc(62vh - 45px);
    overflow-y: auto;
    padding: 28px 0;
    scrollbar-width: thin;
    scrollbar-color: rgba(233,69,96,0.4) transparent;
}
.scroll-body::-webkit-scrollbar { width: 5px; }
.scroll-body::-webkit-scrollbar-track { background: transparent; }
.scroll-body::-webkit-scrollbar-thumb { background: rgba(233,69,96,0.4); border-radius: 3px; }

.section { margin-bottom: 28px; }
.section:last-child { margin-bottom: 0; }
.section h2 {
    font-size: 0.7em;
    font-weight: 600;
    color: var(--primary);
    text-transform: uppercase;
    letter-spacing: 2px;
    margin-bottom: 12px;
    padding-bottom: 8px;
    border-bottom: 1px solid var(--primary-border);
}
.section p { font-size: 0.9em; color: var(--text-muted); line-height: 1.75; margin-bottom: 10px; }
.section p:last-child { margin-bottom: 0; }
.section ul { list-style: none; padding: 0; }
.section ul li {
    font-size: 0.9em;
    color: var(--text-muted);
    line-height: 1.7;
    padding: 3px 0 3px 16px;
    position: relative;
}
.section ul li::before { content: '—'; position: absolute; left: 0; color: rgba(233,69,96,0.5); }
.section strong { color: var(--text); font-size: 0.95em; }
.section ul + p { margin-top: 12px; }
.section .promise-box + p { margin-top: 12px; }

.promise-box {
    background: var(--primary-dim);
    border: 1px solid var(--primary-border);
    border-radius: var(--radius-lg);
    padding: 16px 18px;
    margin-top: 12px;
}
.promise-box p { color: var(--text); font-weight: 500; }

.legal-footer-actions { padding: 16px 32px; text-align: right; }

@media (max-width: 480px) {
    .brand-name { font-size: 2.2em; }
    .form-row { grid-template-columns: 1fr; }
}
@media (max-width: 600px) {
    .scroll-body { padding: 20px; max-height: calc(55vh - 45px); }
    .legal-footer-actions { padding: 14px 20px; }
}

/* ═══════════════════════════════════════════
   CHAT CARD — shared by conversation.html (DM) and group_chat.html (group)
   ═══════════════════════════════════════════ */

#chat-card {
    height: calc(100% - 32px);
    --gc-footer-center-height: auto;
}
#chat-card .gc-body-center {
    padding: 0;
    min-height: 0;
    display: flex;
    flex-direction: column;
}
#chat-card .gc-header-center { padding: 0; text-align: left; }
#chat-card .msg-header { border-bottom: none; }
#emoji-picker { position: fixed; }
.msg-sender-name {
    font-size: 0.74em;
    color: var(--text-muted);
    margin-bottom: 2px;
    padding-left: 2px;
    font-weight: 600;
    letter-spacing: 0.02em;
}
.group-member-strip {
    display: flex;
    align-items: center;
    gap: 3px;
    flex-shrink: 0;
}
.group-member-strip img {
    width: 26px;
    height: 26px;
    border-radius: 50%;
    object-fit: cover;
    border: 1px solid var(--border);
    display: block;
}
.group-member-count {
    font-size: 0.74em;
    color: var(--text-muted);
    margin-left: 4px;
    white-space: nowrap;
}

/* ───────────────────────────────────────────────────────────
themes included cards
 ───────────────────────────────────────────────────────────*/
:root {
    --ct-strip-card-h: 307px;
    --ct-catbar-h: 40px;
    --ct-catbar-gap: 16px;
    --ct-catbar-rows: 3;
    /* Fallback only. The bar wraps, so its real height is not knowable from
       CSS -- community_themes_catbar.js measures the dock and overwrites this
       on :root. Both preview offsets read it. */
    --ct-catbar-dock-h: calc(var(--ct-catbar-h) + (var(--ct-catbar-gap) * 2));
    --ct-catbar-top: 0px;
}

/* ── Info strip ─────────────────────────────────────────────────────────*/
.ct-info-strip {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 15px;
    align-items: start;
}
.ct-info-strip > .gc-container { max-height: var(--ct-strip-card-h); }
.ct-info-strip .gc-body { min-height: 0; }
.ct-info-strip .gc-body-center {
    display: flex;
    flex-direction: column;
    min-height: 0;
}
.ct-strip-list {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
}

/* ── Ranked list rows (top themes / trending/online) ───────────────────────── */
.ct-rank-row, .ct-online-row,  .ct-creator-row {
    display: flex;
    align-items: center;
	height:60px;
    gap: 8px;
    padding-right:8px ;
    border-bottom: 1px solid var(--border);
    transition: background 0.15s;
}
.ct-rank-row:last-child { border-bottom: none; }
.ct-rank-row:hover { background: rgba(255,255,255,0.04); }

/* ── Whole-row link on the rank cards (.ct-rank-link) ──────────────────
   Top Themes and Trending rows are now clickable anywhere, matching
   .ct-creator-row and .ct-online-row, which have always been <a> rows.

   *** THEY ALREADY HAD THE :hover HIGHLIGHT ABOVE AND NO LINK. *** Same
   background, same transition, same apparent affordance as the two rows
   that DO navigate -- so they read as clickable and did nothing. That is
   what this fixes; the highlight was never the problem.

   *** IT IS AN OVERLAY, NOT A WRAPPER, BECAUSE THE ROW ALREADY CONTAINS
   TWO ANCHORS *** -- the author link and the Details button. An <a> cannot
   be nested inside an <a>, so wrapping the row would have meant deleting
   one of those navigation paths. The overlay stretches over the row at
   z-index 0; .ct-rank-above lifts the real controls to z-index 1 so they
   keep their own targets and their own hover states.

   NO JAVASCRIPT ON PURPOSE. This include renders on three pages with three
   different script sets, and an onclick="location.href" is not a link:
   no middle-click, no open-in-new-tab, no keyboard focus. A real anchor
   gets all three for free. */
.ct-rank-row { position: relative; }
.ct-rank-link {
    position: absolute;
    inset: 0;
    z-index: 0;
    /* No text, so it needs no colour -- but it DOES need to be reachable by
       keyboard, which is why it is an anchor with an aria-label rather than
       a bare click surface. */
    border-radius: inherit;
}
.ct-rank-above { position: relative; z-index: 1; }

.ct-rank-num { font-size: 0.7em; color: var(--text-faint); width: 18px; flex-shrink: 0; text-align: right; }
.ct-rank-swatches { display: flex; gap: 3px; flex-shrink: 0; }
.ct-rank-swatch { width: 12px; height: 12px; border-radius: 50%; border: 1px solid rgba(255,255,255,0.15); flex-shrink: 0; }
.ct-rank-info { flex: 1; min-width: 0; }
.ct-rank-name { font-size: 0.88em; font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ct-rank-meta { font-size: 0.88em; color: var(--text-faint); margin-top: 1px; }
.ct-rank-btn { flex-shrink: 0; padding: 3px 10px; background: rgba(255,255,255,0.07); color: var(--text); border: 1px solid var(--border); border-radius: var(--radius); cursor: pointer; font-size: 0.83em; white-space: nowrap; }
.ct-rank-btn:hover { border-color: var(--primary); color: var(--primary); }
/* ── Online users ───────────────────────────────────────────────────── */
.ct-online-row:last-child { border-bottom: none; }
.ct-online-row:hover { background: rgba(255,255,255,0.04); }
.ct-ou-avatar { position: relative; width: 30px; height: 30px; flex-shrink: 0; }
.ct-ou-avatar img { width: 30px; height: 30px; border-radius: var(--avatar-radius, 4px); object-fit: cover; border: 1px solid var(--border); }
.ct-ou-avatar .online-dot { position: absolute; bottom: -1px; right: -1px; width: 8px; height: 8px; }
.ct-ou-name { font-size: 0.88em; color: var(--text); }
/* ── Newest creators ────────────────────────────────────────────────── */
.ct-creator-row:last-child { border-bottom: none; }
.ct-creator-row:hover { background: rgba(255,255,255,0.04); }
.ct-creator-avatar { width: 30px; height: 30px; border-radius: var(--avatar-radius, 4px); object-fit: cover; border: 1px solid var(--border); flex-shrink: 0; }
.ct-creator-name { font-size: 0.88em; color: var(--text); font-weight: 600; }
.ct-creator-sub { font-size: 0.7em; color: var(--text-faint); }
.ct-cat-dock {
    position: sticky;
    top: var(--ct-catbar-top);
    z-index: 5;
    padding: var(--ct-catbar-gap) 0;
    background-color: transparent;
    transition: background-color 0.18s ease;
}
.ct-cat-dock.ct-cat-stuck { background-color: var(--bg-dark); }
.ct-cat-row {
    display: flex;
    flex-wrap: wrap;
    align-content: flex-start;
    overflow-y: auto;
    align-items: center;
    min-height: var(--ct-catbar-h);
    max-height: 109px;
    gap: 8px;
    padding: 12px 12px;
    border: var(--gc-border-width) var(--gc-border-style) transparent;
    border-radius: var(--gc-radius);
    background: transparent;
    transition: background-color 0.18s ease, border-color 0.18s ease;
    scrollbar-width: thin;
}
.ct-cat-dock.ct-cat-stuck .ct-cat-row {
    background-color: var(--bg-dark);
    background-image: linear-gradient(var(--gc-bg-color), var(--gc-bg-color));
    border-color: var(--gc-border-color);
}
.ct-cat-row::-webkit-scrollbar { height: 4px; }
.ct-cat-row::-webkit-scrollbar-thumb {
    background: var(--border);
    border-radius: 2px;
}
.ct-cat-btn {
    padding: 5px 14px;
    border-radius: 20px;
    border: 1px solid var(--border);
    background: rgba(255,255,255,0.05);
    color: var(--text-muted);
    font-size: 0.82em;
    text-decoration: none;
    transition: border-color 0.15s, color 0.15s, background 0.15s;
    white-space: nowrap;
}
.ct-cat-import {
    padding: 5px 14px;
    border-radius: 20px;
    border: 1px solid var(--border);
    background: var(--primary);
    color: var(--text-muted);
    font-size: 0.82em;
    text-decoration: none;
    transition: border-color 0.15s, color 0.15s, background 0.15s;
    white-space: nowrap;
	}
.ct-cat-import:hover { border-color: var(--primary); color: #fff;background: rgba(233,69,96,0.1); }
.ct-cat-btn:hover { border-color: var(--primary); color: var(--primary); }
.ct-cat-btn.ct-cat-active { border-color: var(--primary); color: var(--primary); background: rgba(233,69,96,0.1); font-weight: 600; }
.ct-cat-push { margin-left: auto;}
.ct-cat-viewall {display:flex;align-items:center;justify-content:flex-end;padding:6px 6px 0;border-top: 1px solid var(--border);}
@media (max-width: 900px) { .ct-info-strip { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 600px) { .ct-info-strip { grid-template-columns: 1fr; } }

/* ═══════════════════════════════════════════
   GEAR (.kx-gear) — the one gear icon, sitewide and themable (2026-08-02)

   Replaces the ⚙️ EMOJI that every gear used to be. That is the whole reason
   this exists: an emoji renders in the font's own colours and cannot be
   recoloured, hovered or themed. It is drawn here as a MASK, so
   `background-color: currentColor` paints it and `color` themes it.

   *** .kx-gear IS PRESENTATION ONLY. IT IS NOT .gear-icon. ***
   .gear-icon carries BEHAVIOUR -- a legacy inline-edit handler binds to it.
   #media-gear had that class deliberately STRIPPED (change_log 2026-05-16,
   c2692f8) because it made the handler inject a textarea into the empty
   media card and POST field:undefined -> 400. Putting the look on a separate
   class means a gear can be styled without opting into that. Elements that
   want both carry both; #media-gear carries only this one.
   DO NOT MERGE THESE TWO CLASSES.

   THEME HOOKS -- all four, plus size:
       --gear-color         resting     (default --text-faint: quiet)
       --gear-color-hover   pointer over
       --gear-color-open    while .is-open
       --gear-rotate        turn applied by .is-open
       --gear-size

   .is-open is set by whatever owns the gear's state -- it is generic on
   purpose, so any control can express "the thing I open is open" rather than
   each page inventing its own selector.
   ═══════════════════════════════════════════ */
.kx-gear {
    width: var(--gear-size, 15px);
    height: var(--gear-size, 15px);
    flex-shrink: 0;
    display: inline-block;
    vertical-align: middle;
    cursor: pointer;
    border: none;
    padding: 0;
    background-color: currentColor;
    color: var(--gear-color, var(--text-faint));
    transition: color 0.15s, transform 0.2s;
    -webkit-mask: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='3'/%3E%3Cpath d='M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.6h.09A1.65 1.65 0 0 0 10 3.09V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9v.09a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z'/%3E%3C/svg%3E") center / contain no-repeat;
    mask: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='3'/%3E%3Cpath d='M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.6h.09A1.65 1.65 0 0 0 10 3.09V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9v.09a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z'/%3E%3C/svg%3E") center / contain no-repeat;
}
.kx-gear:hover { color: var(--gear-color-hover, var(--primary)); }

/* *** TWO ANIMATIONS, CHOSEN BY WHAT THE GEAR DOES (Al, 2026-08-02). ***
   A gear that NAVIGATES turns under the pointer -- the turn is a hover
   affordance, and it has to unwind when you leave because nothing happened.
   A gear that OPENS something turns on click and STAYS turned while the
   panel is open, because something did happen and the turn is the state.

   The link case needs no JavaScript and no marker class: a gear wrapped in
   an <a> is a link by construction, so the selector reads the markup rather
   than trusting someone to remember a class. `a:hover .kx-gear` covers the
   four on the profile page (Steam, Xbox, Friends, Badges).

   .is-open is set by JS for the click case -- see the delegated handler in
   profile.js and gearClickOff(), which clears it on click-off. */
a:hover .kx-gear {
    color: var(--gear-color-hover, var(--primary));
    transform: rotate(var(--gear-rotate, 60deg));
}
.kx-gear.is-open {
    color: var(--gear-color-open, var(--primary));
    transform: rotate(var(--gear-rotate, 60deg));
}
/* Pushes the gear to the end of a header row without needing a spacer. */
.kx-gear-end { margin-left: auto; }

/* ── Theme card collapsed actions (.tcard-*) ──────────────────────────
   The per-theme action row that the header gear reveals, shared by
   Community Themes and My Themes. Driven by static/js/theme_card_actions.js;
   the markup contract is documented at the top of that file.

   *** SHARED BECAUSE THE TWO INLINE COPIES HAD ALREADY DIVERGED. *** This
   was .mt-actions/.mt-collapsed in my_themes' own <style>, and Community's
   pasted copy toggled a `tc-collapsed` class that no rule ever defined, so
   its gear did nothing. One set of rules, one page-neutral prefix -- `mt-`
   named a page and `tc-` is already the theme-COMMENTS namespace.

   NOT the shared .fold component: .fold collapses a card's whole BODY
   (`.fold > .gc-body { display:none }`). Here the meta line, the like/comment
   row and the comments section all stay visible and only the button row
   hides. Different job -- reusing .fold would have meant fighting it. */
/* ── Theme card creator identity row (.tcard-creator-*) ───────────────
   Avatar, name, level and XP for the person who MADE the theme, shown on
   cards for themes saved from someone else. Al, 2026-08-03: the Creators
   row layout, brought onto the theme card.

   *** SELF-MADE THEMES DELIBERATELY DO NOT GET THIS. *** They keep the
   compact "by you" line. Al's call: the avatar and pills only appear where
   they tell you something, and on a personal library most cards are yours --
   six of eight on the reference page. Repeating your own face down the
   column is noise, not identity.

   *** NO BORDER ON THE AVATAR. *** Explicit, because .cd-avatar on the
   Creators page has one and this was lifted from it. Do not add it back.

   Shared here rather than page-local because this is the card template for
   Community Themes and Creators too -- the exact split that stacked the
   Creators columns and left its preview unscaled. */
.tcard-creator { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 8px; }
.tcard-creator-avatar {
    width: 40px;
    height: 40px;
    border-radius: var(--avatar-radius, 4px);
    object-fit: cover;
    flex-shrink: 0;
}
.tcard-creator-info { flex: 1; min-width: 0; }
.tcard-creator-name {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    font-size: 0.92em;
    font-weight: 600;
    color: var(--text);
}
.tcard-creator-name a { color: inherit; text-decoration: none; }
.tcard-creator-name a:hover { color: var(--primary); }
/* The meta line sits under the name, inside the info column, so it aligns
   with the name rather than with the avatar. */
.tcard-creator-meta { font-size: 0.88em; color: var(--text-faint); margin-top: 2px; }

.tcard-actions { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 4px; }
.tcard.tcard-collapsed .tcard-actions { display: none; }

/* ── Theme card header row ────────────────────────────────────────────
   Title on the left, Preview / Apply / gear travelling together on the
   right, all on ONE line.

   *** WITHOUT THESE TWO RULES THE CONTROLS WRAP UNDER THE TITLE. ***
   .gc-header-center is a plain block (it is not display:flex anywhere --
   see :234) and `.gc-header-center h2` is `width: 100%` (:506). So an h2
   followed by buttons puts the h2 on its own full-width line and drops
   everything after it onto the next one. That is not a styling nicety; it
   is the difference between a one-line header and a two-line one.

   These lived in my_themes' inline <style> as `.mt-theme-card ...`, which
   is why My Themes looked right and Community -- rendering the same card
   with the same markup -- did not. Shared 2026-08-03.

   `flex: 1` + `min-width: 0` + ellipsis is what makes a long theme name
   truncate instead of shoving the buttons out of the card. It also means
   the title absorbs the free space, so the auto margins on .ct-cat-push
   and .kx-gear-end resolve to zero here -- they are inert in this header
   by design, and still correct if this rule is ever removed.
   The width:auto is deliberate: it overrides the width:100% at :506. */
/* ═══════════════════════════════════════════
   THEME PAGE GRID + LIVE PREVIEW PANE

   Moved out of community_themes.html's inline <style> 2026-08-03, when the
   Creators page grew the same two-column layout and preview pane.

   *** THE MARKUP WAS SHARED AND THE CSS WAS NOT, AND THAT FAILED VISIBLY. ***
   Creator Directory rendered .ct-grid and the whole #mini-preview-* pane
   with NOT ONE rule matching any of it: the two columns stacked, and the
   preview drew at its natural size instead of a scaled 1920x1080 box, so it
   spilled down the page. Nothing errored, and a check asserting
   `class="ct-grid"` is present passed the whole time -- a class in the
   markup proves nothing unless a rule matches it. Same shape as the
   corkboard's "a variable being defined proves nothing".

   #mini-preview-inner is a FIXED 1920x1080 with transform-origin top left;
   scaleMiniPreview() computes outer.offsetWidth / 1920 and scales it. Those
   two numbers are a contract with theme_preview.js -- do not make them
   responsive, or the scale factor stops matching what it paints.

   Page-local rules stay page-local: .ct-discover-card, .ct-cat-tag,
   .ct-head-swatch*, .ct-fav-btn are community_themes' own.
   ═══════════════════════════════════════════ */
.ct-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 16px;
    align-items: start;
}
.ct-col { display: flex; flex-direction: column; gap: 16px; }

/* Parks below the sticky chip row. --ct-catbar-dock-h already includes the
   gap above AND below the bar, so this does not hardcode it -- change the
   gap in the catbar block and every preview pane follows. */
#card-preview {
    position: sticky;
    top: calc(var(--ct-catbar-top) + var(--ct-catbar-dock-h));
}

#mini-preview-outer { overflow: hidden; }
#mini-preview-inner {
    width: 1920px;
    height: 1080px;
    overflow: hidden;
    transform-origin: top left;
    position: relative;
    display: flex;
    flex-direction: column;
}
/* Hidden until the first preview renders. My Themes used a separate
   #mt-mini-preview-wrap + .mt-visible class for this; the two pages did the
   same thing two ways. One mechanism now -- the driver sets visibility and
   opacity inline, and this is the resting state. */
#ct-mini-preview {
    visibility: hidden;
    opacity: 0;
    transition: opacity 0.2s;
}

#mini-preview-inner .card,
#mini-preview-inner .settings-section,
#mini-preview-inner .follow-card,
#mini-preview-inner .badge-card,
#mini-preview-inner .msg-card,
#mini-preview-inner .profile-post-card {
    background-color: var(--bg-card) !important;
    background-image: var(--card-bg-image, none) !important;
    background-size: cover !important;
    border-color: var(--card-border, var(--primary)) !important;
    border-style: var(--border-style, solid) !important;
    border-width: var(--border-width, 1px) !important;
    border-radius: var(--radius, 6px) !important;
    box-shadow: var(--card-shadow, none) !important;
}
#mini-preview-inner .card-header,
#mini-preview-inner .drag-handle {
    border-bottom: var(--card-divider, 1px solid var(--border)) !important;
    background: var(--card-header-bg, transparent) !important;
    background-image: var(--card-header-image, none) !important;
    background-size: var(--card-header-bg-size, cover) !important;
    padding: var(--card-header-padding, 10px 12px) !important;
    min-height: var(--card-header-height, auto) !important;
    color: var(--card-header-text, var(--text)) !important;
}
#mini-preview-inner .card-body {
    background: var(--card-body-bg, transparent) !important;
    background-image: var(--card-body-image, none) !important;
    background-size: var(--card-body-bg-size, cover) !important;
    background-position: top !important;
    padding: var(--card-body-padding, 12px) !important;
}
#mini-preview-inner .card-footer {
    background: var(--card-footer-bg, transparent) !important;
    background-image: var(--card-footer-image, none) !important;
    background-size: var(--card-footer-bg-size, cover) !important;
    min-height: var(--card-footer-height, 8px) !important;
    padding: var(--card-footer-padding, 0px) !important;
}
/* Merged in from my_themes' private pane block (2026-08-03). These six were
   NOT in the shared copy, so Community and Creators were previewing themes
   without honouring --transition-speed, --avatar-radius or --avatar-border,
   and badge SVGs sized wrongly. Same "shared markup, page-local CSS" split
   that stacked the Creators columns. */
#mini-preview-inner * {
    transition-duration: var(--transition-speed, 0.2s) !important;
}
#mini-preview-inner .avatar-placeholder img,
#mini-preview-inner .friend-avatar {
    border-radius: var(--avatar-radius, 0px) !important;
    border-color: var(--avatar-border, var(--primary)) !important;
    border-style: solid !important;
    border-width: var(--avatar-border-width, 0px) !important;
}
#mini-preview-inner #card-badges span svg { width: 100% !important; height: 100% !important; display: block; }
#mini-preview-inner .drag-handle { cursor: default !important; }
#mini-preview-inner .navbar {
    background: var(--navbar-bg, rgba(0,0,0,0.8)) !important;
    border-bottom-color: var(--navbar-border, var(--primary)) !important;
}
#te-preview-body-content {
    flex: 1;
    min-height: 0;
    position: relative;
    isolation: isolate;
}
/* The pane is a picture of a profile, not a working one -- every control
   inside is inert and dimmed so nothing invites a click. */
#mini-preview-inner { cursor: default !important; }
#mini-preview-inner .post-dots-btn,
#mini-preview-inner .pin-btn,
#mini-preview-inner .share-btn-wrap,
#mini-preview-inner .contact-buttons,
#mini-preview-inner .avatar-like-btn,
#mini-preview-inner .compose-box,
#mini-preview-inner .report-btn { pointer-events: none !important; opacity: 0.5; }
#mini-preview-inner .post-actions { pointer-events: none !important; }
#mini-preview-inner .profile-badge-grid { display: grid; grid-template-columns: repeat(8, minmax(0, 1fr)); gap: 6px; margin-top: 8px; overflow: hidden; }
#mini-preview-inner .badge-item { display: flex; flex-direction: column; align-items: center; gap: 4px; cursor: default; min-width: 0; overflow: hidden; }
#mini-preview-inner .badge-item > span:first-child { width: 100% !important; height: auto !important; aspect-ratio: 1; max-width: 2.5em; }

/* One column below 900px, and the preview goes away entirely -- a 1920px
   box scaled into a phone-width column is unreadable. */
@media (max-width: 900px) {
    .ct-grid { grid-template-columns: 1fr; }
    #card-preview { display: none; }
}

/* ── Theme card comments (.tc-*) ──────────────────────────────────────
   Shared by Community Themes and My Themes, alongside
   _theme_comments.html and static/js/theme_comments.js. Moved out of
   community_themes' inline <style> 2026-08-03 when My Themes gained
   comments -- the markup, the behaviour and the styling all travel
   together so a page cannot end up holding two of the three.

   *** display:none LIVES HERE, NOT ON THE ELEMENT. *** toggleThemeComments
   reads style.display and writes 'block'/'none'. An inline display:none in
   the markup would make the FIRST click read as already-open and close
   something that was never opened -- a dead first click, no error. */
.tc-comments-section { display: none; margin-top: 10px; }
.tc-comment-input-row { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
.tc-comment-input {
    flex: 1; background: rgba(255,255,255,0.05); border: 1px solid var(--border);
    border-radius: var(--btn-radius, 4px); color: var(--text); padding: 5px 12px;
    font-size: 0.78em; outline: none;
}
.tc-comment-input:focus { border-color: var(--primary); }
.tc-comment-submit {
    background: var(--primary); color: #000; border: none;
    border-radius: var(--btn-radius, 4px); padding: 5px 12px;
    cursor: pointer; font-size: 0.76em; font-weight: 600; white-space: nowrap;
}
.tc-comment-submit:hover { background: var(--primary-dark, var(--primary)); }

/* The PREVIEW PANE's header is a row too, for the same reason and with the
   same failure if it is not: .gc-header-center is a plain block and its h2
   is width:100%, so controls placed after the title drop to a second line.
   The pane is not a .tcard (it collapses nothing), so it needs saying
   separately.

   *** ONE ID, NOT TWO (2026-08-04). *** These carried #mt-preview-card as
   well, because My Themes named its pane differently to the other two pages.
   That page's pane is #card-preview now, so the second half of each selector
   matched nothing and was dropped. If a pane ever stops picking up its header
   row, check that its id is #card-preview before adding a selector here. */
.tcard .gc-header-center,
#card-preview .gc-header-center { display: flex; align-items: center; gap: 4px; }
#card-preview .gc-header-center h2 {
    width: auto;
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    margin: 0;
    font-size: 0.95em;
}

/* *** THE PANE CONTROLS AND THE CARD CONTROLS ARE THE SAME CONTROLS, SHOWN
   WHERE EACH IS USABLE. *** Below 900px both preview panes are display:none
   (see the breakpoint further down), so pane-only buttons would make Apply
   UNREACHABLE on a phone. Above 900px the pane is present and one pair of
   controls beats a pair on every card. Both sets are always in the markup;
   only visibility switches. */
.mt-pane-btn { display: none; }
@media (min-width: 901px) {
    .mt-pane-btn { display: inline-flex; align-items: center; }
    .mt-pane-btn[hidden] { display: none; }
    /* Card-level Preview/Apply give way to the pane's pair. */
    .mt-theme-card .mt-state-btn,
    .mt-theme-card .mt-applied-pill { display: none; }
}

.tcard .gc-header-center h2 {
    width: auto;
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    margin: 0;
    font-size: 0.95em;
}

/* ═══════════════════════════════════════════
   SHARE OVERLAY (.so-*) — the one share picker, static/js/share_overlay.js

   Sitewide component: profile posts, community posts and theme links all
   open this. It replaced three separate anchored dropdowns that had already
   drifted apart.

   *** IT IS AN OVERLAY BECAUSE ANCHORED MENUS CANNOT SURVIVE SCROLL HERE. ***
   The old one was position:fixed against a button, so scrolling detached it,
   and the fix for that was a capture-phase scroll listener that closed it --
   which meant scrolling the friend list closed the picker. Nothing here is
   anchored to anything, so neither problem exists to solve.
   ═══════════════════════════════════════════ */
.so-backdrop {
    position: fixed;
    inset: 0;
    z-index: 10000;
    /* Hardcoded, not themed. This is the layer whose whole job is to push the
       page back; a theme lightening it would defeat the modal. 0.82 + a blur
       so the page reads as out of focus rather than merely dimmed. */
    background: rgba(0, 0, 0, 0.82);
    backdrop-filter: blur(3px);
    -webkit-backdrop-filter: blur(3px);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
}
/* The page behind must not scroll while this is up. */
body.so-open { overflow: hidden; }

.so-card {
    width: 100%;
    max-width: 1200px;
    max-height: 86vh;
    display: flex;
    flex-direction: column;
    /* *** --bg-card IS rgba(0,0,0,0.5) -- HALF TRANSPARENT. *** Reading it
       directly put the profile page straight through the middle of this
       card: avatars, post text and headings from behind showing between the
       friend tiles, which reads as a rendering fault rather than a modal.

       Composited over an OPAQUE base instead, the same fix the sticky catbar
       needed on 2026-08-01. The gradient is a solid fill of --bg-card
       painted on top of --bg-dark, so the resolved colour is what a card
       looks like sitting on the page, and a theme that sets an opaque
       --bg-card simply covers the base.

       DO NOT "SIMPLIFY" THIS BACK TO `background: var(--bg-card)`. It looks
       redundant and is not. Third time this variable has caught something. */
    background-color: var(--bg-dark);
    background-image: linear-gradient(var(--bg-card), var(--bg-card));
    border: 1px solid var(--primary);
    border-radius: var(--radius-lg, 10px);
    box-shadow: 0 16px 60px rgba(0, 0, 0, 0.75);
    overflow: hidden;
}
.so-head {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 14px 18px;
    border-bottom: 1px solid var(--border);
    flex-shrink: 0;
}
.so-title { font-size: 0.95em; color: var(--text); font-weight: 600; flex: 1; min-width: 0; }
.so-x {
    background: none; border: none; color: var(--text-muted);
    font-size: 1.4em; line-height: 1; cursor: pointer; padding: 0 4px;
}
.so-x:hover { color: var(--primary); }

/* min-height:0 so the scroll area below can actually shrink inside the flex
   column -- without it the card grows past max-height instead. */
.so-body { padding: 14px 18px 0; overflow-y: auto; min-height: 0; flex: 1; }
.so-loading { padding: 22px 2px; color: var(--text-muted); font-size: 0.85em; }
.so-section {
    font-size: 0.70em; color: var(--text-faint);
    text-transform: uppercase; letter-spacing: 0.05em;
    padding: 4px 0 8px;
}
.so-divider { border-top: 1px solid var(--border); margin: 12px 0; }

/* SIX ACROSS, FOUR ROWS VISIBLE = 24 (Al's spec). The cap is on the SCROLL
   WRAPPER, not the grid, so row five onward scrolls rather than paginating --
   get_share_friends returns every friend in one response, so there is
   nothing to load more of. */
.so-grid {
    display: grid;
    grid-template-columns: repeat(6, minmax(0, 1fr));
    gap: 8px;
}
.so-scroll { max-height: 296px; overflow-y: auto; padding-right: 4px; }

.so-tile {
    display: flex; align-items: center; gap: 8px;
    padding: 7px 9px; cursor: pointer; min-width: 0;
    border: 1px solid var(--border); border-radius: var(--radius);
    background: rgba(255, 255, 255, 0.03);
    transition: border-color 0.15s, background 0.15s;
}
.so-tile:hover { border-color: var(--primary); background: rgba(255, 255, 255, 0.06); }
.so-tile:has(.so-cb:checked) { border-color: var(--primary); background: rgba(233, 69, 96, 0.12); }
.so-tile-done { opacity: 0.45; cursor: default; }
.so-cb { accent-color: var(--primary); width: 13px; height: 13px; flex-shrink: 0; }
.so-av {
    width: 26px; height: 26px; flex-shrink: 0; object-fit: cover;
    border-radius: var(--avatar-radius, 4px); border: 1px solid var(--border);
}
.so-av-blank { background: rgba(255, 255, 255, 0.08); display: inline-block; }
.so-name {
    font-size: 0.80em; color: var(--text);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
.so-flag { font-size: 0.65em; color: var(--text-faint); margin-left: auto; flex-shrink: 0; }

.so-foot {
    position: sticky; bottom: 0;
    display: flex; align-items: center; justify-content: flex-end; gap: 10px;
    padding: 14px 0; margin-top: 12px;
    border-top: 1px solid var(--border);
    /* Sticky over a scrolling list, so it has the same problem the card had:
       a half-transparent --bg-card lets friend tiles scroll visibly through
       the button row. Composited over the opaque base for the same reason. */
    background-color: var(--bg-dark);
    background-image: linear-gradient(var(--bg-card), var(--bg-card));
}
.so-count { font-size: 0.78em; color: var(--text-faint); margin-right: auto; }
.so-btn {
    background: var(--primary); color: #000; border: none;
    border-radius: var(--radius); padding: 6px 18px;
    cursor: pointer; font-size: 0.82em; font-weight: 600;
}
.so-btn-ghost {
    background: rgba(255, 255, 255, 0.06); color: var(--text);
    border: 1px solid var(--border); font-weight: 400;
}
.so-btn-ghost:hover { border-color: var(--primary); color: var(--primary); }

.vert-spacer {height:32px;width:2px;background: #e94560;}

@media (max-width: 900px) { .so-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 560px) { .so-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* ══════════════════════════════════════════
   BADGE PAGES (.badge-*, .badges-page)

   Shared by badges.html (your own badge locker, with the hint/streak cards)
   and user_badges.html (someone else's, read-only). These fifteen rules were
   DUPLICATED VERBATIM in both templates' <style> blocks. Checked for drift
   before promoting them: byte-identical, all fifteen, plus three identical
   rules inside each file's @media (max-width:600px). No reconciliation was
   needed -- but that was luck, not design, and it is exactly the state
   .cm-flush was in before it drifted.

   *** .badges-page IS A LAYOUT CLASS, NOT A WIDTH CLASS. *** It used to carry
   `max-width:1100px; margin:0 auto` too. Width now comes from .gc-w-wide on
   the same div -- see the gc-w-* tiers above, and DESIGN.md on not letting
   every page pick its own arbitrary pixel width. Both pages went 1100 -> 1200
   in that change.

   STAYS PAGE-LOCAL, deliberately: badges.html keeps .hint-*, .streak-*,
   .drag-ghost, .earned-check, .badge-select-cb, .badges-page-header and
   @keyframes hintPulse (its locker is editable and reorderable; the read-only
   page has none of that). user_badges.html keeps .badges-empty and
   .badges-page-sub a.
   ═══════════════════════════════════════════ */
.badges-page { padding: 0 0 40px; }
.badges-page-sub { text-align: center; font-size: 0.82em; color: var(--text-muted); margin-bottom: 24px; }
/* *** THE COMMON SUBSET ONLY -- THE GRAB CURSOR IS NOT HERE. *** This is the
   one rule of the fifteen that had DRIFTED between the two templates:
   badges.html carried `cursor: grab; user-select: none;` and
   user_badges.html did not, because only your own locker reorders. Promoting
   badges.html's version wholesale would have put a grab cursor on the
   read-only page's headers -- something that looks draggable and is not, with
   no error and nothing to see in a diff. The drag affordance stays local to
   badges.html alongside .badge-group-header:active. */
.badge-group-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
}
.badge-group-title { margin: 0; font-size: 0.85em; font-weight: 600; color: var(--primary); letter-spacing: 0.08em; text-transform: uppercase; }
.badge-group-count { font-size: 0.75em; color: var(--text-faint); }
.badge-group-cards {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
    gap: 16px;
}
.badge-card {
    position: relative;
    min-height: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
    gap: 4px;
    padding: 0;
    border-radius: var(--radius, 6px);
    border: 1px solid var(--border);
    text-align: center;
    transition: border-color 0.2s;
}
.badge-card.earned { border-color: var(--primary); background: rgba(255,255,255,0.03); }
.badge-card.locked { opacity: 0.3; filter: grayscale(80%) brightness(0.6); }
.badge-card.earned:hover { border-color: var(--primary); box-shadow: 0 0 8px rgba(233,69,96,0.2); }
/* *** margin: 0 auto IS THE CENTERING FIX, AND IT IS LOAD-BEARING. *** The
   parent is `.gc-body-center.text-center`, and text-align does NOT centre a
   block box — it centres INLINE content inside one. This element is a 64px
   fixed-width block (display:flex makes it a flex CONTAINER, not a flex item),
   so it sat hard against the left edge of every badge card while the name and
   description under it centred correctly. That mismatch is what made it look
   like a bug in one badge rather than in the shared rule.
   profile.css loads AFTER this file (base.html:9-10) and re-declares
   .badge-icon with font-size + margin-bottom. margin-bottom is a longhand and
   overrides only the bottom edge, so the `auto` on left/right survives — but
   if that rule ever grows a `margin:` SHORTHAND it will silently un-centre
   these again. */
.badge-icon { font-size: 1.6em; line-height: 1; width: 120px; height: 120px; margin: 0 auto; display: flex; align-items: center; justify-content: center; }
.badge-icon svg { width: 100%; height: 100%; display: block; }
.badge-name { font-size: 0.72em; font-weight: 600; color: var(--text); }
.badge-desc { font-size: 0.62em; color: var(--text-muted); line-height: 1.3; }
.badge-tier { font-size: 0.6em; color: var(--text-faint); margin-top: 2px; }

@media (max-width: 600px) {
    /* Was `padding: 0 10px 32px` in both files. The 10px side padding is gone
       for the same reason the 20px is above -- .gc-w-wide's
       width: calc(100% - 32px) owns the gutter now, at 16px a side. */
    .badges-page { padding: 0 0 32px; }
    .badge-group-cards { grid-template-columns: repeat(3, 1fr); gap: 8px; padding: 10px 0; }
    .badge-icon { width: 48px; height: 48px; font-size: 1.3em; }
}

/* ── The selected-8 grid ──────────────────────────────────────────────────
   The row at the top of /profile/badges/ holding the badges you have chosen
   to show, in the order they appear on your profile card. Drag to reorder.

   *** IT IS A MIRROR OF THE SELECTION, NOT A PLACE BADGES MOVE TO. *** The
   tiles below stay exactly where they are when you pin one. Badges sit in
   per-type rows that read as tier ladders (Carbon Steel -> Unobtainium, left
   to right, because the queryset is order_by('tier')), and lifting a badge out
   of its row to a pinned area would leave a gap in the ladder. Selection and
   arrangement are two different things here; the checkbox owns one and this
   grid owns the other.

   *** THE CARDS THEMSELVES NEED NO CSS OF THEIR OWN. *** They are clones of
   the ladder tiles, so .badge-card / .gc-friend-tile already style them, and
   .results-grid (shared with friends.html, followers.html, following.html and
   search) already lays out repeat(8, 1fr) at exactly the cap this page uses.
   Everything below is the SECTION around them plus the one size override.

   The old .badge-strip / .badge-strip-item / .badge-strip-ghost rules are gone
   with the icon strip they styled (2026-08-05) -- they were used nowhere else.
   No border and no card chrome here either: friends.html's pinned section is a
   label over a grid, and Al asked for the same. */
.badge-pinned-label {
    font-size: 0.75em;
    color: var(--primary);
    text-transform: uppercase;
    letter-spacing: 1px;
    margin-bottom: 10px;
}
.badge-pinned-empty { font-size: 0.8em; color: var(--text-faint); }

/* Matches .results-grid .follow-avatar's 96px, so a pinned badge reads at the
   same weight as a pinned friend. The ladder tiles stay at 64px -- there are
   ~10 rows of them and this is one row of 8. */
.results-grid .badge-icon { width: 120px; height: 120px; }

/* ── Empty slot in the pinned strip ───────────────────────────────────────
   Holds the strip open to eight cells so an unfilled selection reads as
   "five left" instead of dead space, and so a new account can see the feature
   at all. Al: "almost like it's a ghost until populated."

   *** ITS LOOK IS NOT HERE. *** Border, radius and background come from the
   shared PLACEHOLDER / DRAG GHOST rule further up this file, which .badge-slot
   splits with .drag-ghost so an empty slot and a drop target are the same
   object visually. Restyle it there, not here -- a second copy of those three
   declarations is how the old .drag-ghost ended up defined in two files.

   Only the SIZING is local: a slot has no content, so nothing gives it a box.
   100% of the grid cell, which the row's real cards have already sized. When
   all eight are empty there are no real cards to size the row, and the
   .results-grid track collapses -- that is the state to check first if the
   zero-case ever looks wrong.

   NO data-pin-key, which is what keeps these out of the reorder POST, and
   Sortable's `draggable: '[data-pin-key]'` is what stops them being picked
   up. Both live in badge_strip.js. */
.badge-slot {
    height: 100%;
    width: 100%;
}

/* The pinned card is a clone, so it arrives carrying .badge-card's hover rule
   AND the ladder tile's cursor. Say plainly that this one is draggable. */
#pinned-badge-grid .badge-card { cursor: grab; }
#pinned-badge-grid .badge-card:active { cursor: grabbing; }
#pinned-badge-grid .badge-select-cb { cursor: pointer; }

/* ── Platform rows (Steam / Xbox) ────────────────────────────────────────
   1fr 5fr — Al's "1/6 the space" for the link badge and "5/6" for the full
   API spread. Fractions, not pixels, so the split holds at every width the
   .gc-w-wide tier allows instead of only at the one where the numbers happened
   to line up. That is the same mistake .mt-grid made with `1fr 570px`.

   Founder and Verified used to share this strip as two more equal cards. They
   were taken off the badge pages entirely on 2026-08-05 — Al: "founder doesn't
   flaunted that way. and verified is still a pipedream." Both are still earned
   and still counted; they simply do not render. If either comes back it does
   NOT belong in this row, which is specifically about linked accounts.

   *** THE ROW IS THE CARD NOW, NOT A GRID OF TWO CARDS (2026-08-05). *** This
   class used to BE the 1fr 5fr grid, wrapping two gc-containers that had their
   own border and background stripped by inline styles. It is a single
   .gc-container .fold today; the split moved to .bpr-head / .bpr-split, which
   carry the SAME track sizes so the second heading lines up with the column it
   labels. Change one, change the other — that alignment is the whole look. */
.badge-platform-row {
    background: rgba(0, 0, 0, 0.5);
    margin-bottom: 16px;
    border: solid var(--primary) 1px;
    border-radius: 6px;
}
.bpr-head {
    display: grid;
    grid-template-columns: 1fr 5fr;
    align-items: center;
    text-align: left;
    width: 100%;
}
.bpr-head-activity {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    min-width: 0;
}
.bpr-tools,
.badge-group-tools,
.mt-title-tools {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-shrink: 0;
}
/* The gear spins on hover and while the card is open. Deliberately slower than
   .fold-caret's 0.18s — the caret answers a click, the gear is an invitation. */
.bpr-gear {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.95em;
    line-height: 1;
    color: var(--text-faint);
    text-decoration: none;
    opacity: 0.7;
    transition: transform 0.6s ease, color 0.2s, opacity 0.2s;
}
.bpr-gear:hover {
    color: var(--primary);
    opacity: 1;
    transform: rotate(180deg);
}
.fold.open .bpr-gear { color: var(--primary); opacity: 1; }
.badge-platform-row > .gc-header:hover .bpr-gear { opacity: 1; }

/* The body split — same tracks as .bpr-head. align-items:start so the badge
   tile keeps its natural height instead of stretching to match a spread that
   is three game rows tall. */
.bpr-split {
    display: grid;
    grid-template-columns: 1fr 5fr;
    gap: 16px;
    align-items: start;
    width: 100%;
}
.bpr-spread { min-width: 0; }

@media (max-width: 900px) {
    /* Below the tier's comfortable width the 1/6 column cannot hold a badge
       tile without squeezing it, so the two stack. The HEADER collapses to a
       single track too — otherwise "Steam" and "Steam Activity" would still be
       splitting a width neither one fits in. */
    .bpr-split { grid-template-columns: 1fr; }
    .bpr-head { grid-template-columns: 1fr; }
    /* With one track the first heading is redundant: the row below it is the
       badge, and "Steam / Steam Activity" stacked reads as two sections that
       are not there. Keep the one that carries the caret and the gear. */
    .bpr-head > .badge-group-title { display: none; }
}

/* The spread itself. .steam-games / .steam-game / .steam-friend-name /
   .steam-friend-game come from profile.css (loaded sitewide by base.html) and
   are reused rather than re-declared -- see _platform_spread.html. Only what
   that file does NOT already provide is defined here. */
.ps-ident {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 12px;
    margin-top: 12px;
}
.ps-ident-who { display: flex; align-items: center; gap: 10px; }
.ps-avatar { width: 44px; height: 44px; border-radius: 50%; object-fit: cover; }
.ps-idle { font-size: 0.8em; color: var(--text-faint); }
.ps-online { color: var(--online, #00ff88); font-size: 0.75em; }

.ps-stats { display: flex; gap: 18px; flex-wrap: wrap; }
.ps-stat { display: flex; flex-direction: column; align-items: center; line-height: 1.2; }
.ps-stat-n { font-size: 1.05em; font-weight: 700; color: var(--primary); }
.ps-stat-l { font-size: 0.66em; color: var(--text-faint); text-transform: uppercase; letter-spacing: 0.06em; }

.ps-section-label {
    font-size: 0.72em;
    color: var(--text-faint);
    text-transform: uppercase;
    letter-spacing: 0.09em;
    margin: 14px 0 8px;
}
.ps-note { font-size: 0.8em; color: var(--text-muted); font-style: italic; padding: 6px 0; }

.ps-friends { display: grid; grid-template-columns: repeat(6, 1fr); gap: 10px; }
.ps-friend { text-decoration: none; text-align: center; }
.ps-friend img { width: 40px; height: 40px; border-radius: 50%; object-fit: cover; }
@media (max-width: 600px) {
    .ps-friends { grid-template-columns: repeat(3, 1fr); }
    .ps-stats { gap: 12px; }
}
