/* Three themes, picked from a button in the navbar (public/js/theme.js) and
   persisted in localStorage - :root is the default/fallback (dark, the
   original and still the site's "no preference saved yet" look), and
   html[data-theme="..."] overrides the same variable set for light/special.
   Every color used anywhere in this file that's meant to be theme-aware
   goes through one of these variables - a purchased cosmetic effect
   (avatar borders, banners, badges, pseudo-effects) intentionally does NOT,
   since those represent an owned identity that should look the same
   regardless of which theme the viewer happens to have picked. */
:root {
  --bg: #1e1f22;
  --surface: #2b2d31;
  --surface-2: #313338;
  --surface-3: #383a40;
  --border: #3f4147;
  --text: #f2f3f5;
  --text-muted: #949ba4;
  --accent: #5865f2;
  --accent-hover: #4752c4;
  --accent-2: #00a8fc;
  --danger: #da373c;
  --danger-hover: #a12828;
  --success-bg: #06311c;
  --success-text: #23a55a;
  --error-bg: #3c1618;
  --error-text: #f2555a;
  --radius: 8px;
  --shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
  --mono: 'Consolas', 'SFMono-Regular', Menlo, monospace;
}

/* Light - plain, sober, classic light mode. No motif, no decorative
   flourish - just a clean high-contrast recolor (near-black text on
   white/light-gray, one restrained blue used consistently for both
   buttons and links) so it stays readable above all else. */
:root[data-theme="light"] {
  --bg: #f5f6f8;
  --surface: #ffffff;
  --surface-2: #eceef1;
  --surface-3: #dde1e6;
  --border: #d0d4da;
  --text: #1a1d21;
  --text-muted: #565c65;
  --accent: #2563eb;
  --accent-hover: #1d4ed8;
  --accent-2: #1d4ed8;
  --danger: #dc2626;
  --danger-hover: #b91c1c;
  --success-bg: #dcfce7;
  --success-text: #15803d;
  --error-bg: #fee2e2;
  --error-text: #b91c1c;
  --shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}

/* Special - "Arcade": flat electric yellow + hot magenta on near-black,
   deliberately NOT navy/violet (the rejected "Aurora" glow theme) and NOT
   green (the rejected "Terminal" theme). No blur or glow anywhere - the
   one flourish is a single hard (never blurred) offset shadow on the logo
   and on buttons, like an arcade-cabinet marquee cutout. Plain flat
   background, no grid/texture. */
:root[data-theme="special"] {
  --bg: #101014;
  --surface: #1c1c24;
  --surface-2: #26262f;
  --surface-3: #32323e;
  --border: #3c3c48;
  --text: #f5f3e8;
  --text-muted: #93909f;
  --accent: #ffcc00;
  --accent-hover: #e0b400;
  --accent-2: #ff2f6e;
  --danger: #ff2f6e;
  --danger-hover: #d91f58;
  --success-bg: #1a3322;
  --success-text: #6be090;
  --error-bg: #3a1622;
  --error-text: #ff6f96;
  --shadow: 4px 4px 0 rgba(0, 0, 0, 0.7);
}
:root[data-theme="special"] .brand-logo {
  filter: drop-shadow(3px 3px 0 rgba(255, 47, 110, 0.9));
}
:root[data-theme="special"] .btn {
  box-shadow: 3px 3px 0 rgba(0, 0, 0, 0.7);
}
:root[data-theme="special"] .btn:hover {
  transform: translate(-1px, -1px);
  box-shadow: 4px 4px 0 rgba(0, 0, 0, 0.7);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  line-height: 1.5;
  font-size: 15px;
}

a { color: var(--accent-2); text-decoration: none; }
a:hover { color: var(--accent); text-decoration: underline; }

/* 832px = 1040px trimmed ~20% on the right, freeing up room for the
   floating stack (profile, presence, mod-log link, online members,
   newest signups - all merged into one). 957px = 832px widened 15% again,
   but ONLY to the left: the right edge (up against the floating stack)
   stays exactly where the 832px box put it - margin-right keeps the same
   formula as a plain `margin: 0 auto` at 832px would give, and the extra
   125px (957-832) comes entirely out of margin-left instead. */
.container { max-width: 957px; margin-left: 0; margin-right: 0; padding: 0 16px; }
@media (min-width: 833px) {
  .container {
    margin-left: max(0px, calc((100vw - 832px) / 2 - 125px));
    margin-right: max(0px, calc((100vw - 832px) / 2));
    padding-right: max(0px, calc(174px - (100vw - 832px) / 2));
  }
}

.skip-link {
  /* Fixed, not absolute: this has no closer positioned ancestor, and
     absolute would make it resolve against the whole document instead of
     the viewport, so tabbing to it while scrolled down would jump it
     off-screen. */
  position: fixed; left: -999px; top: 0; background: var(--accent); color: #fff;
  padding: 8px 14px; border-radius: 0 0 8px 0; z-index: 100;
}
.skip-link:focus { left: 0; }

/* Visually hidden but still readable by screen readers - used where a
   label is redundant on screen (e.g. an icon button already says enough)
   but still needed for accessibility. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

.site-header {
  background: var(--surface);
  border-bottom: 2px solid var(--accent);
  position: sticky; top: 0; z-index: 10;
}
/* padding-right leaves room for .centre-nav-link, which is position:
   absolute (removed from this flex flow entirely, anchored to .site-header
   instead - see its own comment) and would otherwise sit on top of the
   search box or notif bell instead of beside them. */
.header-inner { display: flex; align-items: center; gap: 16px; padding: 10px 90px 10px 16px; flex-wrap: wrap; }
.brand { display: inline-flex; align-items: center; }
.brand:hover { text-decoration: none; }
.brand-logo { height: 50px; width: auto; display: block; }

.main-nav-left { display: flex; gap: 4px; font-size: 0.88rem; }
.main-nav-left a { color: var(--text-muted); padding: 6px 10px; border-radius: 6px; }
.main-nav-left a:hover { background: var(--surface-2); text-decoration: none; color: var(--text); }
.main-nav-left a.active { color: var(--accent); background: var(--surface-2); }

.search-form { flex: 1; min-width: 140px; max-width: 320px; position: relative; }
.search-form input {
  width: 100%; padding: 7px 12px; border-radius: 999px; border: 1px solid var(--border);
  background: var(--surface-2); color: var(--text); box-sizing: border-box;
}
.search-dropdown {
  position: absolute; top: calc(100% + 6px); left: 0; right: 0; z-index: 20;
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  box-shadow: var(--shadow); overflow: hidden;
}
.search-dropdown a {
  display: block; padding: 9px 14px; color: var(--text); font-size: 0.86rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  border-bottom: 1px solid var(--border);
}
.search-dropdown a:last-of-type { border-bottom: none; }
.search-dropdown a:hover, .search-dropdown a.active { background: var(--surface-3); color: var(--accent); text-decoration: none; }
.search-dropdown-more {
  display: block; padding: 8px 14px; color: var(--text-muted); font-size: 0.8rem;
  background: var(--surface-2); text-align: center;
}
.search-dropdown-more:hover { color: var(--accent); text-decoration: none; }

/* Anchored to .site-header itself (position: sticky - a valid containing
   block), not .header-inner - see the HTML comment in partials/head.ejs
   for why: .header-inner is capped at .container's width, so a
   margin-left: auto inside it could only ever reach that box's edge, not
   the true screen edge. right: 18px matches .floating-stack's own offset
   for visual continuity. */
.centre-nav-link {
  position: absolute; right: 18px; top: 50%; transform: translateY(-50%);
  color: var(--text-muted); padding: 6px 10px; border-radius: 6px; font-size: 0.88rem;
}
.centre-nav-link:hover, .centre-nav-link.active { background: var(--surface-2); color: var(--danger); text-decoration: none; }

.main-nav { display: flex; align-items: center; gap: 12px; font-size: 0.9rem; flex-wrap: wrap; }
.main-nav a { color: var(--text); display: inline-flex; align-items: center; gap: 4px; }
.btn-nav {
  background: var(--accent); color: #fff !important; padding: 6px 14px; border-radius: 999px; font-weight: 600;
}
.btn-nav:hover { background: var(--accent-hover); text-decoration: none; }

/* Notification bell, top-right of the search bar in the navbar (see
   partials/head.ejs) - same dropdown pattern as .search-dropdown just
   above, anchored under a round icon button instead of a text input. */
.notif-bell-wrap { position: relative; }
.notif-bell-btn {
  position: relative; background: none; border: none; color: var(--text-muted); cursor: pointer;
  width: 38px; height: 38px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center;
  transition: color 0.15s ease, background-color 0.15s ease;
}
.notif-bell-btn:hover { color: var(--text); background: var(--surface-2); }
.notif-badge {
  position: absolute; top: 2px; right: 2px; min-width: 16px; height: 16px; padding: 0 3px;
  border-radius: 999px; background: var(--danger); color: #fff; font-size: 0.65rem; font-weight: 700;
  display: flex; align-items: center; justify-content: center; border: 2px solid var(--surface);
}

/* Cycles dark -> light -> special -> dark on click (public/js/theme.js).
   Same round-icon-button treatment as .notif-bell-btn, sitting right next
   to it - unconditional (outside the logged-in/guest split in
   partials/head.ejs), the theme picker works with or without an account.
   Only one of the three icons is ever visible, chosen purely by the
   html[data-theme] the inline bootstrap script (partials/head.ejs) already
   set before first paint - no flash of the wrong icon either. */
.theme-toggle-btn {
  background: none; border: none; color: var(--text-muted); cursor: pointer;
  width: 38px; height: 38px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center;
  transition: color 0.15s ease, background-color 0.15s ease;
}
.theme-toggle-btn:hover { color: var(--text); background: var(--surface-2); }
.theme-icon { display: none; }
html[data-theme="dark"] .theme-icon-dark,
html[data-theme="light"] .theme-icon-light,
html[data-theme="special"] .theme-icon-special { display: block; }
/* Without this, `display: flex` above wins the cascade over the browser's
   own `[hidden] { display: none }` rule, so setting the hidden ATTRIBUTE
   (badge.hidden = true in notifications.js) silently stops working and a
   "0" badge stays visibly on screen. */
.notif-badge[hidden] { display: none; }
.notif-dropdown {
  position: absolute; top: calc(100% + 8px); right: 0; z-index: 20; width: min(340px, 92vw);
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  box-shadow: var(--shadow); overflow: hidden;
}
.notif-dropdown-header {
  display: flex; justify-content: space-between; align-items: center; padding: 10px 14px;
  border-bottom: 1px solid var(--border); font-weight: 700; font-size: 0.86rem;
}
.notif-dropdown-header a { font-weight: 400; font-size: 0.8rem; }
.notif-dropdown-list { max-height: 360px; overflow-y: auto; }
.notif-dropdown-list .empty-state { padding: 20px; }

.notif-item {
  display: flex; align-items: flex-start; gap: 10px; padding: 10px 14px; color: var(--text);
  border-bottom: 1px solid var(--border); font-size: 0.84rem;
}
.notif-item:last-child { border-bottom: none; }
.notif-item:hover { background: var(--surface-2); text-decoration: none; }
.notif-item-unread { background: color-mix(in srgb, var(--accent) 10%, var(--surface)); }
.notif-item .avatar-wrap { flex-shrink: 0; }
.notif-item-text { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.notif-item-time { flex-shrink: 0; color: var(--text-muted); font-size: 0.74rem; white-space: nowrap; }

/* Full history page (/notifications) - same item look, stacked as cards. */
.notif-page-list { display: flex; flex-direction: column; gap: 8px; }
.notif-page-list .notif-item {
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  align-items: center; box-shadow: var(--shadow);
}

/* Mobile-only toggle for .floating-stack (see below and public/js/
   mobile-account.js) - hidden on desktop, where the stack is always
   visible in its own right-hand column. */
.mobile-account-toggle {
  display: none; background: none; border: none; color: var(--text-muted); cursor: pointer;
  width: 38px; height: 38px; border-radius: 50%; align-items: center; justify-content: center;
  /* Above .floating-stack.mobile-open (z-index: 50) - the panel opens
     right under the header and would otherwise cover this button's own
     hit area, so tapping it again to close never reaches the button. */
  position: relative; z-index: 51;
}
.mobile-account-toggle:hover { color: var(--text); background: var(--surface-2); }

/* Connected/viewer count, inside .floating-stack above the mod-log link.
   A vertical accent bar instead of a dot - a dot reads as a status light
   (needs a legend), a bar reads as a quiet accent stripe - kept neutral
   grey rather than a colored gradient so it doesn't compete with the
   actual number as the thing your eye lands on. Label-left/count-right
   (large, bold) rather than a run-on sentence. */
.presence-block { display: flex; flex-direction: column; gap: 4px; width: 100%; }
.floating-presence {
  display: flex; align-items: center; gap: 10px; width: 100%; box-sizing: border-box;
  background: var(--surface-2); border: 1px solid var(--border); box-shadow: var(--shadow);
  border-radius: 9px; padding: 8px 12px; font-size: 0.78rem; color: var(--text-muted); font-weight: 600;
}
.presence-bar { width: 3px; align-self: stretch; border-radius: 2px; flex-shrink: 0; background: var(--surface-3); }
.presence-row { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; flex: 1; min-width: 0; }
.presence-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.presence-count { flex-shrink: 0; font-weight: 800; font-size: 1.25em; color: var(--text); }
/* Guests: no frame of its own, just a plain centered line under the
   connected box, not sharing its background/border. */
.presence-guests { text-align: center; color: var(--text-muted); font-size: 0.78rem; font-weight: 600; }
.presence-guests .presence-count { font-size: 1em; color: var(--text-muted); margin-left: 4px; }

.member-list-card {
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
  box-shadow: var(--shadow); padding: 12px;
}
.member-list-title {
  margin: 0 0 10px; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--text-muted); font-weight: 700;
}
.member-list-empty { margin: 0; font-size: 0.78rem; color: var(--text-muted); }
.member-avatar-grid, .member-avatar-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.member-avatar-link { display: block; border-radius: 50%; transition: transform 0.1s ease; }
.member-avatar-link:hover { transform: scale(1.1); }
.member-avatar-more { position: relative; }
.member-avatar-more summary {
  list-style: none; cursor: pointer; width: 32px; height: 32px; border-radius: 50%;
  background: var(--surface-3); color: var(--text-muted); font-size: 0.72rem; font-weight: 700;
  display: flex; align-items: center; justify-content: center;
}
.member-avatar-more summary::-webkit-details-marker, .member-avatar-more summary::marker { display: none; content: ''; }
.member-avatar-more summary:hover { color: var(--text); }
.member-avatar-more-panel {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 20; width: 180px; max-height: 240px; overflow-y: auto;
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px; box-shadow: var(--shadow); padding: 6px;
}
.member-avatar-more-row { display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: 6px; color: var(--text); font-size: 0.82rem; overflow: hidden; }
.member-avatar-more-row span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.member-avatar-more-row:hover { background: var(--surface-3); text-decoration: none; }

/* Account info (profile card, points, messages/settings/profile icons,
   admin link, logout), who's around right now, the mod-log link, who's
   online and the newest members - ALL live in this one fixed stack,
   deliberately outside <main class="container"> (see partials/head.ejs) -
   pinned just below the sticky header at the viewport's own right edge,
   independent of the centered content column, shown on every page. */
.floating-stack {
  position: fixed; top: 66px; right: 18px; z-index: 15;
  display: flex; flex-direction: column; align-items: stretch; gap: 10px;
  /* Fills exactly the gutter beside the centered 832px container: grows
     with the viewport once the container's own centering margin provides
     more room than the floor below, and holds at a fixed comfortable
     width under that (the matching .container padding-right further up
     reserves precisely this much space, so it never overlaps). */
  width: clamp(150px, calc((100vw - 832px) / 2 - 34px), 320px);
}
.floating-profile-card {
  width: 100%; background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
  box-shadow: var(--shadow); padding: 14px 10px; text-align: center;
}
.fpc-top { display: flex; flex-direction: column; align-items: center; gap: 8px; margin-bottom: 10px; }
.fpc-identity { display: flex; flex-direction: column; align-items: center; gap: 2px; min-width: 0; width: 100%; }
.fpc-name { font-weight: 700; overflow-wrap: anywhere; }
.fpc-points { font-size: 0.78rem; color: #ffcf7d; font-weight: 600; }
.fpc-links { display: flex; flex-direction: column; gap: 8px; border-top: 1px solid var(--border); padding-top: 10px; font-size: 0.82rem; align-items: center; }
.fpc-links a { color: var(--text); display: flex; align-items: center; gap: 6px; }
.fpc-links a:hover { color: var(--accent); text-decoration: none; }
/* Profile / settings / messages as a horizontal row of icon buttons
   instead of a stacked text list. */
.fpc-links-icons { flex-direction: row; justify-content: space-around; gap: 4px; }
.fpc-links-icons a { position: relative; padding: 8px; border-radius: 8px; }
.fpc-links-icons a:hover { background: var(--surface-3); color: var(--accent); }
.conv-badge-dot {
  position: absolute; top: 4px; right: 4px; width: 8px; height: 8px; border-radius: 50%;
  background: var(--accent); border: 2px solid var(--surface);
}
.fpc-admin-link {
  display: block; margin-top: 10px; text-align: center; font-size: 0.8rem; color: var(--text-muted);
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 999px; padding: 5px 12px;
}
.fpc-admin-link:hover { color: var(--accent-2); text-decoration: none; background: var(--surface-3); }
.fpc-logout-form { margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border); text-align: center; }
.fpc-logout-btn {
  background: none; border: none; padding: 4px 10px; border-radius: 6px; cursor: pointer; font: inherit;
  color: var(--text-muted); font-size: 0.82rem; text-decoration: none; transition: color 0.15s ease, background-color 0.15s ease;
}
.fpc-logout-btn:hover { color: var(--danger); background: var(--surface-3); text-decoration: none; }

.floating-modlog-link {
  display: block; width: 100%; box-sizing: border-box; text-align: center;
  font-size: 0.78rem; color: var(--text-muted); background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 9px; padding: 5px 12px;
}
.floating-modlog-link:hover { color: var(--accent-2); text-decoration: none; }

main.container { padding-top: 22px; padding-bottom: 60px; min-height: 60vh; }

h1 { font-size: 1.6rem; margin: 0 0 6px; overflow-wrap: anywhere; word-break: break-word; }
h2 { font-size: 1.15rem; }
.subtitle { color: var(--text-muted); font-size: 0.88rem; margin: 0 0 12px; }

.breadcrumb { color: var(--text-muted); font-size: 0.85rem; margin-bottom: 6px; }
.mono-meta { font-family: var(--mono); font-size: 0.78rem; color: var(--text-muted); }

.page-header { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; margin-bottom: 18px; flex-wrap: wrap; }
/* A flex item's default min-width is `auto`, which stops long unbroken
   text (e.g. a title with no spaces) from ever wrapping - it just forces
   the row wider instead. min-width: 0 lets overflow-wrap actually kick in. */
.page-header > div:first-child { min-width: 0; flex: 1 1 320px; }

.flash { padding: 10px 16px; border-radius: var(--radius); margin-bottom: 18px; font-size: 0.95rem; }
.flash-success { background: var(--success-bg); color: var(--success-text); }
.flash-error { background: var(--error-bg); color: var(--error-text); }

.form-errors { background: var(--error-bg); color: var(--error-text); border-radius: var(--radius); padding: 12px 18px; margin-bottom: 16px; }
.form-errors ul { margin: 0; padding-left: 18px; }

/* Buttons */
.btn, .btn-secondary, .btn-danger, .link-button {
  display: inline-flex; align-items: center; gap: 6px; border: none; border-radius: 6px;
  padding: 8px 16px; font-size: 0.92rem; cursor: pointer; font-family: inherit;
}
.btn { background: var(--accent); color: #fff; font-weight: 600; }
.btn:hover { background: var(--accent-hover); text-decoration: none; color: #fff; }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.btn-secondary { background: var(--surface-3); color: var(--text); border: 1px solid var(--border); }
.btn-secondary:hover { background: var(--border); }
.btn-danger { background: var(--danger); color: #fff; }
.btn-danger:hover { background: var(--danger-hover); }
.link-button { background: none; color: var(--accent-2); padding: 0; text-decoration: underline; font-size: 0.85rem; }
.inline-form { display: inline-block; margin: 0; }

/* "Le Centre" board banner (views/index.ejs, board === 'centre' only) - a
   plain warning-tinted strip, just enough to make it visually obvious this
   isn't the main board without redesigning the whole page around it. */
.centre-banner {
  background: color-mix(in srgb, var(--danger) 12%, var(--surface)); border: 1px solid color-mix(in srgb, var(--danger) 35%, var(--border));
  border-radius: var(--radius); padding: 14px 18px; margin-bottom: 18px;
}
.centre-banner h1 { margin: 0 0 4px; font-size: 1.3rem; }
.centre-banner p { margin: 0; color: var(--text-muted); font-size: 0.88rem; }

/* Tables */
.thread-table { width: 100%; table-layout: fixed; border-collapse: collapse; background: var(--surface); border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow); }
/* Column widths below are specific to the board index's 4-column layout
   (Sujet/Auteur/Nb/Dernière activité) - scoped to .board-table, not the
   shared .thread-table base, since admin's member/shadow tables reuse
   .thread-table with a different column count and would otherwise inherit
   these nth-child widths by accident. */
.board-table th:nth-child(1), .board-table td:nth-child(1) { width: 54%; }
.board-table th:nth-child(2), .board-table td:nth-child(2) { width: 18%; }
/* Long pseudos in the author column: truncate with an ellipsis instead of
   wrapping - .uname's own overflow-wrap:anywhere (meant for the wider post
   sidebar) was breaking a long name across several lines here, which read
   as broken given how narrow this column is. */
.board-table td:nth-child(2) .uname {
  display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; overflow-wrap: normal; word-break: normal; vertical-align: bottom;
}
.board-table th:nth-child(3), .board-table td:nth-child(3) { width: 8%; text-align: right; }
.board-table th:nth-child(4), .board-table td:nth-child(4) { width: 20%; text-align: right; }
.thread-table th, .thread-table td { padding: 6px 14px; border-bottom: 1px solid var(--border); text-align: left; font-size: 0.9rem; }
.thread-table th { background: var(--surface-2); color: var(--text-muted); font-weight: 600; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.03em; }
.thread-table tr:last-child td { border-bottom: none; }
.thread-table tbody tr:nth-child(even):not(.thread-pinned):not(.thread-new-for-viewer) { background: var(--surface-2); }
.thread-pinned { background: color-mix(in srgb, var(--accent) 8%, var(--surface)); }
/* A topic you've posted in, with replies since your last visit - clears
   itself the moment you open it again (routes/forum.js re-stamps
   thread_visits on every thread view). Wins over .thread-pinned/even-row
   striping since it's the more actionable state for THIS viewer. */
.thread-new-for-viewer { background: color-mix(in srgb, var(--danger) 18%, var(--surface)); }
.thread-title-link { font-weight: 600; overflow-wrap: anywhere; word-break: break-word; }
/* Native :visited only matches the EXACT href that was clicked - the
   index's title link (/sujet/ID/slug) and its "last activity" link
   (/sujet/ID?page=N#post-M) point at the same thread but never the same
   URL, so clicking one never greyed the other out, and neither did a
   notification link (yet another URL/anchor). .thread-title-visited
   (index.ejs) is server-tracked instead (thread_visits, stamped on every
   thread view regardless of which link got you there - see
   routes/forum.js), so it's consistent no matter the entry point. :visited
   stays too, as a fallback for guests (thread_visits is member-only). */
.thread-title-link:visited, .thread-title-link.thread-title-visited { color: var(--text-muted); }
/* Quick-glance activity indicator: thread.png while a topic is still
   quiet, thread2.png once it picks up 20+ replies, pin.png overrides both
   while the topic is pinned - unpinning reverts it automatically since
   this is just a plain conditional on the current .pinned state. */
.activity-dot {
  display: inline-block; width: 16px; height: 16px; margin-right: 8px;
  vertical-align: middle; flex-shrink: 0;
}
.pin-icon-inline {
  display: inline-block; width: 22px; height: 22px; margin-right: 6px;
  vertical-align: middle;
}
.thread-meta { color: var(--text-muted); font-size: 0.85rem; margin: 2px 0 0; }
.empty-state { color: var(--text-muted); padding: 20px; text-align: center; }

.th-with-action { display: flex; align-items: center; justify-content: flex-end; gap: 8px; }
.reload-btn {
  background: none; border: none; color: var(--text-muted); cursor: pointer;
  font-size: 1.3rem; width: 34px; height: 34px; border-radius: 6px; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center; transition: color 0.15s ease, background-color 0.15s ease;
}
.reload-btn:hover { color: var(--text); background: var(--surface-3); }
.reload-btn svg { transition: transform 0.4s ease; }
.reload-btn:hover svg { transform: rotate(120deg); }
.last-reply-link { color: var(--text-muted); }
.last-reply-link:hover { color: var(--accent-2); }

.pagination { display: flex; justify-content: center; align-items: center; gap: 18px; margin: 22px 0; color: var(--text-muted); }

/* ---- Cards used by new-thread / reply / edit-post forms ---- */
.card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; box-shadow: var(--shadow); padding: 22px 24px; }
.card-header { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; margin-bottom: 4px; flex-wrap: wrap; }
.card-header h1, .card-header h2 { margin: 0; }
.point-hint { background: var(--surface-3); border: 1px solid var(--border); color: #ffcf7d; font-size: 0.78rem; font-weight: 700; padding: 3px 10px; border-radius: 999px; white-space: nowrap; }

/* Threads / posts */
.mod-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.post-list { display: flex; flex-direction: column; gap: 14px; margin-top: 16px; }

/* "New replies since you loaded this page" banner (public/js/new-posts.js) -
   sits between the post list and pagination, only on the last page. */
.new-posts-banner {
  position: relative; margin: 22px 0 8px;
  background: var(--surface-2); border: 1px solid var(--accent);
  border-radius: 10px; box-shadow: var(--shadow);
}
.new-posts-banner-btn {
  width: 100%; background: none; border: none; cursor: pointer; color: var(--text); text-align: left;
  padding: 16px 16px 11px; display: flex; flex-direction: column; align-items: flex-start; gap: 2px; font: inherit;
}
.new-posts-count { font-weight: 700; font-size: 0.92rem; }
.new-posts-cta { font-size: 0.78rem; color: var(--text-muted); transition: color 0.15s ease; }
.new-posts-banner-btn:hover .new-posts-cta { color: var(--accent); }

/* The avatars "peek" over the top edge of the banner - only their top
   half shows, the bottom half clipped away by the half-height wrapper so
   it reads as hidden behind the banner itself. Left-aligned rather than
   centered, sitting above the text instead of floating independently. */
.new-posts-peek {
  position: absolute; top: -17px; left: 16px;
  display: flex; pointer-events: none;
}
.new-posts-peek .avatar-wrap {
  width: 34px; height: 17px; overflow: hidden; border-radius: 17px 17px 0 0;
  margin-left: -10px; box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.35);
}
.new-posts-peek .avatar-wrap:first-child { margin-left: 0; }
.new-posts-peek .avatar-img { width: 34px; height: 34px; border-radius: 0; }

/* Newly revealed posts slide down as one group, like a little window
   opening - the wrapper matches .post-list's own flex/gap so spacing
   stays identical to posts that were there from the start. */
.new-posts-reveal {
  display: flex; flex-direction: column; gap: 14px; margin-top: 14px;
  animation: new-posts-slide-down 0.4s cubic-bezier(0.2, 0.8, 0.3, 1);
  transform-origin: top center;
}
@keyframes new-posts-slide-down {
  0% { opacity: 0; transform: translateY(-28px) scaleY(0.85); }
  100% { opacity: 1; transform: translateY(0) scaleY(1); }
}
.post {
  display: grid; grid-template-columns: 168px 1fr; gap: 0;
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
  box-shadow: var(--shadow);
  /* No overflow:hidden here (used to clip to the rounded corners below) -
     it was also clipping the "Signaler" popup (.report-details form,
     position:absolute) to invisibility any time it tried to render outside
     this box. The two children get their own matching corner radii
     instead, so the rounded-card look is unchanged but nothing inside gets
     silently clipped. */
}
.post-author {
  display: flex; flex-direction: column; align-items: center; gap: 6px; text-align: center;
  padding: 20px 12px; background: var(--surface-2); border-right: 1px solid var(--border);
  border-radius: 12px 0 0 12px;
  min-width: 0; /* grid item default min-width is auto, which stops long content from wrapping */
}
/* Username and its badge (if any) side by side on one line, not the badge
   dropping to a line of its own below (post-author is a flex column, so
   without this wrapper they'd be two separate stacked flex items). */
.uname-row { display: inline-flex; align-items: center; justify-content: center; gap: 3px; max-width: 100%; min-width: 0; vertical-align: middle; }
.uname { font-weight: 700; font-size: 1.02rem; overflow-wrap: anywhere; word-break: break-word; min-width: 0; }
.uname-compact { font-weight: 400; font-size: inherit; }
/* Only shrunk when the pseudo itself is long enough to wrap in the fixed-
   width post sidebar (see partials/username.ejs, which lowers the length
   threshold when a badge is present) - a short pseudo stays at normal size
   instead of every name being forced small. */
.uname-long { font-size: 0.74rem; }
.user-badge { flex: 0 0 auto; }
.rank-pill {
  font-size: 0.72rem; color: var(--text-muted); margin-top: 2px;
}
.post-count-line { font-size: 0.72rem; color: var(--text-muted); margin-top: 4px; }
.role-badge {
  font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.03em; padding: 2px 8px;
  border-radius: 999px; background: var(--surface-3); color: var(--text-muted);
}
.role-badge.role-admin { background: #4a1f1f; color: #ffb3b3; }
.role-badge.role-moderator { background: #4a3c10; color: #ffd873; }

.post-body-wrap { padding: 16px 20px; min-width: 0; display: flex; flex-direction: column; }
.post-toolbar { display: flex; gap: 10px; align-items: baseline; color: var(--text-muted); font-size: 0.78rem; margin-bottom: 10px; padding-bottom: 10px; }
.post-index { display: none; }
.post-permalink { color: var(--text-muted); }
.edited-tag { font-style: italic; }
.post-body { word-wrap: break-word; overflow-wrap: anywhere; flex: 1; }
.post-body :first-child { margin-top: 0; }
/* Regular [img] links render at their original size (just never wider than
   the post itself, so they can't blow out the page layout horizontally).
   RisiBank [sticker]s are deliberately capped much smaller - they're
   meant to read as a small reaction sticker, not an inline photo. */
/* Applies to forum post bodies AND private-message bubbles - both render
   BBCode through the exact same pipeline, so both need the same image
   rules (a PM sticker was otherwise rendering at full native size, since
   this used to only target .post-body). */
.post-body img, .message-body img { max-width: 100%; border-radius: 6px; margin: 4px 0; }
/* Small enough to sit inline with a line of text (like an emoji) instead
   of stretching the line box tall enough that it visually reads as its
   own paragraph, and no vertical margin (the general rule above adds one
   for block-ish standalone images, which is exactly what pushes a sticker
   off the text line). baseline (not middle): the BOTTOM of the sticker
   sits on the text baseline, same as a normal inline character would -
   sizing it up doesn't break that alignment, the line just grows taller
   to fit it, same as a browser does with a large emoji. */
.post-body img.sticker-img, .message-body img.sticker-img {
  max-width: 100px; max-height: 100px; object-fit: contain; border-radius: 4px;
  vertical-align: baseline; margin: 0 2px;
}
.bbcode-code { background: var(--surface-2); padding: 10px 12px; border-radius: 6px; overflow-x: auto; font-family: var(--mono); font-size: 0.85rem; }
.bbcode-quote { border-left: 3px solid var(--accent); margin: 8px 0; padding: 4px 14px; background: var(--surface-2); border-radius: 0 6px 6px 0; }
.bbcode-quote cite { display: block; font-size: 0.78rem; color: var(--text-muted); font-style: normal; margin-bottom: 2px; }
.bbcode-quote .bbcode-quote { background: var(--surface-3); }
.bbcode-list { margin: 6px 0; padding-left: 22px; }
.bbcode-center { text-align: center; }
.bbcode-u { text-decoration: underline; }
.bbcode-spoiler summary { cursor: pointer; color: var(--accent-2); font-size: 0.85rem; }
.post-body-deleted { color: var(--text-muted); font-style: italic; }
.post-signature { margin-top: 14px; padding-top: 10px; border-top: 1px dashed var(--border); color: var(--text-muted); font-size: 0.82rem; white-space: pre-wrap; }

.post-actions {
  display: flex; gap: 8px; align-items: flex-start; margin-top: 14px; padding-top: 12px; font-size: 0.8rem; flex-wrap: wrap; justify-content: flex-end;
}
.post-actions .inline-form { display: contents; }
.post-actions a, .post-actions .link-button {
  color: var(--text-muted); text-decoration: none; background: var(--surface-2); border: 1px solid var(--border);
  padding: 5px 12px; border-radius: 999px; cursor: pointer; font: inherit; line-height: 1.3;
  transition: color 0.15s ease, background-color 0.15s ease, border-color 0.15s ease;
}
.post-actions a:hover, .post-actions .link-button:hover {
  color: var(--accent); background: var(--surface-3); border-color: var(--accent); text-decoration: none;
}
/* Report opens as a small popover anchored below the button instead of
   pushing the rest of the post around inline. */
.report-details { position: relative; }
.report-details summary {
  list-style: none; cursor: pointer; color: var(--text-muted); background: var(--surface-2); border: 1px solid var(--border);
  padding: 5px 12px; border-radius: 999px; font-size: 0.8rem;
  transition: color 0.15s ease, background-color 0.15s ease, border-color 0.15s ease;
}
.report-details summary::-webkit-details-marker, .report-details summary::marker { display: none; content: ''; }
.report-details summary:hover { color: var(--danger); background: var(--surface-3); border-color: var(--danger); }
.report-details[open] summary { color: var(--danger); border-color: var(--danger); }
.report-details form {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 20; width: min(260px, 80vw);
  display: flex; flex-direction: column; gap: 8px; background: var(--surface); border: 1px solid var(--border);
  border-radius: 10px; padding: 12px; box-shadow: var(--shadow);
}
.report-details textarea { min-height: 60px; resize: vertical; }

/* BBCode toolbar */
.bbcode-toolbar { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; background: var(--surface-2); border: 1px solid var(--border); border-bottom: none; border-radius: 8px 8px 0 0; padding: 8px; }
.bbcode-toolbar button {
  background: var(--surface-3); border: 1px solid var(--border); border-radius: 4px; color: var(--text);
  padding: 5px 9px; font-size: 0.85rem; cursor: pointer; min-width: 30px;
  display: inline-flex; align-items: center; justify-content: center; gap: 5px;
}
.bbcode-toolbar button:hover { border-color: var(--accent); }
.tb-sep { width: 1px; align-self: stretch; background: var(--border); margin: 0 4px; }
.tb-colors { display: flex; gap: 4px; align-items: center; }
.tb-color { width: 22px; height: 22px; border-radius: 50%; border: 2px solid var(--border); padding: 0; cursor: pointer; }
.tb-color-custom {
  width: 22px; height: 22px; border-radius: 50%; border: 2px solid var(--border); cursor: pointer;
  overflow: hidden; display: inline-block; position: relative;
  background: conic-gradient(red, yellow, lime, cyan, blue, magenta, red);
}
.tb-color-input { position: absolute; inset: -4px; width: 30px; height: 30px; padding: 0; border: none; cursor: pointer; opacity: 0; }
.tb-size {
  background: var(--surface-3); border: 1px solid var(--border); border-radius: 4px; color: var(--text);
  padding: 5px 6px; font-size: 0.8rem; cursor: pointer;
}
.tb-stickers { font-weight: 600; display: inline-flex; align-items: center; gap: 5px; }
.tb-stickers img { border-radius: 3px; }
.bbcode-form { max-width: 100%; }
.bbcode-form textarea {
  width: 100%; min-height: 140px; border-radius: 0 0 8px 8px !important;
  border-top: none !important;
}
.reply-form .bbcode-form-actions, .stack-form .bbcode-form-actions { display: flex; align-items: center; gap: 12px; margin-top: 12px; }

.bbcode-preview-box { margin-top: 14px; }
.bbcode-preview-label {
  margin: 0 0 6px; font-size: 0.72rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.04em; color: var(--text-muted);
}
.bbcode-preview-content {
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px;
  padding: 12px 16px; min-height: 40px;
}
.bbcode-preview-content .empty-state { padding: 0; text-align: left; }

/* RisiBank modal */
.risibank-modal {
  position: fixed; inset: 0; background: rgba(0,0,0,0.6); z-index: 1000;
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
.risibank-modal[hidden] { display: none; }
.risibank-modal-inner { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); width: 100%; max-width: 640px; box-shadow: var(--shadow); }
.risibank-modal-header { display: flex; justify-content: space-between; align-items: center; padding: 10px 16px; border-bottom: 1px solid var(--border); font-weight: 600; }
.risibank-modal-header span { display: inline-flex; align-items: center; gap: 6px; }
.risibank-modal-header img { border-radius: 3px; }
.risibank-frame-slot { padding: 8px; }
.risibank-frame-slot iframe { display: block; }

/* Forms - applies everywhere, not just inside .stack-form, so a plain
   <input>/<textarea> dropped into any form (the inline new-thread title
   field, the BBCode reply box, etc.) is never left with the browser's
   default white-on-black control styling. */
input[type="text"], input[type="email"], input[type="password"], input[type="search"],
input[type="number"], input[type="file"], textarea, select {
  padding: 8px 12px; border-radius: 6px; border: 1px solid var(--border);
  background: var(--surface); color: var(--text); font-family: inherit; font-size: 0.92rem;
}
textarea { resize: vertical; }
input[type="text"]:focus, input[type="email"]:focus, input[type="password"]:focus,
input[type="search"]:focus, input[type="number"]:focus, textarea:focus, select:focus {
  outline: 2px solid var(--accent); outline-offset: -1px;
}

.stack-form { display: flex; flex-direction: column; gap: 6px; max-width: 620px; }
.stack-form label { font-weight: 600; margin-top: 8px; font-size: 0.88rem; }
.stack-form .btn { margin-top: 12px; align-self: flex-start; }
.checkbox-field { display: flex !important; align-items: center; gap: 8px; font-weight: 400 !important; }
.checkbox-field input[type="checkbox"] { width: 16px; height: 16px; flex-shrink: 0; }
.form-actions { display: flex; gap: 10px; margin-top: 12px; }
.field-hint { color: var(--text-muted); font-size: 0.78rem; margin: 2px 0; }

.reply-section { margin-top: 28px; }
.reply-form { margin-top: 10px; max-width: 100%; }
[data-cooldown-ms] { margin-top: 12px; }

.auth-form-wrap { max-width: 400px; margin: 0 auto; }

/* ---- Homemade "find the mirrored tile" CAPTCHA (utils/captcha.js,
   public/js/puzzle-captcha.js) - a popup modal, opened only when a gated
   form is actually submitted (see partials/captcha.ejs, which just marks
   the form with data-needs-captcha and a couple of hidden fields). ---- */
.puzzle-required-hint { margin: 6px 0 0; font-size: 0.78rem; color: var(--text-muted); }

.captcha-modal {
  position: fixed; inset: 0; background: rgba(0, 0, 0, 0.65); z-index: 1000;
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
.captcha-modal[hidden] { display: none; }
.captcha-modal-inner {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  width: 100%; max-width: 340px; box-shadow: var(--shadow); padding: 16px;
  animation: captcha-pop-in 0.2s cubic-bezier(0.2, 0.8, 0.3, 1);
}
@keyframes captcha-pop-in {
  from { opacity: 0; transform: scale(0.92) translateY(8px); }
  to { opacity: 1; transform: scale(1) translateY(0); }
}
.captcha-modal.captcha-shake .captcha-modal-inner { animation: captcha-shake 0.4s ease; }
@keyframes captcha-shake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-8px); }
  40% { transform: translateX(7px); }
  60% { transform: translateX(-5px); }
  80% { transform: translateX(3px); }
}
.captcha-modal-header { display: flex; justify-content: space-between; align-items: center; font-weight: 700; margin-bottom: 10px; }
.captcha-modal-hint { margin: 0 0 12px; font-size: 0.84rem; color: var(--text-muted); }
.captcha-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.captcha-tile {
  background: none; border: 2px solid var(--border); border-radius: 10px; padding: 4px;
  cursor: pointer; display: flex; align-items: center; justify-content: center;
  transition: border-color 0.15s ease, transform 0.15s ease, background-color 0.15s ease;
}
.captcha-tile:hover:not(:disabled) { border-color: var(--accent-2); transform: translateY(-2px); }
.captcha-tile:disabled { cursor: default; }
.captcha-tile img { display: block; width: 100%; height: auto; }
.captcha-tile-picked { border-color: var(--accent); }
.captcha-tile-correct { border-color: var(--success-text); background: color-mix(in srgb, var(--success-text) 16%, transparent); }
.captcha-tile-wrong { border-color: var(--danger); background: color-mix(in srgb, var(--danger) 16%, transparent); }
.captcha-modal-status { min-height: 1.2em; margin: 10px 0 0; font-size: 0.84rem; text-align: center; color: var(--text-muted); }
.captcha-modal-status-ok { color: var(--success-text); font-weight: 700; }
.captcha-modal-status-error { color: var(--error-text); }

/* ---- Profile ---- */
.profile-banner {
  height: 180px; border-radius: 12px 12px 0 0; background: var(--surface-2);
  background-size: cover; background-position: center; position: relative; z-index: 0;
  border: 1px solid var(--border); border-bottom: none;
}
.profile-card { border: 1px solid var(--border); border-radius: 12px; box-shadow: var(--shadow); margin-bottom: 18px; background: var(--surface); }
.profile-identity { position: relative; z-index: 1; display: flex; flex-direction: column; align-items: center; text-align: center; padding: 0 20px 18px; margin-top: -60px; }
.profile-identity .avatar-wrap { border: 5px solid var(--surface); box-shadow: 0 4px 14px rgba(0,0,0,0.5); background: var(--surface); }
.profile-identity h1 { margin: 10px 0 0; display: flex; align-items: center; gap: 8px; }
.rank-line-big { font-size: 1rem; color: var(--accent); font-weight: 600; margin: 4px 0 0; }
.profile-stats { display: flex; gap: 18px; color: var(--text-muted); margin: 10px 0 0; font-size: 0.9rem; flex-wrap: wrap; justify-content: center; }
.profile-bio { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 14px 18px; margin-bottom: 14px; }
.profile-posts summary { cursor: pointer; font-weight: 700; padding: 4px 0; color: var(--text); }
.profile-posts summary:hover { color: var(--accent); }
.profile-post-item { border-bottom: 1px solid var(--border); padding: 10px 0; }
.profile-post-item:last-child { border-bottom: none; }

/* Preset profile banners (CSS-only, no image upload needed) */
.banner-banner_nuit { background: linear-gradient(135deg, #0f0c29, #302b63, #24243e); }
.banner-banner_feu { background: linear-gradient(135deg, #7f1d1d, #ff5c35, #f5b301); }
.banner-banner_ocean { background: linear-gradient(135deg, #032f4a, #0f6a8f, #22d3ee); }
.banner-banner_foret { background: linear-gradient(135deg, #052e16, #166534, #4ade80); }
.banner-banner_grille {
  background-color: #0a0a12;
  background-image: linear-gradient(rgba(56,189,248,0.25) 1px, transparent 1px), linear-gradient(90deg, rgba(56,189,248,0.25) 1px, transparent 1px);
  background-size: 28px 28px;
}
/* A perfectly uniform dot grid reads as a polka-dot pattern, not a night
   sky - real stars scatter at irregular sizes/positions/brightness. One
   large, hand-placed tile (not a small repeating unit) avoids the
   robotic-grid look while still tiling seamlessly. */
.banner-banner_etoiles {
  background-color: #05050f;
  background-image:
    radial-gradient(1.4px 1.4px at 18px 24px, rgba(255,255,255,0.95), transparent),
    radial-gradient(1px 1px at 64px 58px, rgba(255,255,255,0.6), transparent),
    radial-gradient(1.8px 1.8px at 112px 18px, rgba(255,255,255,0.9), transparent),
    radial-gradient(1px 1px at 150px 70px, rgba(255,255,255,0.5), transparent),
    radial-gradient(2px 2px at 190px 40px, rgba(168,85,247,0.85), transparent),
    radial-gradient(1px 1px at 230px 82px, rgba(255,255,255,0.7), transparent),
    radial-gradient(1.6px 1.6px at 270px 22px, rgba(56,189,248,0.8), transparent),
    radial-gradient(1px 1px at 40px 90px, rgba(255,255,255,0.4), transparent),
    radial-gradient(1.3px 1.3px at 295px 65px, rgba(255,255,255,0.75), transparent),
    linear-gradient(180deg, #0b0a22, #05050f 55%, #0d0721);
  background-repeat: repeat;
  background-size: 320px 110px;
}

/* Avatars & cosmetics */
.avatar-wrap { display: inline-flex; width: var(--avatar-size, 48px); height: var(--avatar-size, 48px); border-radius: 12px; }
.avatar-img { width: 100%; height: 100%; border-radius: 10px; object-fit: cover; display: block; }
.avatar-dot { width: 100%; height: 100%; border-radius: 10px; background: var(--surface-3); }
.avatar-preview-row { display: flex; gap: 20px; align-items: flex-start; flex-wrap: wrap; }

.ab-border_or { padding: 3px; background: linear-gradient(135deg, #f5b301, #ffe08a); border-radius: 13px; }
.ab-border_argent { padding: 3px; background: linear-gradient(135deg, #c0c0c0, #f1f1f1); border-radius: 13px; }
.ab-border_bronze { padding: 3px; background: linear-gradient(135deg, #cd7f32, #e8a35c); border-radius: 13px; }
.ab-border_neon { padding: 3px; background: #22d3ee; border-radius: 13px; box-shadow: 0 0 10px #22d3ee, 0 0 22px #22d3ee, 0 0 34px #0891b2; animation: neon-pulse 1.5s ease-in-out infinite; }
.ab-border_glace {
  padding: 3px; border-radius: 13px; background: linear-gradient(135deg, #ffffff, #7dd3fc, #e0f2fe, #7dd3fc);
  background-size: 250% 250%; box-shadow: 0 0 8px #bae6fd; animation: fire-shift 3.5s ease infinite;
}
.ab-border_poison { padding: 3px; background: #22c55e; border-radius: 13px; box-shadow: 0 0 10px #22c55e, 0 0 18px #16a34a; animation: neon-pulse 2.2s ease-in-out infinite; }
.ab-border_feu { padding: 3px; border-radius: 13px; background: linear-gradient(135deg, #ff5c35, #ffcf3d, #ff5c35); background-size: 300% 300%; animation: fire-shift 2.5s ease infinite; }

/* Legendary border: the rotating rainbow ring lives on a ::before layer
   BEHIND the avatar image, and only its `transform` is animated (never a
   color filter) - so the ring spins while the avatar photo itself is never
   recolored or altered. */
.ab-border_legendaire { position: relative; padding: 3px; border-radius: 13px; background: var(--surface-3); }
.ab-border_legendaire::before {
  content: ''; position: absolute; inset: -3px; border-radius: 15px; z-index: 0;
  background: conic-gradient(from 0deg, #ff5c35, #ffcf3d, #22d3ee, #a855f7, #ff5c35);
  animation: spin-ring 3s linear infinite;
}
.ab-border_legendaire .avatar-img { position: relative; z-index: 1; }

@keyframes neon-pulse { 0%, 100% { box-shadow: 0 0 6px currentColor; } 50% { box-shadow: 0 0 16px currentColor, 0 0 26px currentColor; } }
@keyframes fire-shift { 0%, 100% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } }
@keyframes spin-ring { to { transform: rotate(360deg); } }

/* Avatar shapes - placed after border rules so they win the border-radius
   cascade when both a border and a shape are equipped; descendant
   selectors on .avatar-img override the base rounded-square radius. */
.shape_cercle { border-radius: 50%; }
.shape_cercle .avatar-img, .shape_cercle .avatar-dot { border-radius: 50%; }
.shape_cercle.ab-border_legendaire::before { border-radius: 50%; }
.shape_carre { border-radius: 4px; }
.shape_carre .avatar-img, .shape_carre .avatar-dot { border-radius: 2px; }
.shape_carre.ab-border_legendaire::before { border-radius: 6px; }
.shape_hexagone, .shape_hexagone .avatar-img, .shape_hexagone .avatar-dot {
  border-radius: 0; clip-path: polygon(25% 5%, 75% 5%, 100% 50%, 75% 95%, 25% 95%, 0% 50%);
}

.pseudo-effect_arc-en-ciel { background: linear-gradient(90deg, #ff5c35, #ffcf3d, #22d3ee, #a855f7, #ff5c35); background-size: 300% auto; -webkit-background-clip: text; background-clip: text; color: transparent !important; animation: rainbow-move 4s linear infinite; }
@keyframes rainbow-move { to { background-position: 300% center; } }
.pseudo-effect_neon { text-shadow: 0 0 4px currentColor, 0 0 10px currentColor; animation: neon-text-pulse 1.8s ease-in-out infinite; }
@keyframes neon-text-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.6; } }
.pseudo-effect_glitch { position: relative; animation: glitch-skew 3s infinite; }
@keyframes glitch-skew { 0%, 96%, 100% { transform: skewX(0); } 97% { transform: skewX(-3deg); } 98% { transform: skewX(3deg); } }
.pseudo-effect_feu { background: linear-gradient(90deg, #7f1d1d, #ff5c35, #f5b301, #ff5c35, #7f1d1d); background-size: 300% auto; -webkit-background-clip: text; background-clip: text; color: transparent !important; animation: rainbow-move 3s linear infinite; }
.pseudo-effect_glace { background: linear-gradient(90deg, #0369a1, #7dd3fc, #f0f9ff, #7dd3fc, #0369a1); background-size: 300% auto; -webkit-background-clip: text; background-clip: text; color: transparent !important; animation: rainbow-move 3.4s linear infinite; }
.pseudo-effect_or-brillant { background: linear-gradient(90deg, #92650a, #f5b301, #fff3c4, #f5b301, #92650a); background-size: 300% auto; -webkit-background-clip: text; background-clip: text; color: transparent !important; animation: rainbow-move 2.6s linear infinite; }


/* One single draggable canvas, not a per-category list with a sidebar.
   .skill-canvas-viewport is the fixed-size "window" (overflow: hidden);
   .skill-canvas is the actual (large, maxRadius*2 square) surface that
   gets panned via `transform: translate()` in public/js/skill-tree.js.
   Every node's left/top is an absolute pixel position computed once in
   utils/shop.js's radial layout (inline style, per node) - not something
   CSS or JS lays out at render time. */
.skill-canvas-viewport {
  position: relative; height: 72vh; min-height: 420px;
  overflow: hidden; border: 1px solid var(--border); border-radius: 14px;
  background: radial-gradient(circle at center, var(--surface-2) 0%, var(--surface) 70%);
  cursor: grab; touch-action: none; box-shadow: var(--shadow);
}
.skill-canvas-viewport.dragging { cursor: grabbing; }
/* Wider on the LEFT only - same asymmetric-widening trick .container
   itself already uses (see its own comment above): width: auto lets the
   browser grow the box to fill whatever negative margin-left opens up,
   while margin-right stays 0 so the right edge never moves from where
   .container already puts it (flush against the floating stack's gutter).
   Reclaims the otherwise-unused left gutter for the one page that's
   actually a big pannable canvas and can use the extra room. */
@media (min-width: 833px) {
  .skill-canvas-viewport {
    width: auto;
    margin-left: calc(-1 * max(0px, (100vw - 832px) / 2 - 125px) + 20px);
    margin-right: 0;
  }
}
/* transform-origin: 0 0 (not the CSS default 50% 50%) - public/js/
   skill-tree.js's wheel-zoom math assumes scale() pivots on the canvas's
   own top-left corner, matching how panX/panY (plain viewport-space pixel
   offsets) are computed. */
.skill-canvas { position: absolute; top: 0; left: 0; transform-origin: 0 0; will-change: transform; }
.skill-canvas-edges { position: absolute; top: 0; left: 0; pointer-events: none; overflow: visible; }
/* Solid = a path you've actually taken (leads to an owned node); dashed
   and dim = not yet walked. Unlocking a node "fills in" its dashed line
   in place (public/js/shop-tree.js animates stroke-dashoffset), which is
   the whole point of the transition - a lit-up path, not a page reload. */
.skill-edge { stroke: var(--border); stroke-width: 2; transition: stroke 0.4s ease, opacity 0.4s ease; }
.skill-edge-dim { stroke-dasharray: 5 5; opacity: 0.35; }
.skill-edge-filling { stroke: var(--accent-2); }

/* Every state - owned, unlockable, locked - is the SAME compact circle
   (matches the locked silhouette's size/shape exactly, not the other way
   around): no rectangular card, no always-visible name/price/button. Info
   lives in the native title tooltip; buying is a click on the circle
   itself. transform: translate(-50%,-50%) centers it exactly on its
   (left, top) point regardless of content. */
.skill-node {
  position: absolute; width: 64px; height: 64px; transform: translate(-50%, -50%);
  background: var(--surface); border: 2px solid var(--node-accent, var(--border));
  border-radius: 50%; overflow: hidden; box-shadow: var(--shadow);
  display: flex; align-items: center; justify-content: center;
  transition: transform 0.15s ease, border-color 0.15s ease, opacity 0.15s ease, box-shadow 0.15s ease;
}
.skill-node:hover { transform: translate(-50%, -50%) scale(1.08); z-index: 2; }
.skill-node-owned { border-color: color-mix(in srgb, var(--success-text) 65%, var(--node-accent, var(--border))); }
.skill-node-unlockable { border-color: var(--node-accent, var(--accent-2)); box-shadow: var(--shadow), 0 0 0 3px color-mix(in srgb, var(--node-accent, var(--accent-2)) 25%, transparent); }
.skill-node-unlockable-poor { opacity: 0.75; box-shadow: var(--shadow); }
.skill-node-clickable { cursor: pointer; }
.skill-node-clickable:active { transform: translate(-50%, -50%) scale(0.94); }
/* The root - the very first skill, dead center - reads as the tree's
   starting point without needing a separate decorative element: a bit
   bigger than everything else, with a soft halo. */
.skill-node-hub { width: 72px; height: 72px; box-shadow: var(--shadow), 0 0 0 6px color-mix(in srgb, var(--node-accent, var(--accent)) 15%, transparent); }
.skill-node-hub:hover { transform: translate(-50%, -50%) scale(1.06); }

/* Locked nodes show nothing but a silhouette until reachable - the tree is
   meant to be discovered node by node as you unlock outward, not read like
   a spoiled map on arrival (see shop-node.ejs `revealed`). */
.skill-node-locked { opacity: 0.5; border-style: dashed; }
.skill-node-locked:hover { opacity: 0.8; transform: translate(-50%, -50%); }
.skill-node-icon { display: flex; color: var(--text-muted); }
.skill-node-icon svg { width: 22px; height: 22px; }
.skill-node-mystery-lock { position: absolute; bottom: 2px; right: 2px; color: var(--text-muted); background: var(--surface); border-radius: 50%; padding: 2px; }
.skill-node-swatch { width: 100%; height: 100%; border-radius: 50%; }
.skill-node-emoji { font-size: 1.5rem; line-height: 1; }
.skill-node-effect-preview {
  display: flex; align-items: center; justify-content: center; width: 100%; height: 100%;
  font-size: 0.6rem; font-weight: 700; padding: 0 4px; text-align: center; overflow: hidden;
  white-space: nowrap; text-overflow: ellipsis;
}
.skill-node-banner-preview { display: block; width: 100%; height: 100%; }
.skill-node-check {
  position: absolute; bottom: -1px; right: -1px; color: #fff; background: var(--success-text);
  border-radius: 50%; padding: 2px; box-shadow: 0 0 0 2px var(--surface);
}

/* The purchase-success pop (public/js/shop-tree.js swaps the node's
   outerHTML from its old state to the new one, then adds this class for
   one animation cycle). */
@keyframes skill-node-pop {
  0% { transform: translate(-50%, -50%) scale(0.55); }
  60% { transform: translate(-50%, -50%) scale(1.18); }
  100% { transform: translate(-50%, -50%) scale(1); }
}
.skill-node-popping { animation: skill-node-pop 0.4s cubic-bezier(0.2, 0.8, 0.3, 1.2); }
.skill-node-pending { animation: skill-node-pulse 0.8s ease-in-out infinite; }
@keyframes skill-node-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
.skill-node-error { animation: skill-node-shake 0.4s ease; }
@keyframes skill-node-shake {
  0%, 100% { transform: translate(-50%, -50%); }
  20% { transform: translate(calc(-50% - 5px), -50%); }
  40% { transform: translate(calc(-50% + 5px), -50%); }
  60% { transform: translate(calc(-50% - 3px), -50%); }
  80% { transform: translate(calc(-50% + 3px), -50%); }
}

/* Preview popup - same overlay pattern as .captcha-modal (click the
   backdrop or the X to close, Escape works too - see public/js/
   shop-tree.js). Opens on click of an unlockable node instead of buying
   directly; the actual purchase only happens from the button in here. */
.skill-preview-modal {
  position: fixed; inset: 0; background: rgba(0, 0, 0, 0.65); z-index: 1000;
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
.skill-preview-modal[hidden] { display: none; }
.skill-preview-card {
  position: relative; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  width: 100%; max-width: 260px; box-shadow: var(--shadow); padding: 20px 16px 16px;
  display: flex; flex-direction: column; align-items: center; gap: 8px; text-align: center;
  animation: captcha-pop-in 0.2s cubic-bezier(0.2, 0.8, 0.3, 1);
}
.skill-preview-close {
  position: absolute; top: 8px; right: 8px; background: none; border: none; color: var(--text-muted);
  font-size: 1rem; line-height: 1; cursor: pointer; padding: 6px; border-radius: 6px;
}
.skill-preview-close:hover { color: var(--text); background: var(--surface-3); }
.skill-preview-visual {
  width: 88px; height: 88px; border-radius: 50%; background: var(--surface-2); border: 2px solid var(--border);
  display: flex; align-items: center; justify-content: center; overflow: hidden; margin-top: 6px;
}
.skill-preview-visual .skill-node-swatch { width: 100%; height: 100%; }
.skill-preview-visual .skill-node-emoji { font-size: 2.4rem; }
.skill-preview-visual .skill-node-icon svg { width: 34px; height: 34px; }
.skill-preview-visual .skill-node-effect-preview { font-size: 0.9rem; }
.skill-preview-card h3 { margin: 0; font-size: 1rem; }
.skill-preview-price {
  display: inline-flex; align-items: center; background: var(--surface-3); border: 1px solid var(--border);
  border-radius: 999px; padding: 3px 12px; font-weight: 700; font-size: 0.82rem; color: #ffcf7d; margin: 0;
}
#skill-preview-buy { width: 100%; justify-content: center; margin-top: 4px; }
#skill-preview-buy:disabled { opacity: 0.5; cursor: not-allowed; }

@media (max-width: 700px) {
  .skill-canvas-viewport { height: 62vh; }
  .skill-node { width: 52px; height: 52px; }
}

.equip-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 14px; }
.equip-form { display: flex; flex-direction: column; gap: 4px; }
.equip-form label { font-size: 0.82rem; color: var(--text-muted); }

.points-adjust-form { display: inline-flex; gap: 4px; margin-left: 8px; }
.points-adjust-input { width: 60px; padding: 3px 6px !important; }

/* Search */
.search-hero { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 20px; margin-bottom: 20px; box-shadow: var(--shadow); }
.search-hero form { display: flex; gap: 10px; }
.search-hero input[type="search"] { flex: 1; padding: 10px 16px; border-radius: 999px; border: 1px solid var(--border); background: var(--surface-2); color: var(--text); font-size: 0.95rem; }
.search-results { list-style: none; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.search-result-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 12px 16px; box-shadow: var(--shadow); overflow-wrap: anywhere; word-break: break-word; }
.search-result-card a { font-weight: 600; overflow-wrap: anywhere; word-break: break-word; }
.search-excerpt { color: var(--text-muted); font-size: 0.85rem; margin: 4px 0 0; overflow-wrap: anywhere; word-break: break-word; }

/* Admin */
.admin-nav { display: flex; gap: 16px; margin: 12px 0 20px; border-bottom: 1px solid var(--border); padding-bottom: 10px; }
.admin-nav a { color: var(--text-muted); }
.admin-nav a:hover { color: var(--text); text-decoration: none; }
.admin-nav a.active { color: var(--accent); font-weight: 600; }
.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 14px; }
.stat-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 18px; text-align: center; box-shadow: var(--shadow); }
.stat-card strong { display: block; font-size: 1.8rem; }
.stat-card span { color: var(--text-muted); font-size: 0.85rem; }

.report-list { display: flex; flex-direction: column; gap: 14px; }
.report-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 14px 18px; box-shadow: var(--shadow); }
.report-post-preview { border-left: 3px solid var(--danger); margin: 8px 0; padding: 4px 14px; color: var(--text-muted); }

.admin-search-form { display: flex; gap: 8px; align-items: center; margin-bottom: 16px; }
.admin-search-form input[type="text"] { flex: 0 1 280px; }
.admin-actions-cell { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.role-badge.shadow-badge { background: #2a1f4a; color: #c9b3ff; margin-left: 6px; }

/* Shadow-member review queue */
.shadow-empty { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); }
.shadow-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 16px; }
.shadow-card {
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
  padding: 16px; box-shadow: var(--shadow); display: flex; flex-direction: column; gap: 12px;
  border-left: 3px solid #7c5cff;
}
.shadow-card-head { display: flex; align-items: center; gap: 10px; }
.shadow-card-identity { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.shadow-card-name { font-weight: 700; overflow-wrap: anywhere; }
.shadow-card-role { align-self: flex-start; }
.shadow-origin-tag {
  font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.03em; font-weight: 700;
  padding: 3px 8px; border-radius: 999px; white-space: nowrap; align-self: flex-start;
}
.shadow-origin-auto { background: #3a2f10; color: #ffd873; }
.shadow-origin-manual { background: #1f2a4a; color: #a8c1ff; }
.shadow-card-stats { display: flex; gap: 8px; }
.shadow-stat {
  flex: 1; text-align: center; background: var(--surface-2); border-radius: 8px; padding: 8px 4px;
}
.shadow-stat strong { display: block; font-size: 1.1rem; }
.shadow-stat span { display: block; font-size: 0.68rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.02em; }
.shadow-card-reason { font-size: 0.85rem; color: var(--text-muted); margin: 0; line-height: 1.4; }
.shadow-card-date { font-size: 0.75rem; color: var(--text-muted); opacity: 0.75; margin: -6px 0 0; }
.shadow-card-actions { display: flex; gap: 8px; margin-top: auto; padding-top: 4px; }
.shadow-card-actions form { flex: 1; }
.shadow-card-actions button { width: 100%; justify-content: center; }

.error-page { text-align: center; padding: 60px 0; }
.error-page h1 { font-size: 2.2rem; }

/* Leaderboard */
.leaderboard-tabs { display: flex; gap: 8px; margin-bottom: 24px; }
.leaderboard-tabs a {
  padding: 8px 18px; border-radius: 999px; background: var(--surface-2); border: 1px solid var(--border);
  color: var(--text-muted); font-weight: 600; font-size: 0.88rem;
}
.leaderboard-tabs a:hover { color: var(--text); text-decoration: none; }
.leaderboard-tabs a.active { background: var(--accent); color: #fff; border-color: var(--accent); }

.podium { display: flex; align-items: flex-end; justify-content: center; gap: 18px; margin: 10px 0 40px; }
.podium-step { display: flex; flex-direction: column; align-items: center; gap: 8px; width: 150px; }
.podium-name { font-weight: 700; overflow-wrap: anywhere; text-align: center; }
.podium-value { font-size: 0.78rem; color: var(--text-muted); }
.podium-block {
  width: 100%; border-radius: 10px 10px 0 0; display: flex; align-items: flex-start; justify-content: center;
  padding-top: 10px; border: 1px solid var(--border); border-bottom: none; box-sizing: border-box;
}
.podium-rank-number { font-size: 1.6rem; font-weight: 900; }
.podium-rank-1 { order: 2; }
.podium-rank-2 { order: 1; }
.podium-rank-3 { order: 3; }
.podium-rank-1 .podium-block { height: 120px; background: linear-gradient(180deg, #ffd873, #a97d1f); border-color: #a97d1f; }
.podium-rank-1 .podium-rank-number { color: #3a2c05; }
.podium-rank-2 .podium-block { height: 88px; background: linear-gradient(180deg, #d7dbe3, #8a90a0); border-color: #8a90a0; }
.podium-rank-2 .podium-rank-number { color: #22252b; }
.podium-rank-3 .podium-block { height: 66px; background: linear-gradient(180deg, #e3a878, #9c6a3f); border-color: #9c6a3f; }
.podium-rank-3 .podium-rank-number { color: #321f08; }

.leaderboard-list { display: flex; flex-direction: column; gap: 8px; }
.leaderboard-row { display: flex; align-items: center; gap: 12px; background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 10px 16px; }
.leaderboard-rank { font-family: var(--mono); color: var(--text-muted); font-weight: 700; width: 36px; flex-shrink: 0; text-align: center; }
.leaderboard-identity { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
.leaderboard-identity a { font-weight: 600; overflow-wrap: anywhere; }
.leaderboard-grade { font-size: 0.72rem; color: var(--text-muted); }
.leaderboard-value { font-weight: 700; color: #ffcf7d; flex-shrink: 0; }

.site-footer { border-top: 1px solid var(--border); padding: 16px 0; color: var(--text-muted); font-size: 0.8rem; }
.footer-line { display: flex; justify-content: space-between; align-items: center; gap: 16px; flex-wrap: wrap; }
.footer-line a { color: var(--text-muted); }
.footer-line a:hover { color: var(--text); }

.settings-section { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 16px 20px; margin-bottom: 16px; }

/* Inline "new thread" card under the board list */
.inline-new-thread { margin-top: 22px; max-width: 100%; }
.inline-title-input { width: 100%; margin-bottom: 8px; }

/* Infinite scroll */
.infinite-sentinel { height: 1px; }
.infinite-loading { padding: 14px 0; }

/* Private messaging - a real split-view messenger: conversation list
   sidebar + active thread panel side by side on desktop, one view at a
   time on mobile (see the media query at the bottom of this file). */
.pm-cta { margin-top: 10px; }

.messenger-layout { display: grid; grid-template-columns: 300px 1fr; gap: 16px; align-items: start; }
/* The messenger specifically felt cramped at the shared .container width,
   so it bleeds further left than the rest of the site - same "widen from
   the left only" technique as .container itself: right edge (against the
   floating stack) stays put, only the left edge moves. Gated behind a
   viewport wide enough that .container already has real margin-left to
   eat into (see its own min-width: 833px rule) - below that there's no
   slack to take without overflowing the screen, so it's left alone. */
@media (min-width: 1100px) {
  .messenger-layout { margin-left: -110px; width: calc(100% + 110px); }
}
.messenger-sidebar {
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px; box-shadow: var(--shadow);
  padding: 14px; max-height: 75vh; overflow-y: auto;
}
.messenger-sidebar-title { margin: 0 0 10px; font-size: 0.78rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.04em; }
.messenger-main {
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px; box-shadow: var(--shadow);
  padding: 18px; display: flex; flex-direction: column; min-height: 65vh;
}
.messenger-empty { align-items: center; justify-content: center; text-align: center; }
.messenger-thread-header { display: flex; align-items: center; gap: 10px; padding-bottom: 12px; margin-bottom: 12px; border-bottom: 1px solid var(--border); }
.messenger-thread-actions { display: flex; gap: 8px; margin-left: auto; flex-shrink: 0; }
.messenger-blocked-notice { background: var(--surface-2); border-radius: var(--radius); }
.messenger-back { display: none; color: var(--text-muted); font-size: 1.3rem; text-decoration: none; }
.messenger-compose { margin-top: auto; padding-top: 12px; }

.conv-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 6px; }
.conv-item { border-radius: 8px; }
.conv-item.conv-active { background: color-mix(in srgb, var(--accent) 14%, var(--surface)); }
.conv-link { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: 8px; color: var(--text); }
.conv-link:hover { text-decoration: none; background: var(--surface-2); }
.conv-info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.conv-name { font-weight: 700; font-size: 0.9rem; }
.conv-item.conv-unread .conv-name { color: var(--accent-2); }
.conv-preview { color: var(--text-muted); font-size: 0.8rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.conv-badge { background: var(--accent); color: #fff; font-size: 0.7rem; font-weight: 700; border-radius: 999px; padding: 1px 7px; flex-shrink: 0; }

.message-list { flex: 1; display: flex; flex-direction: column; gap: 10px; margin-bottom: 16px; overflow-y: auto; }
.message-bubble-row { display: flex; align-items: flex-end; gap: 8px; }
.message-bubble-row.mine { justify-content: flex-end; }
.message-bubble {
  max-width: 70%; background: var(--surface-2); border: 1px solid var(--border); border-radius: 12px;
  padding: 8px 14px; box-shadow: var(--shadow);
}
.message-bubble-row.mine .message-bubble { background: var(--accent); border-color: var(--accent-hover); }
.message-bubble-row.mine .message-bubble .mono-meta { color: rgba(255,255,255,0.75); }
.message-bubble .message-body { word-wrap: break-word; overflow-wrap: anywhere; }
.message-bubble .mono-meta { display: block; margin-top: 4px; text-align: right; font-size: 0.7rem; }

/* Legal pages */
.legal-page h2 { margin-top: 22px; }
.legal-page p, .legal-page li { color: var(--text-muted); }

/* Back-link and follow-bell share one row: link on the left, bell pinned
   to the right edge of the content column (never the floating-stack -
   that lives outside .container entirely, see .floating-stack). */
.thread-header-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 4px; }
.back-to-list-link { display: inline-block; color: var(--text-muted); font-size: 0.88rem; }
.back-to-list-link:hover { color: var(--accent-2); }
.back-to-list-bottom { margin: 4px 0 16px; }
.thread-follow-form { display: block; flex: 0 0 auto; }
.thread-follow-btn {
  display: inline-flex; align-items: center; justify-content: center;
  background: none; border: none; padding: 4px; border-radius: 6px; cursor: pointer;
  color: var(--text); transition: background-color 0.15s ease, color 0.15s ease;
}
.thread-follow-btn svg path { fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linejoin: round; }
.thread-follow-btn.is-following { color: var(--text); }
.thread-follow-btn.is-following svg path { fill: var(--text); stroke: var(--text); }
.thread-follow-btn:hover { background: var(--surface-3); color: var(--accent-2); }

/* Moderation log */
.mod-log-list { list-style: none; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.mod-log-item {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 12px 16px; box-shadow: var(--shadow); display: flex; flex-direction: column; gap: 3px;
}
.mod-log-label { font-weight: 700; font-size: 0.88rem; }
.mod-log-detail { color: var(--text); font-size: 0.9rem; }
.mod-log-meta { color: var(--text-muted); font-size: 0.78rem; }

/* Messenger: one column at a time on narrow screens - which one depends
   on whether this is the plain inbox (list only) or an open thread (list
   swapped for the conversation), set via a class on .messenger-layout. */
@media (max-width: 760px) {
  .messenger-layout { grid-template-columns: 1fr; }
  .messenger-view-thread .messenger-sidebar { display: none; }
  .messenger-view-list .messenger-main { display: none; }
  .messenger-back { display: inline-block; }
  .message-bubble { max-width: 85%; }
}

@media (max-width: 640px) {
  .post { grid-template-columns: 1fr; }
  .post-author {
    flex-direction: row; justify-content: flex-start; border-right: none; border-bottom: 1px solid var(--border);
    border-radius: 12px 12px 0 0; /* now the top bar, not the left column - see .post-author's desktop radius */
  }
  .header-inner { gap: 8px; padding-right: 74px; }
  .centre-nav-link { right: 10px; padding: 5px 8px; font-size: 0.82rem; }
  .main-nav-left { order: 3; width: 100%; overflow-x: auto; }
  .card { padding: 16px; }
}

/* Floating stack: a full sidebar card only makes sense where there's room
   for it beside the 832px content column without shrinking it. Below
   that width (covers phones AND regular laptop-width windows), dock it as
   a small pill in the bottom corner instead - it never competes with the
   thread list / reply form for horizontal space. It scrolls internally
   (max-height + overflow-y) since it now carries a lot more than just the
   profile card. */
@media (max-width: 832px) {
  /* No longer a permanently-docked pill (it read as a stray disconnected
     box on a small screen) - hidden by default, opened as a proper
     dropdown panel by the header's account/presence toggle button (see
     .mobile-account-toggle and public/js/mobile-account.js). Anchored near
     the top rather than the header's own unpredictable height (it can
     wrap at narrow widths), and spans the width instead of guessing a
     column width that no longer applies once it's a full panel. */
  .floating-stack {
    display: none;
    position: fixed; top: 64px; left: 12px; right: 12px; bottom: auto; width: auto;
    max-height: calc(100vh - 90px); overflow-y: auto; z-index: 50;
    background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
    box-shadow: var(--shadow); padding: 12px;
  }
  .floating-stack.mobile-open { display: flex; }
  .mobile-account-toggle { display: inline-flex; }
  .floating-profile-card { padding: 8px; font-size: 0.82rem; }
  .fpc-top { margin-bottom: 6px; }
  .fpc-links { padding-top: 6px; gap: 4px; }
}

/* ---- AI bots admin (/admin/bots) ---- */
.bot-card {
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 10px;
  padding: 14px 16px; margin-bottom: 12px;
}
.bot-card-top { display: flex; align-items: center; gap: 12px; }
.bot-card-identity { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.bot-card-identity strong { overflow-wrap: anywhere; }
.bot-card-username { color: var(--text-muted); font-size: 0.82rem; }
.bot-card-badges { display: flex; gap: 6px; flex-shrink: 0; }
.bot-badge { font-size: 0.68rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; padding: 3px 9px; border-radius: 999px; }
.bot-badge-on { background: var(--success-bg); color: var(--success-text); }
.bot-badge-off { background: var(--surface-3); color: var(--text-muted); }
.bot-card-meta { display: flex; flex-wrap: wrap; gap: 4px 16px; color: var(--text-muted); font-size: 0.8rem; margin: 10px 0; }
.bot-card-meta code { background: var(--surface-3); padding: 1px 6px; border-radius: 4px; font-size: 0.78rem; }
.bot-card-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: flex-start; }
.bot-edit-details { width: 100%; margin-top: 6px; }
.bot-edit-details summary {
  cursor: pointer; color: var(--text-muted); background: var(--surface-3); border: 1px solid var(--border);
  padding: 6px 14px; border-radius: 999px; font-size: 0.85rem; display: inline-block;
}
.bot-edit-details summary:hover { color: var(--accent); border-color: var(--accent); }
.bot-edit-details[open] summary { color: var(--accent); border-color: var(--accent); margin-bottom: 12px; }
.bot-avatar-form { flex-direction: row; align-items: center; gap: 10px; max-width: none; margin-bottom: 16px; }
.bot-avatar-form label { margin: 0; }
.bot-checkbox-label { display: flex !important; align-items: center; gap: 8px; font-weight: 400 !important; }
.bot-checkbox-label input[type="checkbox"] { width: 16px; height: 16px; }
.bot-key-test-row { display: flex; align-items: center; gap: 10px; margin: 4px 0 8px; flex-wrap: wrap; }
.bot-key-test-status { font-size: 0.82rem; color: var(--text-muted); }
.bot-key-test-ok { color: var(--success-text); }
.bot-key-test-error { color: var(--error-text); }

.bot-log { display: flex; flex-direction: column; gap: 6px; max-height: 420px; overflow-y: auto; }
.bot-log-entry {
  display: flex; align-items: flex-start; gap: 10px; background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 8px; padding: 8px 12px; font-size: 0.86rem;
}
.bot-log-kind { flex-shrink: 0; }
.bot-log-body { flex: 1; min-width: 0; overflow-wrap: anywhere; color: var(--text); }
.bot-log-thread { color: var(--text-muted); font-style: normal; }
.bot-log-time { flex-shrink: 0; color: var(--text-muted); font-size: 0.78rem; }
.bot-log-error { border-color: color-mix(in srgb, var(--danger) 50%, var(--border)); }
.bot-log-reply, .bot-log-new_thread { border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); }
