/* "Signal" -- the chosen visual direction: refined dark, blurple-forward,
   Space Grotesk for display/headings, Manrope for body text. Colors still
   share the desktop app's blurple accent (theme.py), just tuned slightly
   darker/warmer to match the direction that was picked from the mockup
   canvas. */
/* Fonts: see genko.css (self-hosted Geist). The old Space Grotesk/Manrope @import was removed -- it was
   render-blocking and no longer used once genko.css overrides --font-body/--font-display. */

:root {
    /* Deepened 2026-09-27 as part of the marketing-page visual pass (about/pricing/guides) --
       a flatter #15151a read as merely "dark gray", not the richer near-black that made the
       redesign mockup feel more premium. Every app screen (Settings, dashboard, etc.) inherits
       these same tokens, so this benefits the whole product, not just the marketing pages. */
    --bg: #0e0e12;
    --bg-raised: #18181f;
    --bg-input: #1e1e27;
    --border: #2b2b36;
    --text: #f0f1f5;
    --text-muted: #9598a6;
    --accent: #5865f2;
    --accent-hover: #6e79f5;
    --accent-2: #f2a65e;
    --accent-soft: rgba(88, 101, 242, 0.14);
    --error: #f24352;
    --success: #3ba55c;
    --warn: #e8a33d;
    --font-body: 'Manrope', system-ui, -apple-system, sans-serif;
    --font-display: 'Space Grotesk', system-ui, sans-serif;
}

* {
    box-sizing: border-box;
}

body {
    margin: 0;
    min-height: 100vh;
    /* 100dvh (dynamic viewport height) accounts for a mobile browser's
       address bar collapsing/expanding as you scroll -- plain 100vh is
       measured against the LARGEST possible viewport, so on a real phone
       it overshoots the visible area and this flex-centered card ends up
       buried in extra blank space above and below it, needing a scroll
       to even find it. Declared second (not replacing the 100vh line
       above) so browsers that don't understand dvh yet just keep the
       vh value instead of the whole property being dropped. */
    min-height: 100dvh;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--bg);
    color: var(--text);
    font-family: var(--font-body);
}

h1, h2, h3, .display {
    font-family: var(--font-display);
}

.card {
    width: 100%;
    max-width: 360px;
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 32px;
}

/* Auth pages (login/register/reset/2FA/etc) are standalone documents with no top-nav,
   so they previously showed no pyontan branding at all -- just the bare page heading.
   This gives them the same soft radial-gradient backdrop as the About hero and a small
   brand mark above the card, without touching the card or its forms. */
.auth-body {
    background:
        radial-gradient(circle at 20% 15%, rgba(88, 101, 242, 0.10), transparent 50%),
        radial-gradient(circle at 85% 85%, rgba(64, 145, 255, 0.09), transparent 50%),
        var(--bg);
}

.auth-card-wrap {
    width: 100%;
    max-width: 360px;
    display: flex;
    flex-direction: column;
    align-items: center;
}

.auth-brand {
    display: flex;
    align-items: center;
    gap: 8px;
    text-decoration: none;
    margin-bottom: 20px;
}

.auth-card-wrap .card {
    width: 100%;
}

h1 {
    font-size: 20px;
    margin: 0 0 4px;
}

p.subtitle {
    color: var(--text-muted);
    font-size: 13px;
    margin: 0 0 24px;
}

label {
    display: block;
    font-size: 13px;
    color: var(--text-muted);
    margin: 16px 0 6px;
}

.field-hint {
    margin: 6px 0 0;
    font-size: 12px;
    color: var(--text-muted);
}

input[type="email"],
input[type="password"],
input[type="text"],
input[type="number"],
input[type="date"],
select,
textarea {
    width: 100%;
    padding: 9px 12px;
    background: var(--bg-input);
    border: 1px solid var(--border);
    border-radius: 8px;
    color: var(--text);
    font-size: 14px;
}

input[type="email"]:focus,
input[type="password"]:focus,
input[type="text"]:focus,
input[type="number"]:focus,
input[type="date"]:focus,
select:focus,
textarea:focus {
    outline: none;
    border-color: var(--accent);
}

input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button { opacity: 0.5; filter: invert(1); }

input:disabled,
select:disabled {
    color: var(--text-muted);
    cursor: not-allowed;
}

textarea {
    resize: vertical;
    font-family: inherit;
}

button {
    width: 100%;
    margin-top: 24px;
    padding: 10px 12px;
    background: var(--accent);
    border: none;
    border-radius: 8px;
    color: white;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
}

button:hover {
    background: var(--accent-hover);
}

.error {
    margin-top: 16px;
    padding: 10px 12px;
    background: rgba(242, 67, 82, 0.12);
    border: 1px solid var(--error);
    border-radius: 8px;
    color: var(--error);
    font-size: 13px;
}

.notice-inline {
    margin-top: 16px;
    padding: 10px 12px;
    background: rgba(59, 165, 92, 0.12);
    border: 1px solid var(--success);
    border-radius: 8px;
    color: var(--success);
    font-size: 13px;
}

.card-footer-link {
    display: inline-block;
    margin-top: 20px;
    color: var(--text-muted);
    font-size: 13px;
    text-decoration: none;
}

.card-footer-link:hover {
    color: var(--text);
}

.role-badge {
    display: inline-block;
    padding: 2px 10px;
    border-radius: 999px;
    font-size: 12px;
    font-weight: 600;
    background: rgba(88, 101, 242, 0.16);
    color: #b7bdfa;
}

.role-badge.admin {
    background: rgba(242, 67, 82, 0.16);
    color: #f7a5ac;
}

a.logout {
    display: inline-block;
    margin-top: 24px;
    color: var(--text-muted);
    font-size: 13px;
    text-decoration: none;
}

a.logout:hover {
    color: var(--text);
}

/* --- Hub page (dashboard.html) -- a scrollable page of stacked panels,
   not the login page's single centered card, so body's flex-centering
   is overridden here. --- */
body.hub {
    display: block;
    align-items: initial;
    justify-content: initial;
    padding: 0 0 64px;
}

/* Shared top nav (base.html) -- every page except login.html */
/* The bar itself stays full-bleed (border-bottom runs edge to edge). Its content is a second,
   inner container -- full width by default (matching the *other* logged-in shell, the sidebar
   layout: sidebar flush left, content filling the rest, nothing centered, so the nav shouldn't
   fake-center itself in a narrow box either -- confirmed directly as a real bug: Dashboard and
   Settings (no shell_class, since they use the sidebar) inherited a stray "max-width: 900px;
   margin: 0 auto" meant for a plain narrow marketing page, boxing the nav in far more than the
   sidebar+content layout beneath it). A marketing page WITHOUT a sidebar (about/blog/etc, via
   shell_class) opts into the matching centered width instead, so its nav lines up with ITS
   centered content. */
.top-nav {
    border-bottom: 1px solid var(--border);
    margin-bottom: 40px;
}

.top-nav-inner {
    /* Matches .hub-shell.has-side's own 1240px cap below -- a sidebar page's content is never
       truly edge-to-edge on a wide monitor either, it just centers wider than a marketing page's
       narrower shells; the nav needs the same bound, not "no bound at all", to actually match it. */
    max-width: 1240px;
    margin: 0 auto;
    padding: 18px 24px;
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.top-nav-inner.roadmap-shell {
    max-width: 1180px;
    margin: 0 auto;
}

.top-nav-inner.about-shell,
.top-nav-inner.blog-shell,
.top-nav-inner.dashboard-shell {
    max-width: 1400px;
    margin: 0 auto;
}

.top-nav-inner.legal-shell {
    max-width: 760px;
    margin: 0 auto;
}

.top-nav-brand {
    display: flex;
    align-items: center;
    gap: 9px;
    text-decoration: none;
}

/* Only mobile needs this -- the desktop sidebar (.side-nav) already carries its own "Back to dashboard"
   link (account/admin areas) or the Dashboard row (library/study), but that whole sidebar is hidden below
   820px (see .side-nav below) and the phone tab bar it's replaced by doesn't reach back to Dashboard from
   inside an area (Library/Study/Settings each show their OWN tabs there, not a way out) -- confirmed
   directly as a real dead end: once in, the only way back was the brand logo, which goes to /about, not
   even the dashboard. Lives at the top of the page content (above each page's own title), not in the top
   bar itself -- the user found a top-bar arrow easy to miss next to the logo. */
.mobile-back-link {
    display: none;
}

@media (max-width: 820px) {
    .mobile-back-link {
        /* .app-main is a flex column with its own gap (see below) -- no margin needed, that already
           spaces it from the page's own header the same as every other section. */
        display: inline-flex;
        align-items: center;
        gap: 6px;
        color: var(--text-muted);
        font-size: 13px;
        font-weight: 600;
        text-decoration: none;
    }

    .mobile-back-link:hover {
        color: var(--text);
    }
}

.top-nav-logo-mark {
    display: block;
    flex-shrink: 0;
    border-radius: 6px;
}

.top-nav-logo {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: 16px;
    color: var(--text);
    letter-spacing: 0.01em;
}

.top-nav-links {
    display: flex;
    align-items: center;
    gap: 30px;
}

.top-nav-links a {
    color: var(--text-muted);
    font-size: 13px;
    font-weight: 600;
    text-decoration: none;
}

.top-nav-links a:hover {
    color: var(--text);
}

.top-nav-links a.nav-logout {
    color: var(--error);
}

.top-nav-links a.nav-logout:hover {
    color: var(--error);
    opacity: 0.8;
}

.top-nav-links a.cta {
    color: var(--text);
    padding: 8px 18px;
    border: 1px solid var(--border);
    border-radius: 8px;
}

.top-nav-links a.cta:hover {
    border-color: var(--accent);
    color: var(--text);
}

.hub-shell {
    max-width: 900px;
    margin: 0 auto;
    padding: 0 24px;
    display: flex;
    flex-direction: column;
    gap: 20px;
}

/* Widened 2026-09-27 (was 1180px, then 1320px) -- marketing pages (about/pricing/blog) were
   reading as narrow, centered columns with the rest of a wide monitor left blank on both sides;
   the app screens sharing plain .hub-shell (Settings, dashboard, etc.) are untouched. Matches
   .top-nav-inner.about-shell above, so the nav lines up with the page content under it. */
.hub-shell.about-shell,
.hub-shell.blog-shell {
    max-width: 1400px;
}

.hub-shell.read-shell {
    max-width: 900px;
}

.hub-shell.legal-shell {
    max-width: 760px;
}

.hub-shell.roadmap-shell {
    max-width: 1180px;
}

/* Profile (/user/{id}) isn't in the sidebar area map in templating.py -- it's visitable by anyone
   with the link, not just the owner navigating their own account, so it doesn't get a sidebar --
   but that meant it fell back to the bare 900px default, noticeably narrower than Dashboard/Library
   (1240px via .has-side) even though it was built with that much room in mind. Matches has-side's
   own width -- .top-nav-inner's default is already 1240px too, so the nav already lines up with no
   extra override needed there. */
.hub-shell.profile-shell {
    max-width: 1240px;
}

/* Dashboard specifically (not Settings/Library/Study, which stay at the plain 1240px .has-side
   width) -- widened 2026-09-28 to match the marketing pages' own 1400px, after the cockpit grid's
   tiles got bigger/more detailed and needed the room. The extra .has-side in this selector (not just
   .dashboard-shell) is needed for specificity: both are two-class rules otherwise, and .has-side is
   the one that would win on source order alone. */
.hub-shell.dashboard-shell.has-side {
    max-width: 1400px;
}

.hub-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 12px;
}

.hub-header h1 {
    font-size: 22px;
}

/* Only the dashboard's own "email + role badge" line actually wants
   flex -- this used to be a bare ".hub-header .subtitle" rule, which
   meant it applied to every other page's plain-text subtitle too,
   splitting normal sentences (anything with an inline element in it,
   like admin_announcements.html's "...at <code>/blog</code>...") into
   separate flex items instead of just flowing as text. Real bug, only
   surfaced once a real narrow screen made the broken wrap visible. */
.hub-header-identity {
    margin: 6px 0 0;
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.hub-shell a.logout {
    margin-top: 4px;
}

.profile-hero {
    position: relative;
    margin-bottom: 52px;
}

.profile-banner {
    width: 100%;
    aspect-ratio: 3 / 1;
    max-height: 220px;
    object-fit: cover;
    border-radius: 16px;
    display: block;
    background: var(--bg-raised);
}

.profile-banner-placeholder {
    background: linear-gradient(140deg, var(--accent), #23255e);
}

.profile-avatar {
    position: absolute;
    left: 24px;
    bottom: -40px;
    width: 96px;
    height: 96px;
    border-radius: 999px;
    object-fit: cover;
    border: 4px solid var(--bg);
    background: var(--bg-raised);
}

/* Two SEPARATE links to Settings' Basic tab, for the profile owner only -- one wrapping just the
   banner, one wrapping just the avatar, each with its own hover scope. These used to be one shared
   <a> around both images, which meant hovering over either one dimmed BOTH and showed BOTH edit
   icons at once -- confirmed directly as a real, confusing bug. display: contents keeps each anchor
   out of layout entirely, so the banner/avatar's own sizing and the avatar's absolute position
   (relative to .profile-hero) are unaffected by the extra element. */
.profile-banner-edit,
.profile-avatar-edit {
    display: contents;
    cursor: pointer;
}
.profile-banner-edit:hover .profile-banner {
    opacity: 0.85;
}
/* A steeper drop than the banner's -- confirmed directly that the SAME 0.85 used on the banner read
   as barely-there here: the avatar is a small 96px circle, so the same relative dim is much less
   visually obvious than it is across a large flat banner. */
.profile-avatar-edit:hover .profile-avatar {
    opacity: 0.5;
}

/* The edit-icon badges: hidden until hover, so the hero reads normally otherwise. Both are
   positioned against .profile-hero (the nearest positioned ancestor, since the <a> around each is
   display: contents and isn't one itself) -- .profile-hero's own box height tracks the banner
   exactly, since the avatar is taken out of flow by its own absolute position, so a plain 50%/50%
   centered badge lines up on the banner with no extra math needed. */
.profile-hero-edit-icon {
    position: absolute;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.6);
    color: #fff;
    border-radius: 999px;
    opacity: 0;
    transition: opacity 0.15s;
    pointer-events: none;
}
.profile-banner-edit:hover .profile-hero-edit-icon-banner,
.profile-avatar-edit:hover .profile-hero-edit-icon-avatar {
    opacity: 1;
}
.profile-hero-edit-icon-banner {
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 40px;
    height: 40px;
}
.profile-hero-edit-icon-avatar {
    /* Centered ON the avatar itself, matching the banner's own centered treatment (this used to sit
       pinned to the avatar's bottom-right corner instead, which read as random/wrong once the shared-
       hover bug above was the actual problem). Avatar is left:24/width:96, bottom:-40/height:96, so
       its center is at left 24+48=72 and, measured the same way as `bottom`, 8 (-40+48) above
       .profile-hero's own bottom edge -- translate(-50%, 50%) then centers THIS badge on that point
       regardless of the badge's own size, the same trick the banner badge uses via top/left 50%. */
    left: 72px;
    bottom: 8px;
    transform: translate(-50%, 50%);
    width: 30px;
    height: 30px;
    border: 2px solid var(--bg);
}

.profile-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 12px;
    margin-bottom: 24px;
}

.profile-identity h1 {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 22px;
}

.profile-joined {
    margin: 2px 0 0;
    font-size: 12px;
    color: var(--text-muted);
}

.profile-bio {
    margin: 4px 0 24px;
    font-size: 14px;
    line-height: 1.5;
    max-width: 60ch;
    color: var(--text-muted);
}

.profile-actions {
    display: flex;
    align-items: center;
    gap: 14px;
}

.follow-button {
    width: auto;
    height: 38px;
    box-sizing: border-box;
    margin: 0;
    padding: 0 20px;
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

/* .hub-shell button's margin-top:12px (0-1-1) otherwise beats this rule's margin:0 (0-1-0) for a real
   <button> element -- same gotcha already documented above for .avatar-edit-menu-item. The kebab trigger
   next to this one is a <summary>, not a <button>, so it never had the extra margin -- that mismatch was
   the actual cause of the two controls not lining up. */
.profile-actions .follow-button {
    margin-top: 0;
}

.follow-button.is-following {
    background: var(--bg-input);
    border: 1px solid var(--border);
    color: var(--text);
}

.profile-more {
    position: relative;
}

.profile-more-trigger {
    display: flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    width: 38px;
    height: 38px;
    border-radius: 999px;
    border: none;
    background: none;
    color: var(--text-muted);
    cursor: pointer;
    list-style: none;
}

.profile-more-trigger::-webkit-details-marker {
    display: none;
}

.profile-more-trigger:hover {
    color: var(--text);
    background: var(--bg-raised);
}

.profile-more-menu {
    left: auto;
    right: 0;
}

.profile-edit-link {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.profile-chart h2 {
    font-size: 15px;
    margin: 0 0 16px;
}

.profile-stats-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
    gap: 12px;
    margin-bottom: 28px;
}

.profile-stat {
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: 14px;
    padding: 16px;
    display: flex;
    flex-direction: column;
    gap: 6px;
    color: var(--accent);
}

.profile-stat-value {
    font-size: 22px;
    font-weight: 700;
    color: var(--text);
}

.profile-stat-label {
    font-size: 12px;
    color: var(--text-muted);
}

.profile-works h2 {
    display: flex;
    align-items: center;
    gap: 8px;
}

.profile-section-count {
    font-size: 12px;
    font-weight: 600;
    color: var(--text-muted);
    background: var(--bg);
    border-radius: 999px;
    padding: 1px 9px;
}

.panel {
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 24px;
}

.panel h2 {
    font-size: 15px;
    margin: 0 0 16px;
}

.hub-shell button {
    width: auto;
    margin-top: 12px;
    padding: 8px 18px;
}

button.secondary {
    background: var(--bg-input);
    border: 1px solid var(--border);
    color: var(--text);
}

button.secondary:hover {
    background: var(--bg-input);
    border-color: var(--accent);
}

.empty-state {
    color: var(--text-muted);
    font-size: 13px;
    margin: 0;
}

/* Notice board */
.notice-list {
    list-style: none;
    margin: 0 0 16px;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.notice-item {
    border-left: 3px solid var(--accent);
    padding: 4px 0 4px 12px;
}

.notice-title {
    font-weight: 600;
    font-size: 14px;
}

.notice-body {
    font-size: 13px;
    color: var(--text-muted);
    margin-top: 2px;
    white-space: pre-wrap;
}

.notice-meta {
    font-size: 11px;
    color: var(--text-muted);
    margin-top: 4px;
}

.notice-form {
    border-top: 1px solid var(--border);
    padding-top: 16px;
    margin-top: 4px;
}

.notice-form label {
    margin: 12px 0 6px;
}

.notice-form label:first-child {
    margin-top: 0;
}

/* Activity feed */
.activity-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.activity-item {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    padding: 8px 12px;
    background: var(--bg-input);
    border-radius: 8px;
    border-left: 3px solid var(--border);
    font-size: 13px;
}

.activity-item.alert {
    border-left-color: var(--error);
    background: rgba(242, 67, 82, 0.08);
}

.activity-meta {
    color: var(--text-muted);
    font-size: 11px;
    white-space: nowrap;
}

/* Library -- job cover grid */
.job-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: 14px;
}

.job-card {
    background: var(--bg-input);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 12px;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.job-cover {
    width: 100%;
    aspect-ratio: 3 / 4;
    border-radius: 6px;
    background: linear-gradient(160deg, var(--accent), #3a41a8);
    color: white;
    font-size: 28px;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 4px;
}

.job-title {
    font-size: 13px;
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.job-status {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-weight: 600;
    color: var(--text-muted);
}

.job-status.status-done {
    color: var(--success);
}

.job-status.status-failed {
    color: var(--error);
}

.job-status.status-running,
.job-status.status-pending {
    color: var(--warn);
}

.job-pages,
.job-synced {
    font-size: 11px;
    color: var(--text-muted);
}

.job-failed-note {
    color: var(--error);
}

/* Panel header with an inline row of links (e.g. Updates: View all / Manage) */
.panel-header-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 8px 16px;
    margin-bottom: 16px;
}

.panel-header-row h2 {
    margin: 0;
}

.panel-header-links {
    display: flex;
    gap: 14px;
}

.panel-header-links a {
    color: var(--accent);
    font-size: 13px;
    text-decoration: none;
}

.panel-header-links a:hover {
    text-decoration: underline;
}

.blog-post-title {
    color: var(--text);
    text-decoration: none;
}

.blog-post-title:hover {
    color: var(--accent);
}

/* Public updates/blog pages -- a fixed-size thumbnail grid, two equal
   cards per row on desktop, one per row on mobile (see the media query
   below). Every card is the same height regardless of what thumbnail or
   text it holds: the cover is locked to one aspect-ratio (min-height: 0
   stops a large/square source image's own intrinsic size from stretching
   the flex item taller than that -- the classic flexbox-child-image
   overflow bug, which is what let a big square app-icon thumbnail blow a
   card up in size before), and the title/excerpt are line-clamped so
   longer text can't grow the card either. */
.blog-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 22px;
}

.blog-card {
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: 16px;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    text-decoration: none;
    color: inherit;
}

.blog-card:hover {
    border-color: var(--accent);
}

/* The blog/guides search hides non-matching cards (and empty guide groups) with the `hidden`
   attribute, but .blog-card's own display: flex above beats the browser's built-in
   [hidden] { display: none } -- so the search set `hidden` and every card stayed on screen. */
.blog-card[hidden],
.guide-group[hidden] {
    display: none;
}

.blog-cover {
    width: 100%;
    aspect-ratio: 16 / 10;
    min-height: 0;
    background: linear-gradient(140deg, var(--accent), #23255e);
    display: flex;
    align-items: flex-end;
    justify-content: flex-end;
    color: white;
    font-family: var(--font-display);
    font-size: 32px;
    font-weight: 700;
}

.blog-cover-initial {
    padding: 16px;
    opacity: 0.5;
}

.blog-cover img {
    width: 100%;
    height: 100%;
    min-height: 0;
    object-fit: cover;
    display: block;
}

.blog-card-body {
    padding: 22px;
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 150px;
}

.blog-card-meta {
    font-size: 11px;
    color: var(--text-muted);
    font-weight: 600;
    margin-bottom: 8px;
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

.blog-card h2 {
    margin: 0 0 10px;
    font-size: 16px;
    line-height: 1.3;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.blog-card-excerpt {
    font-size: 13px;
    line-height: 1.6;
    color: var(--text-muted);
    margin: 0;
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.blog-post-single-cover {
    width: 100%;
    aspect-ratio: 21 / 9;
    border-radius: 16px;
    background: linear-gradient(140deg, var(--accent), #23255e);
    margin-bottom: 32px;
    overflow: hidden;
}

.blog-post-single-cover img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.blog-post-single h1 {
    font-size: 34px;
    font-weight: 700;
    line-height: 1.25;
    margin: 0 0 26px;
}

.blog-post-body {
    margin-top: 14px;
    font-size: 15px;
    line-height: 1.8;
    color: var(--text);
}

/* Real HTML from the blog body editor (or a legacy plain-text post,
   escaped and paragraph-wrapped on the way in -- see
   blog_sanitize.render_body_for_display()) -- no more white-space:pre-wrap
   here, since real block elements (<p>, <ul>, ...) handle their own
   spacing and pre-wrap would otherwise start preserving incidental
   whitespace between tags too. */
.blog-rich-content p {
    margin: 0 0 16px;
}

.blog-rich-content p:last-child {
    margin-bottom: 0;
}

.blog-rich-content h2,
.blog-rich-content h3,
.blog-rich-content h4 {
    font-family: var(--font-display);
    margin: 28px 0 12px;
    line-height: 1.3;
}

.blog-rich-content h2 {
    font-size: 24px;
    padding-bottom: 8px;
    border-bottom: 1px solid var(--border);
}

.blog-rich-content h3 {
    font-size: 19px;
}

.blog-rich-content h4 {
    font-size: 16px;
}

.blog-rich-content ul,
.blog-rich-content ol {
    margin: 0 0 16px;
    padding-left: 24px;
    /* outside (the default) puts the bullet/number in the MARGIN area, positioned
       relative to the <ul>'s own edge -- a nested <li>'s own extra padding-left
       (.ql-indent-N below) shifts its TEXT over but never touches that marker box,
       since padding lives inside the border edge and outside markers sit before it.
       Confirmed directly: a real nested bullet's text indented correctly while its
       "*" stayed pinned to the far-left margin, not next to its own text. inside
       makes the marker part of the li's own content flow, so it moves with the
       same padding that already indents the text. */
    list-style-position: inside;
}

.blog-rich-content li {
    margin-bottom: 6px;
}

/* Indent level, from the toolbar's Indent/Outdent buttons (also what Tab/Shift+Tab does
   inside a list) -- Quill carries depth as a class on whatever block tag it was applied
   to (<li>, but just as often a plain <p>/<h2>/<blockquote> that isn't a list at all),
   never real nested markup. Not scoped to any one tag -- unlike a list, indenting a plain
   paragraph is common here (e.g. a "Section:" label indented above its own bullet list).
   Quill's own CSS (loaded only on the editor page) already understands these; the
   published page needs its own copy since it never loads Quill's stylesheet. */
.blog-rich-content .ql-indent-1 { padding-left: 24px; }
.blog-rich-content .ql-indent-2 { padding-left: 48px; }
.blog-rich-content .ql-indent-3 { padding-left: 72px; }
.blog-rich-content .ql-indent-4 { padding-left: 96px; }
.blog-rich-content .ql-indent-5 { padding-left: 120px; }
.blog-rich-content .ql-indent-6 { padding-left: 144px; }
.blog-rich-content .ql-indent-7 { padding-left: 168px; }
.blog-rich-content .ql-indent-8 { padding-left: 192px; }

.blog-rich-content blockquote {
    margin: 0 0 16px;
    padding: 4px 18px;
    border-left: 3px solid var(--accent);
    color: var(--text-muted);
}

.blog-rich-content pre {
    margin: 0 0 16px;
    padding: 14px 16px;
    background: var(--bg-input);
    border: 1px solid var(--border);
    border-radius: 8px;
    overflow-x: auto;
    font-size: 13px;
}

.blog-rich-content code, .blog-editor .ql-editor code {
    font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
    background: var(--bg-input);
    padding: 2px 6px;
    border-radius: 4px;
    font-size: 0.9em;
}

/* Undo the inline-code pill above for code already inside a code BLOCK
   (its own pre already has the boxed background) -- otherwise a code block's
   text renders as a pill nested inside a bigger box. */
.blog-rich-content pre code, .blog-editor .ql-editor pre code {
    background: none;
    padding: 0;
    border-radius: 0;
    font-size: inherit;
}

.blog-rich-content a {
    color: var(--accent);
}

.blog-rich-content img {
    max-width: 100%;
    height: auto;
    border-radius: 8px;
    margin: 8px 0 16px;
    display: block;
}

/* A block-level image (the default above) ignores its paragraph's text-align
   entirely -- centering/right-aligning only affects inline-level content, so an
   image inside an aligned paragraph needs to actually BE inline-level again to
   respond to it. Quill's own editor never overrides img's display at all for
   exactly this reason (it stays the browser default, inline); scoping this to
   just the aligned case keeps every other image's own-line block treatment.
   vertical-align is load-bearing too, not decoration: inline-block's own default
   is "baseline" -- the image's BOTTOM edge lines up with the surrounding text's
   baseline, so any image taller than one text line visibly floats up above the
   line instead of sitting on it (confirmed directly: a 60px image next to normal
   text rendered clearly detached, well above where the text sat). middle reads
   as naturally "on the line" the way a plain inline image mixed with text does. */
.blog-rich-content .ql-align-center img,
.blog-rich-content .ql-align-right img {
    display: inline-block;
    vertical-align: middle;
}

.blog-rich-content .ql-align-center {
    text-align: center;
}

.blog-rich-content .ql-align-right {
    text-align: right;
}

.blog-rich-content .ql-align-justify {
    text-align: justify;
}

.blog-rich-content hr {
    border: none;
    border-top: 1px solid var(--border);
    margin: 28px 0;
}

/* Admin blog editor (Quill) -- match the app's own dark theme instead
   of Quill's default light "snow" chrome. */
.blog-editor {
    margin-bottom: 12px;
}

.blog-editor .ql-toolbar {
    border-color: var(--border);
    border-radius: 8px 8px 0 0;
    background: var(--bg-input);
    /* Quill's own layout for toolbar groups is float-based: each .ql-formats group
       sits inline-block next to the others, so a group that's internally wrapped
       onto two lines (more buttons than fit on the remaining width) can end up a
       different height than its neighbors -- and with no shared alignment rule,
       shorter neighbors default to vertical-align: baseline, visually floating up
       or down relative to a taller group instead of lining up. Flex here (with
       align-items) keeps every group vertically centered on whichever line it
       lands on, regardless of how any one group happens to wrap.
       !important on .ql-formats below is load-bearing: quill.snow.min.css defines
       ".ql-snow .ql-formats{display:inline-block}" at the exact same specificity
       as the selector here, and it loads AFTER this stylesheet (base.html links
       style.css before the page's extra_head block, which is where Quill's CSS
       comes in) -- so on a tie, Quill's later rule wins. (This whole block was
       actually inert for a different, bigger reason until the template fix
       alongside it: "blog-editor" was on the element Quill itself repurposes into
       .ql-container, not on a wrapper around it, so ".blog-editor .ql-toolbar" as
       a descendant selector never matched anything at all -- see
       admin_announcements.html's comment on its wrapper div. That's why the
       toolbar had never actually picked up this dark theming in the first place.) */
    display: flex !important;
    flex-wrap: wrap;
    align-items: center !important;
    row-gap: 6px;
}

.blog-editor .ql-formats {
    display: inline-flex !important;
    align-items: center !important;
}

/* The site's own button style (margin on top, bold, full width) reaches Quill's toolbar buttons too, which is what
   pushed them below the dropdowns. Same sizes as the note editor's toolbar (genko.css, .ne-modal). */
.blog-editor .ql-toolbar.ql-snow {
    padding: 8px 14px;
}

.blog-editor .ql-toolbar.ql-snow button {
    margin: 0;
    height: 28px;
    padding: 3px 5px;
    font-weight: 400;
}

.blog-editor .ql-container {
    border-color: var(--border);
    border-radius: 0 0 8px 8px;
    background: var(--bg-input);
    font-size: 14px;
    color: var(--text);
}

.blog-editor .ql-editor.ql-blank::before {
    color: var(--text-muted);
    font-style: normal;
}

.blog-editor .ql-stroke {
    stroke: var(--text-muted);
}

.blog-editor .ql-fill {
    fill: var(--text-muted);
}

.blog-editor .ql-picker-label {
    color: var(--text-muted);
    border-color: var(--border);
}

/* Quill's own CSS has several MORE specific rules that only apply while a
   toolbar dropdown is actually open (".ql-picker.ql-expanded ..."), each with
   more class selectors chained together than the plain rules above -- so
   those silently won and left Quill's light-theme #ccc showing (border, text,
   and the little dropdown-arrow icon) the moment a picker was clicked open,
   even though everything looked right while collapsed. Matching the same
   selector shape (prefixed with .blog-editor) is what actually outranks them,
   not just repeating the same low-specificity rule again. */
.blog-editor .ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-label,
.blog-editor .ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-options {
    border-color: var(--border);
}

.blog-editor .ql-snow .ql-picker.ql-expanded .ql-picker-label {
    color: var(--text-muted);
}

.blog-editor .ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-fill {
    fill: var(--text-muted);
}

.blog-editor .ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-stroke {
    stroke: var(--text-muted);
}

.blog-editor .ql-toolbar button:hover .ql-stroke,
.blog-editor .ql-toolbar button.ql-active .ql-stroke {
    stroke: var(--gk-accent-text, var(--accent));
}

.blog-editor .ql-toolbar button:hover .ql-fill,
.blog-editor .ql-toolbar button.ql-active .ql-fill {
    fill: var(--gk-accent-text, var(--accent));
}

.blog-editor .ql-picker-options {
    background: var(--bg-raised);
    border-color: var(--border);
}

/* Highlight background -- color itself now comes from Quill's own default inline
   `style="color:...;background-color:..."` (Quill's stock 35-color palette, validated
   server-side by a real CSS parser -- see blog_sanitize.py), so no per-color CSS is
   needed here at all; this only adds the pill shape on top of whatever background
   color ends up inline. [style*="background-color"] matches regardless of which tag
   Quill attached it to (<span>, <strong>, <code>, ...) -- see blog_sanitize.py's
   _STYLED_INLINE_TAGS for why it's never just <span>. */
.blog-editor .ql-editor [style*="background-color"],
.blog-rich-content [style*="background-color"] {
    border-radius: 3px;
    padding: 1px 4px;
    margin: -1px 0;
}

@media (max-width: 860px) {
    .blog-grid {
        grid-template-columns: 1fr;
    }
}

.back-link {
    display: inline-block;
    margin-bottom: 16px;
    color: var(--text-muted);
    font-size: 13px;
    text-decoration: none;
}

.back-link:hover {
    color: var(--text);
}

/* Admin post management */
.admin-post-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.admin-post-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 10px 14px;
    background: var(--bg-input);
    border-radius: 8px;
    border: 1px solid var(--border);
}

.admin-post-actions {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-shrink: 0;
}

/* A flex item's default min-width is auto, not 0 -- without this, this
   text block refuses to shrink below its own content width, so a long
   post title (or a narrow phone screen) pushes .admin-post-actions
   partly off-screen instead of the title just truncating. Structural
   (:first-child), not a new class, since the item markup is otherwise
   identical between admin_announcements.html and admin_users.html. */
.admin-post-item > div:first-child {
    min-width: 0;
    flex: 1;
}

.admin-post-item .notice-title {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.admin-post-actions form {
    margin: 0;
}

.feedback-item { align-items: flex-start; }
.feedback-item .notice-meta { overflow-wrap: anywhere; }
.feedback-message { margin-top: 8px; font-size: 13px; overflow-wrap: anywhere; }

/* On a phone the buttons sitting beside the message left it a sliver of a column; stack them under it instead. */
@media (max-width: 700px) {
    .feedback-item { flex-direction: column; align-items: stretch; }
    .feedback-item .notice-title { white-space: normal; }
    .feedback-item .admin-post-actions { flex-shrink: 1; }
}

/* Real bug: button.danger resets its own margin-top, but button.secondary
   doesn't -- so when both sit side by side (e.g. feedback's Reopen +
   Delete), .secondary silently inherits .hub-shell button's margin-top:
   12px while .danger sits at 0, visibly misaligning the two. Scoped to
   this container rather than fixing button.secondary globally, since
   other .secondary buttons elsewhere rely on the default top margin. */
.admin-post-actions button {
    margin-top: 0;
}

/* Same size for every button in the row (Delete used to be much smaller than Mark resolved).
   Edit (admin_users.html / admin_roadmap.html) is an <a class="button-link"> next to that same
   Delete button -- same gap here, a plain 13px text link beside a real 38px button -- so it gets
   the same treatment, styled as a neutral bordered button rather than Delete's red. */
.admin-post-actions button.secondary,
.admin-post-actions button.danger,
.admin-post-actions a.button-link {
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: auto;
    height: 38px;
    margin-top: 0;
    padding: 0 18px;
    font-size: 14px;
    font-weight: 600;
    line-height: 1;
}

.admin-post-actions a.button-link {
    border-radius: 8px;
    background: var(--bg-raised);
    border: 1px solid var(--border);
    color: var(--text);
    text-decoration: none;
}

.admin-post-actions a.button-link:hover {
    border-color: var(--accent);
    text-decoration: none;
}

.button-link {
    color: var(--accent);
    font-size: 13px;
    text-decoration: none;
}

.button-link:hover {
    text-decoration: underline;
}

button.danger {
    width: auto;
    margin-top: 0;
    padding: 6px 14px;
    background: transparent;
    border: 1px solid var(--error);
    color: var(--error);
    font-size: 12px;
}

button.danger:hover {
    background: rgba(242, 67, 82, 0.12);
}

/* Marketing/landing page (about.html) -- built in the "Signal" direction. */
/* 2026-09-27: hero moved from a centered single column to a two-up grid, with a real (not a
   fake-screenshot) mockup of pyontan's own Click Lookup popup as the visual instead of empty
   space -- widening .about-shell (above) only helped once there was something to put in the
   extra width. Falls back to a single column under .about-hero-grid's own breakpoint below. */
.about-hero {
    padding: 72px 0 40px;
}

.about-hero-grid {
    display: grid;
    /* Widened from 460px -- a small, letterboxed screenshot read as cautious/placeholder-ish
       rather than the bold, zoomed-in advertisement feel a hero image should have. */
    grid-template-columns: minmax(0, 1fr) minmax(0, 560px);
    gap: 56px;
    align-items: center;
}

.about-hero-copy {
    text-align: left;
}

.chip-row {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 28px;
    max-width: 520px;
}

.chip {
    font-size: 12px;
    font-weight: 600;
    padding: 6px 13px;
    border-radius: 999px;
    background: var(--bg-raised);
    border: 1px solid var(--border);
    color: var(--text-muted);
}

/* .chip is plain text everywhere else (the About hero's feature list) -- an <a> version with an
   icon, like Feedback's "Join the Discord" chip, needs its own layout so the icon and text sit
   inline instead of stacking. */
a.chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    text-decoration: none;
}
a.chip:hover {
    border-color: var(--accent);
    color: var(--text);
}

/* The hero visual: a real screenshot of pyontan's own lookup popup, staged on a soft gradient
   backdrop -- a hand-built fake mockup here undersold the actual UI (confirmed directly, side by
   side against a real screenshot: the real popup has translation, grammar explain, a full word
   breakdown with reading/JLPT level/part of speech and an example sentence -- a plain two-line
   card was a worse, less accurate advert for the app than just showing the real thing). */
.popup-scene {
    position: relative;
    border-radius: 20px;
    background:
        radial-gradient(circle at 25% 20%, rgba(88, 101, 242, 0.16), transparent 55%),
        radial-gradient(circle at 80% 75%, rgba(242, 166, 94, 0.10), transparent 50%),
        var(--bg-raised);
    border: 1px solid var(--border);
    /* Was 40px -- ate 80px off the carousel's actual rendered width for a thin gradient
       margin nobody asked for, which is a real part of why real screenshots looked small
       even after widening .about-hero-grid/.carousel themselves (direct feedback). A
       slim frame still shows the gradient as a deliberate border, not the dominant space. */
    padding: 12px;
    min-height: 320px;
    display: flex;
    align-items: center;
    justify-content: center;
}

.popup-shot {
    width: 100%;
    max-width: 360px;
    border-radius: 14px;
    box-shadow: 0 24px 60px -20px rgba(0, 0, 0, 0.55);
    display: block;
}

.showcase-note {
    text-align: center;
    font-size: 12px;
    color: var(--text-dim, var(--text-muted));
    margin-top: 16px;
}

@media (max-width: 860px) {
    .about-hero-grid {
        grid-template-columns: 1fr;
    }
    .about-hero-copy {
        text-align: center;
    }
    .chip-row {
        justify-content: center;
        margin-left: auto;
        margin-right: auto;
    }
}

/* "See it in action" -- one annotated real mockup rather than a two-panel before/after
   diff, so it reads as pyontan's own product shot, not a layout borrowed from anywhere else. */
.showcase {
    padding: 24px 0 72px;
}

.showcase-head {
    max-width: 560px;
    margin: 0 auto 40px;
    text-align: center;
}

.showcase-head h2 {
    font-size: 30px;
    margin: 0 0 12px;
}

.showcase-head p {
    color: var(--text-muted);
    font-size: 15px;
    line-height: 1.6;
    margin: 0;
}

/* A plain 3-column flex row (callouts | card | callouts) instead of manually-offset absolute
   labels -- the numbers stay put relative to the card at any width without needing hand-tuned
   pixel positions, and it degrades to a single stacked column below 860px for free. */
.showcase-frame {
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: 20px;
    padding: 48px;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 40px;
}

.showcase-col {
    display: flex;
    flex-direction: column;
    gap: 28px;
    max-width: 200px;
}

.showcase-col.right {
    align-items: flex-start;
}

.showcase-callout {
    display: flex;
    gap: 10px;
    font-size: 13px;
    line-height: 1.5;
    color: var(--text-muted);
}

.showcase-callout-num {
    width: 22px;
    height: 22px;
    flex-shrink: 0;
    border-radius: 50%;
    background: var(--accent);
    color: #fff;
    font-size: 11px;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
}

@media (max-width: 860px) {
    .showcase-frame { flex-direction: column; padding: 32px 24px; }
    .showcase-col { max-width: 100%; order: 2; }
    .showcase-col.right { order: 3; }
}

.about-badge {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin: 0 0 24px;
    padding: 6px 14px;
    border-radius: 999px;
    border: 1px solid var(--border);
    color: var(--accent-2);
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.about-hero h1 {
    font-size: 52px;
    font-weight: 700;
    line-height: 1.1;
    letter-spacing: -0.01em;
    margin: 0 0 22px;
}

.about-tagline {
    color: var(--text-muted);
    font-size: 17px;
    max-width: 560px;
    margin: 0 auto 36px;
    line-height: 1.6;
}

.about-cta-row {
    display: flex;
    justify-content: center;
    gap: 14px;
}

.button-link.cta {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 13px 26px;
    border-radius: 10px;
    background: var(--accent);
    color: white;
    font-size: 14px;
    font-weight: 700;
}

.button-link.cta:hover {
    background: var(--accent-hover);
    text-decoration: none;
}

.button-link.cta.secondary {
    background: transparent;
    border: 1px solid var(--border);
    color: var(--text);
}

.button-link.cta.secondary:hover {
    border-color: var(--accent);
}

/* A locked-feature page's two actions (See plans / Back) -- Study's own version of this lives in
   study.css as .study-actions, not loaded on non-study pages like Shared library's gate, so this is
   the same flex row in the main sheet for everywhere else that needs it. */
.locked-actions {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    margin-top: 4px;
}

.about-features {
    margin: 0 0 80px;
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 22px;
}

/* Pricing tier lists: a plain flex column with a small accent dot marker instead of the
   browser's default bulleted <ul>, closer to the mockup's cleaner look. */
.tier-list {
    list-style: none;
    margin: 0 0 20px;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 11px;
    font-size: 14px;
    line-height: 1.5;
    color: var(--text-muted);
}

.tier-list li {
    position: relative;
    padding-left: 20px;
}

.tier-list li::before {
    content: "";
    position: absolute;
    left: 0;
    top: 7px;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--accent);
}

.tier-popular {
    position: relative;
    border-color: var(--accent) !important;
    box-shadow: 0 0 0 3px var(--accent-soft);
}

.tier-popular-badge {
    position: absolute;
    top: -12px;
    left: 28px;
    background: var(--accent);
    color: #fff;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    padding: 4px 12px;
    border-radius: 999px;
}

.about-feature {
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: 16px;
    padding: 28px;
    /* Flex column so pricing.html's .tier-body (flex: 1 below) can push
       each card's action area (button/note) down to a shared bottom edge
       -- harmless for about.html's own use of this class (icon+heading+
       paragraph, no trailing action), since nothing there needs to grow. */
    display: flex;
    flex-direction: column;
}

.tier-body {
    flex: 1;
}

.about-feature-icon {
    width: 40px;
    height: 40px;
    border-radius: 10px;
    background: rgba(88, 101, 242, 0.14);
    display: flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 18px;
}

.about-feature h2 {
    font-size: 16px;
    margin: 0 0 10px;
}

.about-feature p {
    font-size: 13px;
    color: var(--text-muted);
    line-height: 1.6;
    margin: 0;
}

.about-feature code {
    background: var(--bg-input);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 1px 5px;
    font-size: 0.95em;
}

.download-note {
    margin-top: 14px;
    color: var(--text-muted);
    font-size: 13px;
}

/* A plain 1px rule, same as .about-footer-cta's own border-top below -- used as a visible section
   break before .about-showcase, so the page doesn't run straight from the feature-icon grid into
   the next section with no visual pause between them. */
.section-divider {
    height: 1px;
    background: var(--border);
    margin: 0 0 80px;
}

/* Kept from the old dashboard-teaser section -- still used as the small uppercase label above each
   .about-showcase-row's heading below. */
.about-teaser-kicker {
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--accent-2);
    margin-bottom: 14px;
}

/* Feature deep-dives on the About page (2026-09-29, second pass) -- replaced a single fake
   "dashboard" teaser card that just linked to the dashboard a second time. A first version reused
   the hero carousel's own screenshots in alternating rows; confirmed directly as reading like
   recycled content (two of the three images were the SAME ones shown seconds earlier in the hero
   above) with the portrait dictionary shot sitting oversized next to them. This version drops
   screenshots entirely: a 2x2 grid of graphic capability cards, richer than the plain 3-icon grid
   above (a short description plus real capability chips per card, not just one line), covering the
   four features that don't already get a real showcase elsewhere on this page.*/
.about-capabilities {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 24px;
    margin-bottom: 88px;
}

.about-capability-card {
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: 16px;
    padding: 32px;
}

.about-capability-card h2 {
    font-size: 19px;
    margin: 0 0 12px;
}

.about-capability-card p {
    font-size: 14px;
    line-height: 1.65;
    color: var(--text-muted);
    margin: 0 0 20px;
}

.about-capability-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

@media (max-width: 720px) {
    .about-capabilities {
        grid-template-columns: 1fr;
    }
}

.about-footer-cta {
    border-top: 1px solid var(--border);
    padding: 56px 0;
    text-align: center;
}

.about-footer-cta h2 {
    font-size: 24px;
    margin: 0 0 10px;
}

.about-footer-cta p {
    font-size: 14px;
    color: var(--text-muted);
    margin: 0 0 24px;
}

@media (max-width: 720px) {
    .about-features {
        grid-template-columns: 1fr;
    }
}

/* Sync token reveal */
.token-reveal {
    border-color: var(--success);
}

.token-value {
    display: block;
    margin-top: 12px;
    padding: 12px;
    background: var(--bg-input);
    border: 1px dashed var(--success);
    border-radius: 8px;
    font-size: 13px;
    word-break: break-all;
    color: var(--text);
}

/* Clickable job-card variant (dashboard.html's library teaser links
   straight to the reader) -- same look as the plain .job-card, just
   also a real link. */
.job-card-link {
    display: flex;
    flex-direction: column;
    gap: 4px;
    text-decoration: none;
    color: inherit;
}

.job-card-link:hover {
    border-color: var(--accent);
}

/* Library page -- ordering list */
.library-list {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.library-row {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 10px 14px;
    background: var(--bg-input);
    border: 1px solid var(--border);
    border-radius: 10px;
}

.job-cover.small {
    width: 40px;
    height: 52px;
    font-size: 16px;
    margin-bottom: 0;
    flex-shrink: 0;
}

.library-row-info {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.library-row-info .job-title {
    color: var(--text);
    text-decoration: none;
    font-size: 14px;
}

.library-row-info .job-title:hover {
    color: var(--accent);
}

.library-row-info .job-status,
.library-row-info .job-pages {
    font-size: 11px;
}

.library-row-info .job-pages {
    margin-left: 8px;
}

.library-row-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
}

.library-row-actions form {
    margin: 0;
}

.library-row-actions button.secondary {
    padding: 4px 10px;
    margin-top: 0;
}

.library-row-actions button.secondary:disabled {
    opacity: 0.35;
    cursor: not-allowed;
}

/* Reader page */
.read-disclaimer {
    margin-bottom: 16px;
    padding: 10px 14px;
    background: var(--bg-input);
    border: 1px solid var(--border);
    border-radius: 8px;
    color: var(--text-muted);
    font-size: 12px;
    line-height: 1.5;
}

.read-tab-radio {
    display: none;
}

.read-tab-buttons {
    display: flex;
    gap: 4px;
    margin-bottom: 16px;
}

.read-tab-buttons label {
    padding: 7px 16px;
    border: 1px solid var(--border);
    border-radius: 8px;
    font-size: 12px;
    font-weight: 600;
    color: var(--text-muted);
    cursor: pointer;
    background: var(--bg-raised);
}

#tab-image:checked ~ .read-tab-buttons label[for="tab-image"],
#tab-transcript:checked ~ .read-tab-buttons label[for="tab-transcript"] {
    color: var(--text);
    border-color: var(--accent);
}

.read-tab-panel {
    display: none;
}

#tab-image:checked ~ .read-image-panel,
#tab-transcript:checked ~ .read-transcript-panel {
    display: block;
}

.read-image-panel {
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 12px;
    text-align: center;
}

.read-page-image {
    max-width: 100%;
    border-radius: 6px;
}

.read-transcript-panel {
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 24px;
}

.read-transcript-list {
    margin: 0;
    padding-left: 22px;
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.read-transcript-list li {
    font-size: 13px;
}

.read-transcript-original {
    color: var(--text-muted);
}

.read-transcript-translation {
    color: var(--text);
    margin-top: 2px;
}

.read-nav-row {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: 12px;
    margin-top: 16px;
}

.read-nav-row--rtl {
    flex-direction: row-reverse;
}

.button-link.disabled {
    pointer-events: none;
    opacity: 0.35;
}

@media (max-width: 640px) {
    .about-hero h1 {
        font-size: 34px;
    }
}

/* Nav: account dropdown (avatar + Settings/admin links/theme/logout),
   plain <details>/<summary> -- native click-to-toggle disclosure, no JS,
   no checkbox hack needed since there's nothing else on the page that
   needs to reach into this state from outside it. */
.nav-account {
    position: relative;
}

.nav-account summary {
    list-style: none;
    cursor: pointer;
}

.nav-account summary::-webkit-details-marker,
.nav-account summary::marker {
    display: none;
    content: "";
}

.top-nav-avatar {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
}

.top-nav-avatar img {
    width: 26px;
    height: 26px;
    border-radius: 999px;
    overflow: hidden;
    object-fit: cover;
    border: 1px solid var(--border);
    display: block;
}

.nav-account-chevron {
    color: var(--text-muted);
    transition: transform 0.15s ease;
    flex-shrink: 0;
}

.nav-account[open] .nav-account-chevron {
    transform: rotate(180deg);
}

.nav-account-menu {
    position: absolute;
    top: calc(100% + 10px);
    right: 0;
    z-index: 50;
    display: none;
    flex-direction: column;
    gap: 2px;
    min-width: 200px;
    padding: 6px;
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: 10px;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
}

.nav-account[open] .nav-account-menu {
    display: flex;
}

.nav-account-menu a,
.nav-account-theme-button {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    margin: 0;
    padding: 9px 10px;
    border-radius: 8px;
    border: none;
    background: transparent;
    color: var(--text-muted);
    font-size: 13px;
    font-weight: 600;
    text-decoration: none;
    text-align: left;
    cursor: pointer;
    font-family: inherit;
}

.nav-account-menu a:hover,
.nav-account-theme-button:hover {
    background: var(--bg-input);
    color: var(--text);
}

.nav-account-menu a.nav-logout {
    color: var(--error);
}

.nav-account-menu a.nav-logout:hover {
    color: var(--error);
    background: var(--bg-input);
}

.nav-account-divider {
    height: 1px;
    margin: 4px 2px;
    background: var(--border);
}

.nav-account-theme-form {
    margin: 0;
}

.theme-toggle-form {
    margin: 0;
}

.theme-toggle-button {
    width: 36px;
    height: 36px;
    margin: 0;
    padding: 0;
    background: transparent;
    border: 0;
    border-radius: 8px;
    color: var(--text-muted);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
}

.theme-toggle-button:hover {
    color: var(--text);
    border-color: var(--accent);
    background: transparent;
}

/* --- Mobile nav: a real collapsible menu instead of just letting every
   link wrap onto more rows -- that held up fine with 3-4 links, but an
   admin sees several (Blog/Dashboard/Library/account dropdown), which
   just looks broken wrapped. Pure CSS (the same hidden-checkbox trick
   as the Settings tabs), no JS. Placed AFTER the base .top-nav-avatar/
   .nav-account rules above (not right after they're first mentioned
   near the nav markup) -- a real bug caught by actually loading a
   narrow viewport: with equal specificity, CSS resolves ties by source
   order, so this media query has to come after any unconditional rule
   it's meant to override, or the later unconditional rule silently
   wins even on a narrow screen. --- */
.nav-toggle-checkbox {
    display: none;
}

.nav-toggle-button {
    display: none;
    width: 38px;
    height: 38px;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: 8px;
    color: var(--text-muted);
    cursor: pointer;
    flex-shrink: 0;
    margin: 0; /* it's a <label>, not a form field -- without this it inherits the generic label { margin: 16px 0 6px }
       below and sits lower than the bell/logo next to it (confirmed: a real 5px vertical offset) */
}

.nav-toggle-button:hover {
    color: var(--text);
    background: var(--bg-raised);
}

.nav-toggle-button .icon-close {
    display: none;
}

.nav-toggle-checkbox:checked ~ .nav-toggle-button .icon-menu {
    display: none;
}

.nav-toggle-checkbox:checked ~ .nav-toggle-button .icon-close {
    display: block;
}

.top-nav-avatar-label {
    display: none;
}

@media (max-width: 700px) {
    .top-nav {
        flex-wrap: wrap;
        padding: 14px 18px;
    }

    .nav-toggle-button {
        display: flex;
    }

    .top-nav-links {
        display: none;
        width: 100%;
        flex-direction: column;
        align-items: stretch;
        gap: 2px;
        margin-top: 16px;
        padding-top: 16px;
        border-top: 1px solid var(--border);
    }

    .nav-toggle-checkbox:checked ~ .top-nav-links {
        display: flex;
    }

    .top-nav-links a,
    .top-nav-links summary {
        padding: 11px 10px;
        border-radius: 8px;
    }

    .top-nav-links a:hover,
    .top-nav-links summary:hover {
        background: var(--bg-raised);
    }

    .top-nav-links a.cta {
        text-align: center;
        margin-top: 6px;
    }

    .top-nav-avatar-label {
        display: inline;
        color: var(--text);
        font-size: 13px;
        font-weight: 600;
    }

    /* On mobile the whole nav is already an expanded panel -- the
       account menu drops down inline underneath its row instead of
       floating as a separate absolutely-positioned card. */
    .nav-account-menu {
        position: static;
        margin-top: 2px;
        padding: 0 0 0 12px;
        border: none;
        box-shadow: none;
        background: transparent;
    }

}

/* --- Generic pure-CSS tabs (Settings page) -- same radio-input trick as
   the reader's Image/Transcript toggle, generalized with a shared prefix
   so any number of tabs can share one pattern. --- */
.tabs-radio {
    display: none;
}

.tabs-buttons {
    display: flex;
    gap: 6px;
    margin-bottom: 20px;
    flex-wrap: wrap;
}

.tabs-buttons label {
    padding: 8px 16px;
    border: 1px solid var(--border);
    border-radius: 8px;
    font-size: 13px;
    font-weight: 600;
    color: var(--text-muted);
    cursor: pointer;
    background: var(--bg-raised);
}

.tabs-buttons label.danger-tab {
    color: var(--error);
    border-color: rgba(242, 67, 82, 0.3);
}
label.danger-tab:active {
    color: var(--error);
    border-color: rgba(242, 67, 82, 0.3);
}

#settings-tab-basic:checked ~ .tabs-buttons label[for="settings-tab-basic"],
#settings-tab-advanced:checked ~ .tabs-buttons label[for="settings-tab-advanced"],
#settings-tab-study:checked ~ .tabs-buttons label[for="settings-tab-study"],
#settings-tab-translation:checked ~ .tabs-buttons label[for="settings-tab-translation"],
#settings-tab-security:checked ~ .tabs-buttons label[for="settings-tab-security"],
#settings-tab-notifications:checked ~ .tabs-buttons label[for="settings-tab-notifications"] {
    color: var(--text);
    border-color: var(--accent);
}

#settings-tab-danger:checked ~ .tabs-buttons label[for="settings-tab-danger"] {
    color: white;
    background: var(--error);
    border-color: var(--error);
}

.tabs-panel {
    display: none;
}

#settings-tab-basic:checked ~ .tabs-panel-basic,
#settings-tab-advanced:checked ~ .tabs-panel-advanced,
#settings-tab-privacy:checked ~ .tabs-panel-privacy,
#settings-tab-study:checked ~ .tabs-panel-study,
#settings-tab-translation:checked ~ .tabs-panel-translation,
#settings-tab-security:checked ~ .tabs-panel-security,
#settings-tab-notifications:checked ~ .tabs-panel-notifications,
#settings-tab-billing:checked ~ .tabs-panel-billing,
#settings-tab-danger:checked ~ .tabs-panel-danger {
    display: flex;
    flex-direction: column;
    gap: 20px;
}

/* Settings: avatar upload -- click the avatar itself (hover shows a pencil
   icon) to open a small dropdown with Change photo / Remove, rather than a
   separate always-visible Upload button. Same <details> + hidden-marker
   technique as the nav-account dropdown, for the same reason: no JS needed
   for open/close. The actual <input type="file"> stays in its own form,
   completely hidden -- "Change photo" is a <label for> pointing at it
   (clicking a label associated with a file input opens the native file
   picker natively, no JS), and picking a file auto-submits via the one
   small inline onchange handler below. */
.avatar-row {
    display: flex;
    align-items: center;
    gap: 16px;
    flex-wrap: wrap;
}

.avatar-field-hint {
    margin: 0;
}

.avatar-edit {
    position: relative;
}

.avatar-preview-trigger {
    display: block;
    width: 80px;
    height: 80px;
    cursor: pointer;
    list-style: none;
}

.avatar-preview-trigger::-webkit-details-marker {
    display: none;
}

.avatar-preview-trigger::marker {
    content: "";
}

.avatar-preview {
    display: block;
    width: 80px;
    height: 80px;
    border-radius: 999px;
    overflow: hidden;
    border: 1px solid var(--border);
}

.avatar-preview img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.banner-edit .avatar-preview-trigger {
    width: 240px;
    height: 80px;
}

.banner-preview {
    width: 240px;
    height: 80px;
    border-radius: 10px;
    background: var(--bg);
}

.banner-edit .avatar-edit-hover {
    border-radius: 10px;
}

/* Named crop-dialog*, not cropper-modal* -- Cropper.js's own stylesheet ships a
   ".cropper-modal { opacity: .5 }" rule (its internal shading layer outside the crop
   box), and that class name collided with this wrapper, silently dimming the entire
   dialog to 50% opacity. */
.crop-dialog {
    position: fixed;
    inset: 0;
    z-index: 100;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
    background: rgba(0, 0, 0, 0.6);
}

.crop-dialog-inner {
    width: 100%;
    max-width: 560px;
    max-height: 90vh;
    overflow-y: auto;
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: 16px;
    padding: 24px;
}

.crop-dialog-inner h2 {
    margin: 0 0 16px;
    font-size: 18px;
}

.crop-dialog-stage {
    max-height: 60vh;
    overflow: hidden;
    background: var(--bg);
    border-radius: 10px;
}

.crop-dialog-stage img {
    display: block;
    max-width: 100%;
}

.crop-dialog-actions {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
    margin-top: 20px;
}

.crop-dialog-actions button {
    width: auto;
    margin: 0;
}

.avatar-edit-hover {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 999px;
    background: rgba(0, 0, 0, 0.45);
    color: #fff;
    opacity: 0;
    transition: opacity 0.15s ease;
    pointer-events: none;
}

.avatar-preview-trigger:hover .avatar-edit-hover,
.avatar-preview-trigger:focus-visible .avatar-edit-hover {
    opacity: 1;
}

.avatar-edit-menu {
    position: absolute;
    /* Close enough to read as attached to the avatar, not floating --
       was +8px, which combined with the (now-fixed) padding/margin bug
       below made it look like two disconnected, unevenly-spaced blocks. */
    top: calc(100% + 4px);
    left: 0;
    z-index: 20;
    display: none;
    flex-direction: column;
    gap: 2px;
    min-width: 160px;
    padding: 6px;
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: 10px;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
}

.avatar-edit[open] .avatar-edit-menu {
    display: flex;
}

/* Scoped under .avatar-edit-menu (two classes, specificity 0-2-0)
   rather than bare .avatar-edit-menu-item (0-1-0) -- confirmed directly
   as a real bug: .hub-shell button's specificity (0-1-1) otherwise beat
   the bare class rule for padding/margin on the <button> ("Remove")
   but not the <label> ("Change photo"), since only one of the two menu
   items is a real <button>. That's exactly what made them sit at
   different left edges with an oversized gap between them -- "Remove"
   was silently rendering with .hub-shell button's 18px side padding
   and 12px top margin instead of this rule's 10px/0. */
.avatar-edit-menu .avatar-edit-menu-item {
    display: block;
    width: 100%;
    margin: 0;
    padding: 9px 10px;
    border-radius: 8px;
    border: none;
    background: transparent;
    color: var(--text-muted);
    font-size: 13px;
    font-weight: 600;
    text-decoration: none;
    text-align: left;
    cursor: pointer;
    font-family: inherit;
}

.avatar-edit-menu .avatar-edit-menu-item:hover {
    background: var(--bg-input);
    color: var(--text);
}

.avatar-edit-menu .avatar-edit-menu-item-danger {
    color: var(--error);
}

.avatar-edit-menu .avatar-edit-menu-item-danger:hover {
    color: var(--error);
    background: var(--bg-input);
}

.avatar-upload-form {
    margin: 0;
}

.avatar-upload-form input[type="file"] {
    display: none;
}

/* Settings: sessions list */
.session-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.session-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 10px 14px;
    background: var(--bg-input);
    border: 1px solid var(--border);
    border-radius: 8px;
    font-size: 13px;
}

/* Same flex min-width fix as .admin-post-item above -- otherwise the
   device/IP text block can push the "Log out" button off-screen on a
   narrow phone instead of wrapping. */
.session-item > div:first-child {
    min-width: 0;
    flex: 1;
    overflow-wrap: break-word;
}

.session-item.current {
    border-color: var(--accent);
}

.session-meta {
    font-size: 11px;
    color: var(--text-muted);
    margin-top: 2px;
}

.session-item form {
    margin: 0;
}

/* Settings: checkbox rows (Advanced tab) */
.checkbox-row {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 16px 0 6px;
}

.checkbox-row input[type="checkbox"] {
    width: auto;
}

.checkbox-row label {
    margin: 0;
}

/* Settings: danger zone */
.danger-zone-panel {
    border-color: var(--error);
}

/* Theme toggle icon direction cue is handled inline via the sun/moon SVG
   swap in base.html -- nothing more needed here. */

/* --- Light theme -- one identity (Signal), a light variant of the same
   tokens. Toggled via [data-theme] on <html>, set from the `theme`
   cookie (see templating.py's resolve_theme()). --- */
:root[data-theme="light"] {
    --bg: #f7f7fa;
    --bg-raised: #ffffff;
    --bg-input: #eef0f4;
    --border: #dcdce4;
    --text: #17171c;
    --text-muted: #67677a;
    --accent: #5865f2;
    --accent-hover: #4752c4;
    --accent-2: #c97a2e;
    --accent-soft: rgba(88, 101, 242, 0.10);
    --error: #d0293a;
    --success: #237a41;
    --warn: #a2660a;
}

:root[data-theme="light"] .about-badge {
    color: #a2660a;
}

:root[data-theme="light"] .role-badge {
    background: rgba(88, 101, 242, 0.12);
    color: #3947c9;
}

:root[data-theme="light"] .role-badge.admin {
    background: rgba(208, 41, 58, 0.1);
    color: #b02233;
}

/* --- Toasts -- a general-purpose, reusable popup notification, not tied
   to any one feature. A route triggers one by stashing a message in the
   session right before a redirect (request.session["flash_notice"] =
   "..." or ["flash_error"] = "...") -- templating.py's pop_flash() reads
   and clears it exactly once, via the _toast.html partial included in
   base.html. Pure CSS, no JS: fades in, holds, then fades out on its
   own via animation; position:fixed means a lingering invisible element
   after the fade takes up no layout space and blocks nothing. --- */
.toast-stack {
    position: fixed;
    bottom: 24px;
    right: 24px;
    z-index: 1000;
    display: flex;
    flex-direction: column-reverse;
    gap: 10px;
    max-width: 340px;
    pointer-events: none;
}

.toast {
    pointer-events: auto;
    padding: 14px 16px;
    border-radius: 10px;
    font-size: 13px;
    line-height: 1.5;
    background: var(--bg-raised);
    border: 1px solid var(--border);
    color: var(--text);
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
    animation: toast-in 0.25s ease-out, toast-out 0.4s ease-in 5.5s forwards;
}

/* Colored, slightly transparent variants -- a tinted background + border
   + matching text color, same rgba-tint pattern .notice-inline/.error
   already use elsewhere in this app (not a plain accent-border stripe,
   which is all these used to be). success/error are the two with a real
   trigger today (flash_notice/flash_error in _toast.html); warn/info
   exist ready for whenever a route needs one -- e.g. a "some pages
   failed" partial-success message is a natural future use for warn. */
.toast-success {
    background: rgba(59, 165, 92, 0.16);
    border-color: rgba(59, 165, 92, 0.4);
    color: var(--success);
}

.toast-error {
    background: rgba(242, 67, 82, 0.16);
    border-color: rgba(242, 67, 82, 0.4);
    color: var(--error);
}

.toast-warn {
    background: rgba(232, 163, 61, 0.16);
    border-color: rgba(232, 163, 61, 0.4);
    color: var(--warn);
}

.toast-info {
    background: rgba(88, 101, 242, 0.16);
    border-color: rgba(88, 101, 242, 0.4);
    color: var(--accent);
}

/* The "plain blacker one" -- a neutral variant for a message that isn't
   really a success or a warning, just a fact ("Copied.", "Signed out
   everywhere else."). Deliberately hardcoded near-black rather than a
   theme token: like a Material snackbar, this one stays dark regardless
   of light/dark site theme, so it reads as a distinct, neutral style
   rather than "a success toast that forgot its color". */
.toast-plain {
    background: #101014;
    border-color: rgba(255, 255, 255, 0.1);
    color: #f2f2f5;
}

@keyframes toast-in {
    from {
        opacity: 0;
        transform: translateY(12px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

@keyframes toast-out {
    from {
        opacity: 1;
    }
    to {
        opacity: 0;
        visibility: hidden;
    }
}

@media (max-width: 480px) {
    .toast-stack {
        left: 16px;
        right: 16px;
        bottom: 16px;
        max-width: none;
    }
}

@media (max-width: 420px) {
    /* .job-grid's default minmax(140px, 1fr) needs ~294px of content
       width to fit even 2 columns -- on a real phone (~288px left
       after .hub-shell's and .panel's own padding), that math only
       ever finds room for 1, so every card -- no matter how many
       exist -- renders full-width and stretches very tall instead of
       forming an actual grid. Lower the minimum specifically at this
       width so 2 columns fit instead. */
    .job-grid {
        grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
        gap: 12px;
    }
}

/* Site footer */
.site-footer {
    margin-top: 64px;
    border-top: 1px solid var(--border);
}

.site-footer-inner {
    max-width: 900px;
    margin: 0 auto;
    padding: 28px 24px 40px;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 14px;
}

.site-footer-links {
    display: flex;
    flex-wrap: wrap;
    gap: 8px 20px;
}

.site-footer-links a,
.site-footer-links label {
    display: inline-block;
    color: var(--text-muted);
    font-size: 13px;
    text-decoration: none;
    margin: 0;
    cursor: pointer;
}

.site-footer-links a:hover,
.site-footer-links label:hover {
    color: var(--text);
}

.site-footer-copyright {
    color: var(--text-muted);
    font-size: 12px;
    display: flex;
    align-items: center;
    gap: 6px;
}

/* Legal / static content pages */
.legal-content h2 {
    font-size: 17px;
    margin: 28px 0 10px;
}

.legal-content h2:first-child {
    margin-top: 0;
}

.legal-content p,
.legal-content li {
    font-size: 14px;
    line-height: 1.7;
    color: var(--text-muted);
}

.legal-content ul {
    margin: 8px 0;
    padding-left: 22px;
}

.legal-updated {
    font-size: 12px;
    color: var(--text-muted);
    margin-bottom: 24px;
}

/* Roadmap -- a Trello-style board: one column per status, side by side on
   a wide screen, stacked on a narrow one. Every column always renders
   (even with zero items) so the board reads as a consistent table
   separated by status, rather than columns silently vanishing depending
   on what's currently in them. */
.roadmap-board {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 16px;
    align-items: start;
}

.roadmap-column {
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 18px;
    min-width: 0;
}

.roadmap-column-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin: 0;
    font-size: 15px;
}

.roadmap-column-count {
    font-size: 12px;
    font-weight: 600;
    color: var(--text-muted);
}

.roadmap-column-empty {
    margin-top: 16px;
}

@media (max-width: 900px) {
    .roadmap-board {
        grid-template-columns: 1fr;
    }
}

.roadmap-status-badge {
    display: inline-block;
    padding: 2px 10px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

.roadmap-status-planned {
    background: var(--bg-input);
    color: var(--text-muted);
}

.roadmap-status-in_progress {
    background: rgba(232, 163, 61, 0.16);
    color: var(--warn);
}

.roadmap-status-done {
    background: rgba(59, 165, 92, 0.16);
    color: var(--success);
}

.roadmap-status-parked {
    background: rgba(242, 67, 82, 0.12);
    color: var(--error);
}

.roadmap-list {
    display: flex;
    flex-direction: column;
    gap: 14px;
    margin-top: 16px;
    padding-top: 14px;
    border-top: 1px solid var(--border);
}

.roadmap-item {
    padding-bottom: 14px;
    border-bottom: 1px solid var(--border);
}

.roadmap-item:last-child {
    border-bottom: none;
    padding-bottom: 0;
}

.roadmap-item-title {
    font-size: 14px;
    font-weight: 600;
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.roadmap-item-category {
    font-size: 11px;
    font-weight: 600;
    color: var(--text-muted);
    background: var(--bg-input);
    padding: 2px 8px;
    border-radius: 999px;
}

.roadmap-item-description {
    font-size: 13px;
    color: var(--text-muted);
    line-height: 1.6;
    margin-top: 4px;
}

/* --- Cookie consent banner + Manage Options modal ---------------------
   Pure CSS, no JS: a hidden checkbox (#cookie-manage-toggle) toggles the
   modal open/closed via the same ":checked ~ sibling" technique already
   used for the mobile nav (.nav-toggle-checkbox). The whole partial is
   only rendered at all when no consent choice has been made yet
   (_cookie_banner.html's own {% if %}), so there's no separate JS-driven
   "hide after choosing" step needed either -- the next page load just
   doesn't include it. --- */
.cookie-manage-toggle {
    display: none;
}

.cookie-banner {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 900;
    background: var(--bg-raised);
    border-top: 1px solid var(--border);
    box-shadow: 0 -10px 30px rgba(0, 0, 0, 0.35);
}

.cookie-banner-inner {
    max-width: 1000px;
    margin: 0 auto;
    padding: 18px 24px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 24px;
    flex-wrap: wrap;
}

.cookie-banner-text {
    flex: 1 1 380px;
    min-width: 0;
}

.cookie-banner-text strong {
    display: block;
    font-size: 14px;
    margin-bottom: 4px;
}

.cookie-banner-text p {
    margin: 0;
    font-size: 13px;
    line-height: 1.6;
    color: var(--text-muted);
}

.cookie-banner-text a {
    color: var(--accent);
}

.cookie-banner-actions {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    flex: 0 0 auto;
}

.cookie-banner-form {
    margin: 0;
}

.cookie-banner-manage-btn,
.cookie-banner-form button {
    width: auto;
    margin: 0;
    padding: 9px 16px;
    font-size: 13px;
    white-space: nowrap;
    cursor: pointer;
}

.cookie-banner-manage-btn {
    display: inline-block;
    background: transparent;
    border: 1px solid var(--border);
    border-radius: 8px;
    color: var(--text);
    font-weight: 600;
}

.cookie-banner-manage-btn:hover {
    border-color: var(--accent);
}

.cookie-modal-backdrop {
    display: none;
    position: fixed;
    /* Explicit physical inset properties, not width:100vw/height:100vh --
       100vw in particular doesn't subtract scrollbar width, which can
       introduce a horizontal scrollbar that then eats into the real
       vertical viewport, confirmed directly as a real gap at the bottom
       even after the top-of-page gap (a separate, now-fixed <label>+
       position:fixed bug) was resolved. top/right/bottom/left:0 sizes
       against the actual containing-block edges instead, same as
       `inset: 0` -- inset:0 itself was never actually the problem; that
       was misattributed to this rule when the real cause was the
       backdrop being a <label>, not a <div>. */
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 950;
    background: rgba(0, 0, 0, 0.55);
    cursor: pointer;
}

.cookie-modal {
    display: none;
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 960;
    width: min(560px, calc(100vw - 32px));
    max-height: min(640px, calc(100vh - 64px));
    overflow-y: auto;
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: 14px;
    padding: 24px;
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}

.cookie-manage-toggle:checked ~ .cookie-modal-backdrop,
.cookie-manage-toggle:checked ~ .cookie-modal {
    display: block;
}

.cookie-modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 12px;
}

.cookie-modal-header h2 {
    font-size: 17px;
    margin: 0;
}

.cookie-modal-close {
    width: 28px;
    height: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 8px;
    font-size: 20px;
    line-height: 1;
    color: var(--text-muted);
    cursor: pointer;
    flex: 0 0 auto;
}

.cookie-modal-close:hover {
    background: var(--bg-input);
    color: var(--text);
}

.cookie-modal-intro {
    font-size: 13px;
    color: var(--text-muted);
    line-height: 1.6;
    margin: 0 0 18px;
}

.cookie-category {
    padding: 14px 0;
    border-top: 1px solid var(--border);
}

.cookie-category:first-of-type {
    border-top: none;
    padding-top: 0;
}

.cookie-category-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    font-size: 14px;
    font-weight: 600;
}

.cookie-category-header label {
    display: inline;
    margin: 0;
    font-size: 14px;
    font-weight: 600;
    color: var(--text);
}

.cookie-category-header input[type="checkbox"] {
    width: auto;
}

.cookie-category-badge {
    font-size: 11px;
    font-weight: 600;
    padding: 2px 8px;
    border-radius: 999px;
    white-space: nowrap;
}

.cookie-category-always-on {
    background: rgba(59, 165, 92, 0.15);
    color: var(--success);
}

.cookie-category-none {
    background: var(--bg-input);
    color: var(--text-muted);
}

/* Same muted-pill treatment as the cookie-category badges above, reused on
   the pricing page for planned-but-not-yet-built features -- kept as its
   own class rather than borrowing the cookie-specific names verbatim. */
.badge-soon {
    display: inline-block;
    margin-left: 6px;
    font-size: 11px;
    font-weight: 600;
    padding: 2px 8px;
    border-radius: 999px;
    white-space: nowrap;
    background: var(--bg-input);
    color: var(--text-muted);
}

/* Same pill, but for a feature that's actually built and working today --
   just not open to this plan yet (Super Premium/admin only while it's in
   early access). Warm/amber instead of muted gray: it exists, unlike
   .badge-soon's genuinely-not-built-yet meaning. */
.badge-early {
    display: inline-block;
    margin-left: 6px;
    font-size: 11px;
    font-weight: 600;
    padding: 2px 8px;
    border-radius: 999px;
    white-space: nowrap;
    background: rgba(242, 166, 94, 0.16);
    color: var(--accent-2);
}

.cookie-category p {
    margin: 6px 0 0;
    font-size: 13px;
    color: var(--text-muted);
    line-height: 1.6;
}

.cookie-list {
    margin: 8px 0 0;
    padding-left: 18px;
    font-size: 12px;
    color: var(--text-muted);
    line-height: 1.7;
}

.cookie-modal-form {
    margin: 0;
}

.cookie-modal-actions {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
    margin-top: 18px;
}

.cookie-modal-cancel,
.cookie-modal-actions button {
    width: auto;
    margin: 0;
    padding: 9px 18px;
    font-size: 13px;
    cursor: pointer;
}

.cookie-modal-cancel {
    display: inline-flex;
    align-items: center;
    background: transparent;
    border: 1px solid var(--border);
    border-radius: 8px;
    color: var(--text);
}

.cookie-modal-cancel:hover {
    border-color: var(--accent);
}

@media (max-width: 600px) {
    .cookie-banner-inner {
        flex-direction: column;
        align-items: stretch;
    }

    .cookie-banner-actions {
        justify-content: stretch;
    }

    .cookie-banner-manage-btn,
    .cookie-banner-form {
        flex: 1 1 auto;
    }

    .cookie-banner-form button {
        width: 100%;
    }
}


/* Usage bars (settings > Billing) */
.usage-row { margin-top: 18px; }
.usage-label { display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 6px; }
.usage-label span { color: var(--text-muted, #8a8a94); }
.usage-bar { height: 10px; border-radius: 999px; background: var(--surface-2, rgba(128, 128, 140, 0.25)); overflow: hidden; }
.usage-bar > div { height: 100%; border-radius: 999px; background: var(--accent, #6d5ef0); min-width: 2px; transition: width 0.3s ease; }
/* Fills in from the left when the page opens, rather than appearing already full. */
.usage-bar > div { transform-origin: left center; animation: usage-bar-in 0.9s cubic-bezier(.2, .7, .2, 1) both; }
@keyframes usage-bar-in { from { transform: scaleX(0); } }
@media (prefers-reduced-motion: reduce) { .usage-bar > div { animation: none; } }
.usage-bar-warn > div { background: #e0a030; }
.usage-bar-full > div { background: #d9534f; }

/* Other ways to sign in (passkey, Discord, Patreon) under the login and sign-up forms */
.alt-signin { display: flex; flex-direction: column; gap: 10px; margin-top: 18px; }
.alt-signin-or { display: flex; align-items: center; gap: 12px; color: var(--text-muted); font-size: 12px; }
.alt-signin-or::before, .alt-signin-or::after { content: ""; flex: 1; height: 1px; background: var(--border); }
.alt-signin-button {
    display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%; box-sizing: border-box;
    padding: 10px 14px; border-radius: 8px; border: 1px solid var(--border); background: var(--bg-input);
    color: var(--text); font-size: 14px; font-weight: 600; text-decoration: none; cursor: pointer; margin-top: 0;
}
.alt-signin-button:hover { border-color: var(--accent); text-decoration: none; }
.alt-signin-button[hidden] { display: none; }
.alt-signin-button:disabled { opacity: .6; cursor: default; }

/* Passkeys in Settings */
.passkey-row { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px; padding: 12px 0; border-top: 1px solid var(--border); }
.passkey-actions { display: flex; flex-wrap: wrap; gap: 10px; }
.passkey-inline { display: flex; align-items: center; gap: 8px; }
.passkey-inline input { width: 150px; margin: 0; }
.passkey-inline button { margin-top: 0; }
.passkey-note { flex-basis: 100%; margin-top: -4px; }

/* The old address says it has moved (_moved_notice.html) */
.moved-notice { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: 6px 14px; width: 100%; box-sizing: border-box; padding: 8px 16px; background: var(--accent); color: #fff; font-size: 0.9rem; text-align: center; }
.moved-notice a { color: #fff; font-weight: 600; text-decoration: underline; white-space: nowrap; }
.auth-body .moved-notice { position: fixed; top: 0; left: 0; right: 0; z-index: 50; }
.auth-body:has(.moved-notice) .auth-language { top: 54px; }
@media (max-width: 600px) { .auth-body:has(.moved-notice) .auth-language { top: 92px; } }
.passkey-add { margin-top: 12px; border-top: 1px solid var(--border); padding-top: 12px; }
#passkey-add-button[hidden], #passkey-unsupported[hidden], #passkey-error[hidden] { display: none; }

/* Shared library */
.shared-search { display: flex; gap: 8px; margin: 0 0 16px; }
.shared-search input { flex: 1 1 auto; margin: 0; }
.shared-search button { margin-top: 0; }
.shared-pager { display: flex; align-items: center; justify-content: center; gap: 16px; margin-top: 16px; }
/* Previous / Page N of M / Next under any paged list (templates/_pager.html) */
.pager { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 12px 16px; margin: 20px 0 4px; }
.pager .field-hint { margin: 0; }
.check-row { display: flex; align-items: flex-start; gap: 10px; margin: 12px 0; font-size: 14px; }
.check-row input { margin-top: 4px; width: auto; }
.mod-report { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; flex-wrap: wrap; padding: 14px 0; border-top: 1px solid var(--border); }
.mod-actions { display: flex; flex-direction: column; gap: 8px; flex: 0 0 min(460px, 100%); min-width: 0; }
.mod-report > div:first-child { flex: 1 1 240px; min-width: 0; }
.mod-actions.mod-actions-wide { flex: 1 1 100%; }
.mod-actions form { display: flex; gap: 8px; align-items: center; }
.mod-actions input { margin: 0; width: 100%; }
.mod-actions-row { display: flex; gap: 8px; align-items: center; justify-content: flex-end; flex-wrap: wrap; }
.mod-actions-row form { margin: 0; }
.hub-shell .mod-actions .mod-actions-row button, .hub-shell .mod-actions .mod-actions-row .button-link { box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; height: 38px; min-width: 110px; margin: 0; padding: 0 18px; font-size: 14px; font-weight: 600; line-height: 1; border-radius: 8px; text-decoration: none; }
.mod-actions button { margin-top: 0; }

/* Shared library and library covers: the first page as the cover, with the initial behind it as a fallback */
.job-cover.has-image { position: relative; overflow: hidden; }
.job-cover.has-image img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: top; }
.subtitle a, .field-hint a, .read-disclaimer a { color: var(--accent); text-decoration: none; }
.subtitle a:hover, .field-hint a:hover, .read-disclaimer a:hover { text-decoration: underline; }
.mod-report a.job-title { color: var(--text); font-weight: 600; text-decoration: none; }
.mod-report a.button-link { color: var(--accent); }
.mod-actions button { white-space: nowrap; }


/* Library: MangaDex-style covers with no boxes, a tab strip, and the manga page */
.lib-tabs { display: flex; gap: 4px; margin: 0 0 20px; border-bottom: 1px solid var(--border); overflow-x: auto; overflow-y: hidden; scrollbar-width: none; }
.lib-tab { display: inline-flex; align-items: center; flex: 0 0 auto; white-space: nowrap; padding: 10px 16px; color: var(--text-muted); text-decoration: none; font-weight: 600; font-size: 14px; border-bottom: 2px solid transparent; margin-bottom: -1px; }
.lib-tab:hover { color: var(--text); }
.lib-tab.active { color: var(--text); border-bottom-color: var(--accent); }
.lib-tab-count { font-size: 11px; background: var(--bg-raised); border-radius: 999px; padding: 1px 7px; margin-left: 4px; }
.manga-filters { display: flex; gap: 8px; flex-wrap: wrap; margin: 0 0 20px; }
.manga-filters input[type="text"] { flex: 1 1 260px; margin: 0; }
.manga-filters select { margin: 0; width: auto; }
.manga-filters button { margin-top: 0; }
.manga-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 24px 18px; }
.manga-card { display: block; color: var(--text); text-decoration: none; }
.manga-cover { position: relative; aspect-ratio: 2 / 3; border-radius: 8px; overflow: hidden; background: linear-gradient(160deg, var(--accent), #3a3f9e); display: flex; align-items: center; justify-content: center; }
.manga-cover img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: top; }
.manga-cover-letter { font-size: 42px; font-weight: 700; color: rgba(255, 255, 255, .85); }
.manga-card:hover .manga-cover { outline: 2px solid var(--accent); }
.manga-title { margin-top: 8px; font-weight: 600; font-size: 14px; line-height: 1.3; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.manga-meta { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
.manga-progress { color: var(--accent); }
.manga-badge { position: absolute; top: 6px; left: 6px; font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 999px; background: rgba(0, 0, 0, .65); color: #fff; }
.manga-badge-down { background: var(--error); }

.manga-list { display: flex; flex-direction: column; gap: 10px; }
.manga-list-row { display: flex; gap: 14px; align-items: center; padding: 10px; border-radius: 10px; color: var(--text); text-decoration: none; }
.manga-list-row:hover { background: var(--bg-raised); }
.manga-list-cover { position: relative; flex: 0 0 48px; width: 48px; height: 64px; border-radius: 6px; overflow: hidden; background: linear-gradient(160deg, var(--accent), #3a3f9e); display: flex; align-items: center; justify-content: center; }
.manga-list-cover img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: top; }
.manga-list-cover .manga-cover-letter { font-size: 18px; }
.manga-list-info { min-width: 0; flex: 1 1 auto; }
.manga-list-title { font-weight: 600; font-size: 14px; display: flex; align-items: center; gap: 8px; }
.manga-list-badge { font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 999px; background: rgba(255, 255, 255, .08); color: var(--text-muted); }
.manga-hero { display: flex; gap: 28px; margin: 8px 0 28px; }
.manga-hero-cover { flex: 0 0 200px; }
.manga-hero-info { flex: 1 1 auto; min-width: 0; }
.manga-hero-info h1 { margin: 0 0 10px; }
.manga-facts { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
.manga-chip { font-size: 12px; padding: 3px 10px; border-radius: 999px; background: var(--bg-raised); color: var(--text-muted); text-decoration: none; border: 1px solid var(--border); }
a.manga-chip:hover { color: var(--text); border-color: var(--accent); }
.manga-chip-category { background: color-mix(in srgb, var(--accent) 18%, transparent); color: var(--accent); border-color: transparent; font-weight: 600; }
.manga-byline { color: var(--text-muted); font-size: 13px; margin: 4px 0; }
.manga-byline a { color: var(--accent); text-decoration: none; }
.manga-description { margin: 14px 0; line-height: 1.6; max-width: 70ch; white-space: pre-wrap; }
.manga-actions { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-top: 16px; }
.manga-section { margin: 0 0 32px; }
.manga-section h2 { font-size: 18px; margin: 0 0 12px; }
.chapter-group { margin-bottom: 14px; }
.chapter-volume { font-size: 12px; text-transform: uppercase; letter-spacing: .08em; color: var(--text-muted); padding: 6px 0; }
.chapter-row { display: flex; justify-content: space-between; gap: 12px; padding: 10px 12px; border-radius: 8px; color: var(--text); text-decoration: none; }
.chapter-row:hover { background: var(--bg-raised); }
.chapter-meta { color: var(--text-muted); font-size: 13px; }
.review-form { display: flex; flex-direction: column; gap: 8px; margin-bottom: 18px; max-width: 640px; }
.review-form select, .review-form textarea { margin: 0; }
.review-form button { align-self: flex-start; margin-top: 0; }
.review { padding: 12px 0; border-top: 1px solid var(--border); }
.review p { margin: 6px 0; white-space: pre-wrap; }
.review-stars { color: #f0b429; letter-spacing: 2px; margin-left: 6px; }
.link-button { background: none; border: 0; padding: 0; margin: 0; width: auto; color: var(--text-muted); font-size: 12px; cursor: pointer; text-decoration: underline; }
.form-row { display: flex; gap: 16px; flex-wrap: wrap; }
.form-row > div { flex: 1 1 200px; }
.chapter-edit { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; padding: 10px 0; border-top: 1px solid var(--border); }
.chapter-edit-order { display: flex; gap: 4px; }
.chapter-edit-order button, .chapter-edit button { margin-top: 0; }
.chapter-edit-fields { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.chapter-edit-fields label { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-muted); margin: 0; }
.chapter-edit-fields input { margin: 0; width: auto; }
.chapter-edit-title input { min-width: 180px; }
.reader-bar { display: flex; justify-content: space-between; align-items: center; gap: 16px; flex-wrap: wrap; margin: 0 0 14px; }
.reader-titles { display: flex; flex-direction: column; }
.reader-selects { display: flex; gap: 8px; flex-wrap: wrap; }
.reader-selects select { margin: 0; width: auto; }
.read-image-link { display: block; }
.job-list { display: flex; flex-direction: column; }
.job-row { display: flex; justify-content: space-between; gap: 16px; align-items: center; flex-wrap: wrap; padding: 14px 0; border-top: 1px solid var(--border); }
.job-row:first-child { border-top: 0; }
/* The standalone /library/jobs page sat directly on the page background with these same flat
   top-border dividers -- the pre-redesign look. Dashboard's own "Jobs" widget reuses .job-list/.job-row
   too, but already sits inside its own .panel card (dashboard.html), so a blanket card treatment here
   would nest a second bordered box inside that one. Scoped to .job-list-page, added only on the
   standalone page's own wrapper, so the Dashboard widget is untouched. */
.job-list-page { display: flex; flex-direction: column; gap: 2px; background: var(--bg-raised); border: 1px solid var(--border); border-radius: 14px; padding: 6px; }
.job-list-page .job-row { border-top: 0; border-radius: 10px; padding: 12px; }
.job-list-page .job-row:hover { background: var(--bg-input); }
/* Also switched the progress track from bg-raised to the same translucent .usage-bar track color,
   since a solid bg-raised track became invisible once the row itself sits on a bg-raised card. */
.job-list-page .job-row-bar { background: var(--surface-2, rgba(128, 128, 140, 0.25)); }
.job-row-main { flex: 1 1 320px; min-width: 0; }
.job-row-title { color: var(--text); font-weight: 600; text-decoration: none; }
.job-row-progress { display: flex; align-items: center; gap: 12px; margin: 6px 0; }
.job-row-bar { flex: 0 1 260px; width: 260px; height: 6px; border-radius: 999px; background: var(--bg-raised); overflow: hidden; }
.job-row-fill { height: 100%; background: var(--accent); }
.job-row-fill.status-done { background: #3ba55d; }
.job-row-fill.status-failed { background: var(--error); }
.job-row-count { font-size: 12px; color: var(--text-muted); }
.job-row-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.job-row-actions button, .job-row-actions select { margin: 0; }
.job-row-actions select { width: auto; }
.job-row-add { display: flex; gap: 6px; }

/* Study preview on the main dashboard: the same .study-stats/.study-bars from study.css, just a bit
   shorter here since it's a glance, not the full Progress page. */
.dash-study-stats { margin-bottom: 14px; }
.dash-study-bars { height: 64px; gap: 6px; }
.notify-table { width: 100%; border-collapse: collapse; margin: 12px 0; }
.notify-table th, .notify-table td { text-align: left; padding: 10px 8px; border-bottom: 1px solid var(--border); font-size: 14px; }
.notify-table th:not(:first-child), .notify-table td:not(:first-child) { text-align: center; width: 100px; }
.notify-table input { width: auto; margin: 0; }
/* The bell dropdown (.nav-bell-panel) is already its own bg-raised card with flat dividers between
   rows, like any dropdown menu -- that's kept as-is. The full /notifications page used to sit directly
   on the page background with the same flat dividers, which read as much flatter than every other list
   on the site since the redesign (Library's jobs, comments, etc. all sit inside a card). `.notif-list`
   only exists on the standalone page (the dropdown uses `.nav-bell-items` as its row container instead),
   so scoping the card+rounded-row treatment to it leaves the dropdown untouched. */
.notif-list { display: flex; flex-direction: column; gap: 2px; width: 100%; margin-top: 24px; background: var(--bg-raised); border: 1px solid var(--border); border-radius: 14px; padding: 6px; }
.notif-row { display: flex; align-items: stretch; border-top: 1px solid var(--border); }
.notif-row .notif-item { flex: 1 1 auto; min-width: 0; margin: 0; }
.notif-row .notif-button { border-top: 0; }
.notif-list .notif-row { border-top: 0; border-radius: 10px; }
.notif-actions { display: flex; align-items: center; gap: 2px; padding-right: 8px; }
.notif-actions form { margin: 0; }
.hub-shell .notif-icon, .nav-bell-panel .notif-icon { width: 32px; height: 32px; margin: 0; padding: 0; display: inline-flex; align-items: center; justify-content: center; background: none; border: 0; border-radius: 8px; color: var(--text-muted); cursor: pointer; }
.hub-shell .notif-icon:hover, .nav-bell-panel .notif-icon:hover { background: var(--bg-raised); color: var(--text); }
.notif-head-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.notif-head-actions form { margin: 0; }
.notif-head-actions button { margin-top: 0; }
.hub-shell .notif-button, .nav-bell-panel .notif-button { font: inherit; font-weight: 400; display: flex; flex-direction: column; gap: 2px; width: 100%; text-align: left; margin: 0; padding: 12px 14px; background: none; border: 0; border-top: 1px solid var(--border); border-radius: 0; color: var(--text); cursor: pointer; }
.notif-list .notif-button { border-top: 0; border-radius: 10px; }
.notif-row:hover { background: var(--bg-raised); }
.notif-list .notif-row:hover { background: var(--bg-input); }
.notif-row.notif-unread:hover { background: color-mix(in srgb, var(--accent) 15%, transparent); }
.notif-row.notif-unread { background: color-mix(in srgb, var(--accent) 9%, transparent); }
.notif-title { position: relative; }
.notif-row.notif-unread .notif-title { font-weight: 700; padding-left: 16px; }
.notif-row.notif-unread .notif-title::before { content: ""; position: absolute; left: 0; top: .5em; width: 8px; height: 8px; border-radius: 50%; background: var(--accent); }
.notif-row:not(.notif-unread) .notif-title { font-weight: 500; color: var(--text-muted); }
.notif-title { font-weight: 600; font-size: 15px; }
.notif-body, .notif-time { font-size: 12px; color: var(--text-muted); }
.nav-bell { position: relative; display: inline-flex; align-items: center; margin-right: 6px; }
.nav-bell-button { position: relative; background: none; border: 0; padding: 8px; margin: 0; width: auto; color: var(--text-muted); cursor: pointer; border-radius: 8px; display: inline-flex; }
.nav-bell-button:hover { color: var(--text); background: var(--bg-raised); }
.nav-bell-count { position: absolute; top: 2px; right: 0; min-width: 16px; height: 16px; padding: 0 4px; border-radius: 999px; background: var(--error); color: #fff; font-size: 10px; font-weight: 700; display: flex; align-items: center; justify-content: center; }
.nav-bell-count[hidden], .nav-bell-panel[hidden] { display: none; }
.nav-bell-panel { position: absolute; right: 0; top: calc(100% + 8px); width: 340px; max-width: 92vw; background: var(--bg-raised); border: 1px solid var(--border); border-radius: 12px; box-shadow: 0 12px 32px rgba(0, 0, 0, .35); z-index: 60; overflow: hidden; }
.nav-bell-head { display: flex; justify-content: space-between; align-items: center; padding: 12px 14px; border-bottom: 1px solid var(--border); }
.nav-bell-items { max-height: 440px; overflow-y: auto; scrollbar-width: thin; scrollbar-color: var(--border) transparent; }
.nav-bell-items .field-hint { padding: 14px; }
.nav-bell-all { display: block; text-align: center; padding: 10px; color: var(--accent); text-decoration: none; font-size: 13px; border-top: 1px solid var(--border); }
.toast-stack { position: fixed; right: 16px; bottom: 16px; display: flex; flex-direction: column; gap: 10px; z-index: 80; }
.toast { display: flex; flex-direction: column; gap: 2px; max-width: 320px; padding: 12px 14px; background: var(--bg-raised); border: 1px solid var(--border); border-left: 3px solid var(--accent); border-radius: 10px; box-shadow: 0 10px 28px rgba(0, 0, 0, .4); color: var(--text); text-decoration: none; font-size: 13px; transition: opacity .35s, transform .35s; }
.toast span { color: var(--text-muted); font-size: 12px; }
.toast-out { opacity: 0; transform: translateY(8px); }
@media (max-width: 700px) {
    .manga-hero { flex-direction: column; }
    .manga-hero-cover { flex-basis: auto; max-width: 180px; }
    .manga-grid { grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 20px 14px; }
}

.form-actions-end { display: flex; justify-content: flex-end; }
.form-actions-end button { width: auto; }

/* Manga page: stats, icon buttons, tabs, comments */
.manga-hero-info h1 { font-size: 30px; line-height: 1.2; margin: 0 0 6px; }
.manga-facts { margin: 12px 0; }
.manga-chip { text-transform: uppercase; letter-spacing: .04em; font-size: 11px; font-weight: 600; padding: 4px 10px; border-radius: 6px; }
.manga-stats { display: flex; flex-wrap: wrap; gap: 6px 18px; margin: 12px 0 4px; color: var(--text-muted); font-size: 14px; }
.manga-stat { display: inline-flex; align-items: center; gap: 6px; }
.manga-stat svg { color: #f0b429; }
.manga-stat + .manga-stat svg { color: var(--text-muted); }
.manga-actions { flex-wrap: nowrap; gap: 8px; margin-top: 18px; max-width: 560px; }
.manga-icon-form { margin: 0; display: flex; }
.hub-shell .manga-icon-button { width: 46px; height: 46px; margin: 0; padding: 0; display: inline-flex; align-items: center; justify-content: center; flex: 0 0 46px; border: 0; border-radius: 10px; background: var(--bg-raised); color: var(--text); cursor: pointer; }
.hub-shell .manga-icon-button:hover { background: color-mix(in srgb, var(--accent) 22%, var(--bg-raised)); }
.hub-shell .manga-icon-button.is-on { background: var(--accent); color: #fff; }
.manga-actions .manga-read { flex: 1 1 auto; height: 46px; display: inline-flex; align-items: center; justify-content: center; margin: 0; padding: 0 18px; border-radius: 10px; text-align: center; }
.manga-links { display: flex; gap: 16px; margin: 10px 0 0; font-size: 13px; }
.manga-links a { color: var(--accent); text-decoration: none; }
.manga-links a:hover { text-decoration: underline; }
.manga-dates { color: var(--text-muted); font-size: 13px; margin-top: 12px; }
.manga-tabs { display: flex; gap: 4px; margin: 8px 0 20px; padding: 4px; border-radius: 12px; background: var(--bg-raised); overflow-x: auto; }
.manga-tabs a { flex: 0 0 auto; padding: 9px 16px; border-radius: 8px; color: var(--text-muted); font-weight: 600; font-size: 14px; text-decoration: none; white-space: nowrap; }
.manga-tabs a:hover { color: var(--text); }
.manga-tabs a.is-on { background: var(--bg); color: var(--text); }
.tabs-on .manga-panel[hidden] { display: none; }
.manga-panel { margin: 0 0 32px; }
.chapter-row { align-items: center; padding: 12px 14px; }
.chapter-row.is-current { box-shadow: inset 3px 0 0 var(--accent); background: color-mix(in srgb, var(--accent) 8%, transparent); }
.chapter-name { font-weight: 500; }
.comment-form { display: flex; flex-direction: column; gap: 10px; margin: 0 0 22px; width: 100%; }
.comment-form textarea, .comment-form select { margin: 0; width: 100%; box-sizing: border-box; }
.comment-form button { align-self: flex-end; width: auto; margin: 0; padding: 9px 22px; }
.comment { display: flex; gap: 14px; padding: 16px; margin: 0 0 10px; border-radius: 12px; background: var(--bg-raised); }
.comment-avatar { flex: 0 0 44px; width: 44px; height: 44px; border-radius: 10px; object-fit: cover; background: var(--bg); }
.comment > a { flex: 0 0 44px; display: block; }
.comment-main { flex: 1 1 auto; min-width: 0; }
.comment-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; }
.comment-name { font-size: 15px; font-weight: 700; color: var(--text); text-decoration: none; }
a.comment-name:hover { text-decoration: underline; }
.comment-badge { font-size: 11px; font-weight: 600; padding: 1px 8px; border-radius: 999px; background: color-mix(in srgb, var(--accent) 20%, transparent); color: var(--accent); }
.comment-badge-plain { background: var(--bg); color: var(--text-muted); }
.comment-date { margin-left: auto; font-size: 12px; color: var(--text-muted); }
.comment-body { margin: 6px 0 0; line-height: 1.55; white-space: pre-wrap; overflow-wrap: anywhere; }
.comment-actions { margin: 8px 0 0; }
.comment .review-stars { margin-left: 0; }

/* Bell on a phone: a sheet under the top bar instead of a small box */
@media (max-width: 700px) {
    .top-nav-inner > .nav-bell { margin-left: auto; margin-right: 10px; }
    .nav-bell-panel { position: fixed; left: 8px; right: 8px; top: 64px; width: auto; max-width: none; }
    .nav-bell-items { max-height: 60vh; }
    .manga-hero-info h1 { font-size: 24px; }
    .manga-actions { max-width: none; }
    .comment { padding: 12px; gap: 10px; }
}

/* Manga page: title row with the edit icon, folding description */
.manga-title-row { display: flex; align-items: flex-start; gap: 10px; }
.manga-title-row h1 { margin: 0 0 6px; }
.manga-edit { flex: 0 0 auto; margin-top: 4px; width: 34px; height: 34px; display: inline-flex; align-items: center; justify-content: center; border-radius: 8px; color: var(--text-muted); text-decoration: none; }
.manga-edit:hover { background: var(--bg-raised); color: var(--text); }
.manga-description.is-clamped { display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden; }
.manga-description-wrap { margin: 14px 0; }
.manga-description-wrap .manga-description { margin: 0; }
.hub-shell .manga-more { width: auto; margin: 6px 0 0; padding: 0; background: none; border: 0; color: var(--accent); font-size: 13px; font-weight: 600; cursor: pointer; }
.hub-shell .manga-more:hover { text-decoration: underline; background: none; }
.field-count { float: right; font-size: 12px; color: var(--text-muted); }
.hub-shell .mod-actions .mod-actions-row .button-link { background: var(--bg-raised); border: 1px solid var(--border); color: var(--text); }
.hub-shell .mod-actions .mod-actions-row .button-link:hover { border-color: var(--accent); }
.hub-shell .mod-actions .mod-actions-row button.danger { border-width: 1px; }

.hub-shell .notif-row .notif-button, .nav-bell-panel .notif-row .notif-button { border-top: 0; }

/* Reading list: status and rating menus on the manga page, the status chips, scores */
.pop { position: relative; flex: 0 0 auto; }
.pop > summary { list-style: none; cursor: pointer; }
.pop > summary::-webkit-details-marker { display: none; }
.hub-shell .manga-icon-button { position: relative; }
.manga-icon-number { position: absolute; right: 2px; bottom: 2px; min-width: 16px; padding: 0 3px; border-radius: 999px; background: var(--bg); color: var(--text); font-size: 10px; font-weight: 700; line-height: 16px; text-align: center; }
.pop-menu { position: absolute; z-index: 30; top: calc(100% + 6px); left: 0; min-width: 220px; padding: 6px; border-radius: 12px; background: var(--bg-raised); border: 1px solid var(--border); box-shadow: 0 14px 34px rgba(0, 0, 0, .4); }
.pop-title { padding: 8px 10px 6px; font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--text-muted); }
.pop-menu form { margin: 0; }
.hub-shell .pop-item { display: flex; align-items: center; gap: 10px; width: 100%; margin: 0; padding: 9px 10px; border: 0; border-radius: 8px; background: none; color: var(--text); font-size: 14px; font-weight: 500; text-align: left; cursor: pointer; }
.hub-shell .pop-item:hover { background: color-mix(in srgb, var(--accent) 16%, transparent); }
.hub-shell .pop-item.is-on { background: var(--accent); color: #fff; }
.hub-shell .pop-remove { color: var(--text-muted); border-top: 1px solid var(--border); border-radius: 0 0 8px 8px; margin-top: 4px; }
.pop-num { display: inline-block; min-width: 20px; font-weight: 700; }
.review-score { font-weight: 700; color: #f0b429; }
.review-score-of { font-weight: 400; color: var(--text-muted); font-size: 12px; }
.status-chips { display: flex; gap: 8px; flex-wrap: wrap; margin: 0 0 16px; }
.status-chip { display: inline-flex; align-items: center; gap: 6px; padding: 7px 14px; border-radius: 999px; background: var(--bg-raised); border: 1px solid transparent; color: var(--text-muted); font-size: 13px; font-weight: 600; text-decoration: none; white-space: nowrap; }
.status-chip:hover { color: var(--text); }
.status-chip.is-on { background: color-mix(in srgb, var(--accent) 20%, transparent); color: var(--text); border-color: var(--accent); }
.status-chip .lib-tab-count { margin-left: 0; background: var(--bg); }
.manga-badge-status { background: var(--accent); }
@media (max-width: 700px) { .pop-menu { position: fixed; left: 16px; right: 16px; top: auto; bottom: 16px; max-height: 70vh; overflow-y: auto; } }

/* ---- The shared shell (every page): icon links in the top bar, a sidebar on wide screens and a tab bar on phones.
   What the sidebar shows depends on the area you're in (see templates/_app_nav.html). ---- */
.top-nav-links { flex: 1 1 auto; gap: 4px; margin-left: 28px; }
.top-nav-links .nav-icon-link {
    display: inline-flex; align-items: center; gap: 8px; padding: 8px 12px; border: 0; border-radius: 8px;
    color: var(--text-muted); font-size: 13px; font-weight: 600; text-decoration: none;
}
.top-nav-links .nav-icon-link:hover { background: var(--bg-raised); color: var(--text); }
.top-nav-links .nav-bell, .top-nav-links > .cta { margin-left: auto; }
.study-icon { flex: none; }

.hub-shell.has-side { max-width: 1240px; flex-direction: row; align-items: flex-start; gap: 32px; }
.app-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 20px; }
.app-main [hidden] { display: none !important; }

.side-nav { position: sticky; top: 24px; flex: 0 0 216px; display: flex; flex-direction: column; gap: 16px; }
.side-plan { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 0 4px; }
.side-plan a { font-size: 13px; color: var(--accent); text-decoration: none; font-weight: 600; }
.side-plan a:hover { text-decoration: underline; }
.side-plan-chip {
    font-size: 12px; font-weight: 600; letter-spacing: .02em; padding: 4px 10px; border-radius: 999px;
    background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--accent);
}
.side-list { display: flex; flex-direction: column; gap: 2px; }
.side-heading { font-size: 11px; text-transform: uppercase; letter-spacing: .08em; color: var(--text-muted); padding: 6px 12px; }
.side-heading-spaced { margin-top: 14px; }
.side-link {
    display: flex; align-items: center; gap: 12px; padding: 10px 12px; border-radius: 10px;
    color: var(--text-muted); text-decoration: none; font-size: 14px; font-weight: 500;
}
.side-link:hover { background: var(--bg-raised); color: var(--text); }
.side-link.active { background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--text); }
.side-link.active .study-icon { color: var(--accent); }
/* Red at all times, not just on hover -- a destructive-actions link reading the same as every other
   sidebar item until you happen to hover it undersold what it actually leads to. Icon needs its own
   override too: .side-link.active .study-icon (above) ties it in specificity and would otherwise win
   on source order, leaving the icon blue while the label text next to it went red. */
.side-link-danger, .side-link-danger:hover, .side-link-danger.active { color: var(--error, #c25b5b); }
.side-link-danger.active { background: color-mix(in srgb, var(--error, #c25b5b) 14%, transparent); }
.side-link-danger .study-icon, .side-link-danger.active .study-icon { color: var(--error, #c25b5b); }
.tab-bar { display: none; }

/* pages that have their own tabs for the same places don't need them twice */
.area-library .lib-tabs { display: none; }
@media (min-width: 821px) { .area-account .tabs-buttons { display: none; } }

@media (max-width: 820px) {
    .hub-shell.has-side { flex-direction: column; gap: 0; }
    .side-nav { display: none; }
    .app-main { width: 100%; }
    /* The tab bar is fixed, so the page leaves room under the content and the footer stays clear of it. */
    .with-side:not(.area-account) .hub-shell { padding-bottom: 84px; }
    .with-side:not(.area-account) .site-footer-inner { padding-bottom: 96px; }
    .area-account .tab-bar { display: none; }
    .tab-bar {
        display: flex; position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
        background: var(--bg-raised); border-top: 1px solid var(--border);
        padding: 6px 4px calc(6px + env(safe-area-inset-bottom));
    }
    .tab-link {
        flex: 1 1 0; display: flex; flex-direction: column; align-items: center; gap: 3px; padding: 6px 2px;
        font-size: 11px; color: var(--text-muted); text-decoration: none; border-radius: 10px; min-width: 0;
    }
    .tab-link span { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .tab-link.active { color: var(--accent); }
}
@media (max-width: 700px) {
    .top-nav-links { margin-left: 0; }
    .top-nav-links .nav-bell, .top-nav-links > .cta { margin-left: 0; }
    .top-nav-links .nav-icon-link { padding: 11px 10px; }
    /* Dashboard/Library already appear in the opened account menu (see _nav_account.html), which also
       carries its own Log out row -- without hiding this the hamburger panel would show it twice. */
    .top-nav-links .nav-icon-link-quick { display: none; }
}
.with-side .site-footer-inner { max-width: 1240px; }

/* the right-hand group of the top bar: theme, bell, account, log out (or Log in and theme when signed out) */
.top-nav-end { display: flex; align-items: center; gap: 4px; margin-left: auto; }
.top-nav-end .nav-bell, .top-nav-links .top-nav-end > .cta { margin-left: 0; }
.top-nav-end > .cta { display: inline-flex; align-items: center; height: 36px; padding: 0 18px; box-sizing: border-box; }
.top-nav-end .theme-toggle-form { margin: 0; }
/* On a phone .top-nav-end still lives inside the collapsible .top-nav-links panel (the bell is the one
   exception -- notifications.js physically moves IT out to sit next to the menu button; see there). Its
   own margin-left: auto is a horizontal top-bar trick that has no business surviving into that panel's
   vertical stack -- confirmed directly as a real bug: without unsetting it here, the cross-axis auto
   margin stops align-items: stretch from applying (per spec) and the account menu ends up shrunk to fit
   and shoved to the right instead of stacking full-width under Blog/Download/Discord like everything
   else in the panel. display: contents removes its own box entirely, so its one remaining visible child
   (the account menu -- Log out is hidden here, the bell is already gone) is stretched exactly like any
   other direct child of .top-nav-links. */
@media (max-width: 700px) { .top-nav-end { display: contents; } }
/* Between a phone and a wide screen (an upright tablet) the links' words don't fit beside the language menu, bell and
   account: icons only, the word in their tooltip. */
@media (min-width: 701px) and (max-width: 1000px) {
    .top-nav-links .nav-icon-link > span { display: none; }
    .top-nav-links .nav-icon-link { padding: 9px 10px; }
}

.field-error { margin: 6px 0 0; color: var(--error); font-size: 13px; line-height: 1.4; }
.has-error { border-color: var(--error) !important; }
form { --field-error-gap: 6px; }

.notify-table .role-badge { margin-left: 10px; }
.hub-shell .notif-button:hover, .nav-bell-panel .notif-button:hover, .hub-shell .notif-icon:hover, .nav-bell-panel .notif-icon:hover { background: none; }
.hub-shell .notif-icon:hover, .nav-bell-panel .notif-icon:hover { color: var(--text); }

.comment { align-items: flex-start; }
.comment-side { flex: 0 0 auto; display: flex; flex-direction: column; align-items: stretch; gap: 8px; min-width: 96px; }
.comment-side form { margin: 0; }
.hub-shell .vote-button, .vote-count { display: inline-flex; align-items: center; justify-content: center; gap: 6px; width: 100%; box-sizing: border-box; height: 34px; margin: 0; padding: 0 12px; border: 1px solid var(--border); border-radius: 8px; background: transparent; color: var(--text-muted); font-size: 13px; font-weight: 600; cursor: pointer; }
.vote-count { cursor: default; }
.hub-shell .vote-button:hover { background: var(--bg); color: var(--text); border-color: var(--accent); }
.hub-shell .vote-button.is-on { background: var(--accent); border-color: var(--accent); color: #fff; }
.hub-shell .comment-delete { display: inline-flex; align-items: center; justify-content: center; gap: 6px; width: 100%; box-sizing: border-box; height: 34px; margin: 0; padding: 0 12px; border: 1px solid color-mix(in srgb, var(--error) 55%, transparent); border-radius: 8px; background: transparent; color: var(--error); font-size: 13px; font-weight: 600; cursor: pointer; }
.hub-shell .comment-delete:hover { background: color-mix(in srgb, var(--error) 14%, transparent); }
.comment-sort { display: flex; gap: 6px; margin: 0 0 12px; font-size: 13px; }
.comment-sort a { padding: 5px 12px; border-radius: 999px; color: var(--text-muted); text-decoration: none; font-weight: 600; }
.comment-sort a.is-on { background: var(--bg-raised); color: var(--text); }

.view-toggle { display: flex; gap: 6px; margin: 0 0 16px; font-size: 13px; }
.view-toggle-btn { padding: 5px 12px; border-radius: 999px; color: var(--text-muted); text-decoration: none; font-weight: 600; }
.view-toggle-btn.is-on { background: var(--bg-raised); color: var(--text); }
@media (max-width: 700px) { .comment { flex-wrap: wrap; } .comment-side { flex-direction: row; width: 100%; min-width: 0; } .comment-side > * { flex: 1 1 0; } }

/* Guide-post layout (dev preview only, see /guide-preview and guide_post_preview.html). */
.guide-article {
    max-width: 760px;
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    gap: 22px;
    padding-bottom: 72px;
}

.guide-back {
    color: var(--text-muted);
    font-size: 13px;
    text-decoration: none;
}

.guide-back:hover {
    color: var(--text);
}

.guide-tag {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--accent-2);
}

.guide-tag.dim {
    color: var(--text-muted);
}

.guide-meta {
    font-size: 13px;
    color: var(--text-muted);
}

.guide-box {
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: 14px;
    padding: 22px 26px;
}

.guide-box-title {
    font-weight: 700;
    font-size: 14px;
    margin-bottom: 10px;
}

.guide-step {
    display: flex;
    gap: 18px;
}

.guide-step-num {
    width: 30px;
    height: 30px;
    border-radius: 9px;
    background: var(--accent-soft);
    color: var(--accent-hover);
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: var(--font-display);
    font-weight: 700;
    font-size: 14px;
    flex-shrink: 0;
}

.guide-step h3 {
    font-size: 18px;
    margin: 0 0 6px;
}

.guide-step p {
    font-size: 15px;
    line-height: 1.65;
    color: var(--text-muted);
    margin: 0;
}

.guide-callout {
    background: var(--accent-soft);
    border: 1px solid var(--accent);
    border-radius: 14px;
    padding: 20px 24px;
}

.guide-callout-title {
    font-weight: 700;
    font-size: 14px;
}

.guide-callout p {
    font-size: 13px;
    color: var(--text-muted);
    margin: 6px 0 0;
}

.guide-related {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 14px;
}

.guide-related-card {
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 16px;
    text-decoration: none;
    color: inherit;
    display: block;
}

.guide-related-card:hover {
    border-color: var(--accent);
}

.guide-related-card .guide-tag {
    display: block;
    margin-bottom: 8px;
}

.guide-related-title {
    font-weight: 700;
    font-size: 14px;
}

.guide-cta-box {
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: 16px;
    padding: 36px;
    text-align: center;
}

@media (max-width: 700px) {
    .guide-related { grid-template-columns: 1fr; }
}

/* Reusable screenshot carousel (see static/carousel.js) -- drops into any page. A slide can be a
   real <img> or, until real screenshots exist, a labeled placeholder panel like the one below. */
.carousel {
    position: relative;
    width: 100%;
    /* Widened (was 380px/4:3) and switched to a wider 16:9-ish ratio -- a small,
       letterboxed image read as cautious/placeholder-ish rather than the bold,
       zoomed-in advertisement feel a hero image should have (direct feedback).
       object-fit: cover below now crops to fill this instead of letterboxing --
       landscape screenshots (OCR, LAT, Study) all fill it convincingly; a tall
       portrait capture (the Dictionary window) does NOT work here at all, cover
       would crop away nearly everything -- that one's pulled from this carousel
       until there's a landscape-friendly shot of it (a wider crop, or the window
       resized before capturing). */
    max-width: 560px;
    aspect-ratio: 16 / 9;
    border-radius: 14px;
    overflow: hidden;
    box-shadow: 0 24px 60px -20px rgba(0, 0, 0, 0.55);
    background: var(--bg-raised);
}

.carousel-slide {
    position: absolute;
    inset: 0;
    opacity: 0;
    transition: opacity 0.4s ease;
}

.carousel-slide.is-on {
    opacity: 1;
}

.carousel-slide img {
    width: 100%;
    height: 100%;
    /* cover, not contain -- see .carousel's own comment above. Every slide here is
       a real landscape screenshot now, so this fills the frame edge to edge. */
    object-fit: cover;
}

/* Opt-in escape hatch for a slide whose content runs edge to edge with no real
   margin (a pure caption/subtitle capture, unlike a screenshot with app chrome
   around the text) -- cover's crop would clip words on both sides instead of
   just trimming empty space. Confirmed directly on the real LAT capture: its
   widest line lost its first and last characters (both languages) under cover. */
.carousel-slide img.fit-contain {
    object-fit: contain;
}

/* The "screenshot coming soon" placeholder needs its own centered layout + inset
   padding now that .carousel-slide itself carries neither (a real <img> wants to
   fill edge to edge, a placeholder's label/note do not). */
.carousel-slide-placeholder {
    width: 100%;
    height: 100%;
    background: var(--bg-input);
    border: 1px dashed var(--border-hi, var(--border));
    display: flex;
    align-items: center;
    justify-content: center;
    flex-direction: column;
    gap: 8px;
    text-align: center;
    padding: 24px;
}

.carousel-slide-label {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: 15px;
}

.carousel-slide-note {
    font-size: 12px;
    color: var(--text-muted);
}


/* Generic small-caps label, reused across the redesigned marketing pages (blog intro, etc.). */
.eyebrow {
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--accent-2);
    margin: 0;
}

.blog-intro {
    display: flex;
    flex-direction: column;
    gap: 12px;
    max-width: 640px;
    margin-bottom: 12px;
}

.blog-intro h1 {
    margin: 0;
}

.blog-search {
    display: flex;
    align-items: center;
    gap: 10px;
    background: var(--bg-input);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 10px 14px;
    color: var(--text-muted);
    margin-top: 8px;
}

.blog-search input {
    flex: 1;
    background: none;
    border: none;
    outline: none;
    color: var(--text);
    font-family: var(--font-body);
    font-size: 14px;
}

@media (min-width: 980px) {
    .blog-grid {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

/* Guide sections: a guide is written with plain H2 headings in the same editor blog posts use --
   these give each one an auto-incrementing numbered badge (pure CSS counter, no per-post typing)
   and, via static/guide.js, an anchor id that "On this page" (below) links to. Reusable on any
   real guide, not specific to the mock. */
.blog-rich-content.guide-sections {
    counter-reset: guide-section;
}

.blog-rich-content.guide-sections h2 {
    counter-increment: guide-section;
    position: relative;
    padding-left: 48px;
    margin-top: 44px;
    scroll-margin-top: 24px;
}

.blog-rich-content.guide-sections h2:first-child {
    margin-top: 0;
}

.blog-rich-content.guide-sections h2::before {
    content: counter(guide-section, decimal-leading-zero);
    position: absolute;
    left: 0;
    top: -2px;
    width: 32px;
    height: 32px;
    border-radius: 9px;
    background: var(--accent-soft, rgba(88, 101, 242, 0.14));
    color: var(--accent-hover);
    font-family: var(--font-display);
    font-size: 13px;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
}

.guide-toc {
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: 14px;
    padding: 18px 22px;
}

.guide-toc-title {
    font-weight: 700;
    font-size: 13px;
    margin-bottom: 10px;
}

.guide-toc ol {
    margin: 0;
    padding-left: 20px;
    display: flex;
    flex-direction: column;
    gap: 6px;
    font-size: 14px;
}

.guide-toc a {
    color: var(--text-muted);
}

.guide-toc a:hover {
    color: var(--accent-hover);
}

.guide-group {
    margin-top: 44px;
}

.guide-group:first-of-type {
    margin-top: 8px;
}

.guide-group-title {
    font-family: var(--font-display);
    font-size: 20px;
    margin: 0 0 18px;
    padding-bottom: 10px;
    border-bottom: 1px solid var(--border);
}

/* Dashboard "cockpit" header + tile grid (about-page redesign carried into the app shell,
   without changing the app's own sidebar navigation). */
.cockpit-header {
    display: flex;
    align-items: center;
    gap: 16px;
}

.cockpit-avatar {
    width: 56px;
    height: 56px;
    border-radius: 14px;
    object-fit: cover;
    flex-shrink: 0;
    border: 1px solid var(--border);
}

.cockpit-identity {
    flex: 1;
    min-width: 0;
}

.cockpit-name {
    display: flex;
    align-items: center;
    gap: 8px;
    font-family: var(--font-display);
    font-size: 20px;
    font-weight: 700;
}

.cockpit-sub {
    font-size: 13px;
    color: var(--text-muted);
    margin-top: 2px;
}

/* A deliberate 3-column grid (was auto-fit/minmax(200px), which packed 4-then-a-lone-5th-tile
   into the old narrower shell) -- confirmed directly as reading badly once a 5th/6th tile joined
   the grid. Fixed at 3 so it wraps predictably (3, then 2, then eventually 3 again as more tiles
   are added) instead of auto-fit silently deciding a different column count as the shell widened
   alongside it. Narrower breakpoints below still reflow to fewer columns for real screen space,
   not for this grid's own aesthetic reasons. */
.cockpit-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 20px;
}

@media (max-width: 980px) {
    .cockpit-grid {
        grid-template-columns: repeat(2, 1fr);
    }
}

@media (max-width: 620px) {
    .cockpit-grid {
        grid-template-columns: 1fr;
    }
}

.cockpit-tile {
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: 14px;
    padding: 24px 26px;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.cockpit-tile-plan {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft, rgba(88, 101, 242, 0.14));
}

.cockpit-tile-label {
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-muted);
}

.cockpit-tile-value {
    font-family: var(--font-display);
    font-size: 26px;
    font-weight: 700;
}

.cockpit-tile-sub {
    font-size: 13px;
    color: var(--text-muted);
}

.cockpit-tile-link {
    font-size: 12px;
    font-weight: 700;
    color: var(--accent-hover);
    text-decoration: none;
    margin-top: auto;
}

.cockpit-tile-link:hover {
    text-decoration: underline;
}

.cockpit-tile form {
    margin-top: auto;
}

.cockpit-tile button {
    width: auto;
    margin-top: 0;
    padding: 6px 14px;
    font-size: 12px;
}

/* Settings > Billing's Plan panel gets the same accent highlight as the dashboard cockpit's
   plan tile -- same visual language, different panel host (a full .panel here, not a small tile). */
.plan-panel-highlight {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft, rgba(88, 101, 242, 0.14));
}

/* Settings > Billing's Plan card: real key/value rows (status, amount, renewal date, billing
   source) instead of one run-on sentence -- requested directly after the plain-text version read
   as dull next to a competitor's own subscription card. */
.plan-details {
    display: flex;
    flex-direction: column;
    margin: 14px 0 18px;
}

.plan-detail-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding: 11px 0;
    border-bottom: 1px solid var(--border);
}

.plan-detail-row:last-child {
    border-bottom: none;
}

.plan-detail-label {
    font-size: 13px;
    color: var(--text-muted);
}

.plan-detail-value {
    font-size: 15px;
    font-weight: 700;
    color: var(--text);
    text-align: right;
}

.plan-detail-unit {
    font-size: 12px;
    font-weight: 500;
    color: var(--text-muted);
}

.plan-status-badge {
    display: inline-flex;
    align-items: center;
    padding: 3px 11px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.plan-status-badge.is-active {
    background: color-mix(in srgb, var(--success) 18%, transparent);
    color: var(--success);
}

.plan-status-badge.is-free {
    background: var(--bg-input);
    color: var(--text-muted);
}

/* AI conversation practice (/converse) -- a chat panel plus an optional past-conversations
   sidebar, only shown when the account has conversation history turned on. */
.converse-layout {
    display: flex;
    gap: 16px;
    align-items: flex-start;
}

.converse-panel {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
}

.converse-sidebar {
    flex: 0 0 240px;
}

.converse-sidebar h2 {
    margin-top: 0;
}

.chat-thread {
    display: flex;
    flex-direction: column;
    gap: 8px;
    min-height: 320px;
    max-height: 520px;
    overflow-y: auto;
    padding: 4px 2px 12px;
    margin-bottom: 12px;
}

.chat-bubble {
    max-width: 80%;
    padding: 10px 14px;
    border-radius: 14px;
    font-size: 14px;
    line-height: 1.5;
    white-space: pre-wrap;
    word-break: break-word;
}

.chat-bubble.user {
    align-self: flex-end;
    background: var(--accent);
    color: #fff;
    border-bottom-right-radius: 4px;
}

.chat-bubble.assistant {
    align-self: flex-start;
    background: var(--bg-input);
    border: 1px solid var(--border);
    color: var(--text);
    border-bottom-left-radius: 4px;
}

.chat-note {
    align-self: flex-start;
    max-width: 80%;
    font-size: 12px;
    color: var(--text-muted);
    padding: 0 4px;
}

.chat-input-row {
    display: flex;
    gap: 8px;
    align-items: flex-end;
}

.chat-input-row textarea {
    flex: 1 1 auto;
    resize: vertical;
    min-height: 44px;
}

.converse-conversation-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.converse-conversation-list li {
    display: flex;
    align-items: center;
    gap: 4px;
}

.converse-conversation-open {
    flex: 1 1 auto;
    text-align: left;
    background: transparent;
    border: none;
    padding: 6px 8px;
    border-radius: 8px;
    color: var(--text);
    font-size: 13px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.converse-conversation-open:hover {
    background: var(--bg-input);
}

.converse-conversation-delete {
    background: transparent;
    border: none;
    color: var(--text-muted);
    padding: 4px 8px;
    font-size: 15px;
    line-height: 1;
}

.converse-conversation-delete:hover {
    color: var(--error);
}

@media (max-width: 720px) {
    .converse-layout {
        flex-direction: column;
    }
    .converse-sidebar {
        flex-basis: auto;
        width: 100%;
    }
}

/* Links with no class of their own inside the app pages. Without this they fell back to the browser's default
   dark blue (and purple once visited), unreadable on the dark theme -- a recurring issue (Dictionary guides, "Look up").
   :where() keeps it at zero specificity, so any component's own link style still wins. */
:where(.hub-shell) a:where(:not([class])),
:where(.hub-shell) a:where(:not([class])):visited { color: var(--gk-accent-text, var(--accent)); }
:where(.hub-shell) a:where(:not([class])):hover { color: var(--text); }

.settings-steps { margin: 6px 0 12px; padding-left: 20px; font-size: 14px; line-height: 1.6; }

/* Settings > Translation > Your own AI */
.own-ai-actions { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.own-ai-actions form { margin: 0; }
.hub-shell .own-ai-actions button { margin: 0; width: auto; }
.own-ai-change { margin-top: 10px; }
.own-ai-change summary { cursor: pointer; color: var(--gk-accent-text, var(--accent)); font-size: 14px; }
.own-ai-model-row { display: flex; gap: 8px; }
.own-ai-model-row input { flex: 1; min-width: 0; margin: 0; }
.hub-shell .own-ai-model-row button { margin: 0; width: auto; white-space: nowrap; }
#own-ai-url-row[hidden] { display: none; }

/* Phones with a notch or a home bar. Pages draw edge to edge (viewport-fit=cover, needed for the installed app),
   so keep the content clear of the notch in landscape and of the home bar at the very bottom. The tab bar
   already leaves room for the home bar itself. The manga reader handles its own edges. */
html:not(:has(> body.reader)) {
    background: var(--bg);
    padding: 0 env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
}
@media (max-width: 820px) {
    .tab-bar { padding-left: max(4px, env(safe-area-inset-left)); padding-right: max(4px, env(safe-area-inset-right)); }
}

/* Uploads (library_jobs.html): every status the desktop app reports, and the row's own actions. */
.job-status.status-queued, .job-status.status-translating, .job-status.status-uploading { color: var(--warn); }
.job-status.status-stopped, .job-status.status-interrupted { color: var(--text-muted); }
.job-row-fill.status-stopped, .job-row-fill.status-interrupted { background: var(--text-muted); }
.job-add { display: flex; gap: 8px; align-items: center; margin: 0; }
.job-add select { width: auto; max-width: 220px; margin: 0; padding: 6px 10px; font-size: 13px; }
.hub-shell .job-row-actions form { margin: 0; }
.hub-shell .job-row-actions button { margin: 0; }

/* ---- Admin: users, an account's page, plans & limits (admin_users.html, admin_user.html, admin_limits.html) */
.admin-filters { display: flex; gap: 8px; flex-wrap: wrap; align-items: stretch; margin: 0 0 16px; }
.admin-filters input[type="text"] { flex: 1 1 260px; margin: 0; }
.admin-filters select { flex: 1 1 150px; width: auto; min-width: 0; margin: 0; }
.admin-filters button, .admin-filters .button-link { margin: 0; display: inline-flex; align-items: center; justify-content: center; }
.admin-table-panel { padding: 8px 0; }
.admin-table-panel .empty-state { padding: 0 28px; }
.admin-table-wrap { overflow-x: auto; }
.admin-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.admin-table th, .admin-table td { text-align: left; padding: 10px 12px; border-bottom: 1px solid var(--border); vertical-align: top; }
.admin-table th { font-size: 12px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.04em; white-space: nowrap; }
.admin-table th a { color: inherit; text-decoration: none; }
.admin-table th a.is-sorted { color: var(--text); }
.admin-table th a.is-sorted::after { content: " ↓"; }
.admin-table tbody tr:last-child td { border-bottom: none; }
.admin-table .num { text-align: right; white-space: nowrap; }
.admin-table-panel .admin-table th:first-child, .admin-table-panel .admin-table td:first-child { padding-left: 28px; }
.admin-table-panel .admin-table th:last-child, .admin-table-panel .admin-table td:last-child { padding-right: 28px; }
.admin-account { color: var(--text); font-weight: 600; text-decoration: none; word-break: break-all; }
.admin-account:hover { color: var(--accent); }
.admin-sub { color: var(--text-muted); font-size: 12.5px; font-weight: 400; }
.admin-sub .role-badge { margin-left: 4px; }
.admin-pages { display: flex; gap: 12px; align-items: center; justify-content: center; padding: 14px 28px 6px; }
.admin-post-tabs { margin-bottom: 14px; }  /* Blog posts / Guides, above the list (admin_announcements.html) */
.admin-fold summary { cursor: pointer; list-style: none; }
.admin-fold summary::-webkit-details-marker { display: none; }
.admin-fold summary h2 { display: inline; margin: 0; }
.admin-fold summary h2::before { content: "+ "; color: var(--text-muted); }
.admin-fold[open] summary h2::before { content: "− "; }
.admin-fold[open] summary { margin-bottom: 12px; }
.admin-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px; margin: 0 0 16px; }
.admin-stat { display: flex; flex-direction: column; gap: 4px; padding: 16px 18px; border-radius: var(--gk-radius, 14px); background: var(--bg-raised); border: 1px solid var(--border); color: var(--text); text-decoration: none; }
.admin-stat:hover { border-color: var(--accent); }
.admin-stat-label { font-size: 12px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.04em; }
.admin-stat-value { font-size: 22px; font-weight: 700; }
.admin-stat-value small { font-size: 14px; font-weight: 500; color: var(--text-muted); }
.admin-tabs { margin-bottom: 16px; scroll-margin-top: 84px; } /* clear of the sticky top bar when the page opens at the tabs */
.tabs-on .admin-section:not(.is-active) { display: none; }
/* The Account tab holds two panels (Account, Delete account) in one wrapper, so the page's own spacing doesn't reach
   between them. */
.admin-section > .panel + .panel { margin-top: 16px; }
.admin-account-page > .field-hint + .admin-section { margin-top: 12px; }
.admin-danger form { margin-top: 14px; }
.admin-danger form button { margin-top: 12px; }
.admin-danger form button:disabled { opacity: 0.45; cursor: not-allowed; }
.panel h3 { font-size: 14px; font-weight: 600; margin: 22px 0 8px; }
.admin-usage-grid .usage-row:first-child { margin-top: 4px; }
.admin-adjust { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin: 16px 0 0; padding: 12px; border-radius: 12px; background: var(--bg-raised); border: 1px solid var(--border); }
.admin-adjust input, .admin-adjust select { margin: 0; width: auto; }
.admin-adjust input[type="number"] { width: 110px; }
.admin-adjust input[name="note"] { flex: 1 1 220px; }
.admin-adjust button { margin: 0; }
.admin-limit-list { display: flex; flex-direction: column; }
.admin-limit-item { padding: 14px 0; border-top: 1px solid var(--border); }
.admin-limit-item:first-child { border-top: none; padding-top: 4px; }
.admin-limit-head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; flex-wrap: wrap; margin-bottom: 2px; }
.admin-limit-now { font-weight: 600; white-space: nowrap; }
.admin-limit-form { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 8px 12px; align-items: end; margin-top: 10px; }
.admin-limit-form label { display: flex; flex-direction: column; gap: 4px; margin: 0; font-size: 12.5px; font-weight: 600; color: var(--text-muted); }
.admin-limit-form input { margin: 0; width: 100%; }
.admin-limit-form .admin-limit-note { grid-column: span 2; }
.admin-limit-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.admin-limit-actions button { margin: 0; }
.admin-plan-cells { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 10px; margin-top: 8px; }
.admin-plan-cell { padding: 10px 12px; border-radius: 12px; background: var(--bg-raised); border: 1px solid var(--border); }
.admin-plan-name { font-size: 12px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 6px; }
.admin-plan-form { display: flex; gap: 6px; align-items: center; margin: 0 0 4px; }
.admin-plan-form input { margin: 0; width: 100%; min-width: 0; }
.admin-plan-form button { margin: 0; padding: 6px 10px; font-size: 13px; }
@media (max-width: 480px) { .admin-limit-form .admin-limit-note { grid-column: auto; } }
.admin-verdict { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 12px; font-weight: 600; white-space: nowrap; }
.admin-verdict.is-ok { background: color-mix(in srgb, #3fb37f 18%, transparent); color: #3fb37f; }
.admin-verdict.is-bad { background: color-mix(in srgb, #e0a030 18%, transparent); color: #e0a030; }
.admin-history { list-style: none; margin: 0; padding: 0; }
.admin-history li { padding: 9px 0; border-top: 1px solid var(--border); font-size: 14px; }
.admin-history li:first-child { border-top: none; }
.admin-history li > span:first-child { color: var(--text-muted); font-size: 12.5px; margin-right: 8px; font-variant-numeric: tabular-nums; }
@media (max-width: 640px) {
    .admin-table-panel .admin-table th:first-child, .admin-table-panel .admin-table td:first-child { padding-left: 16px; }
    .admin-table-panel .admin-table th:last-child, .admin-table-panel .admin-table td:last-child { padding-right: 16px; }
}

/* ---------- Language menu (_language_picker.html): top right, a flag and code, the list under it ---------- */
.lang-menu { position: relative; flex-shrink: 0; }
.lang-menu summary {
    list-style: none; cursor: pointer; display: flex; align-items: center; gap: 7px; height: 36px; box-sizing: border-box;
    padding: 0 10px; border: 1px solid var(--border); border-radius: 12px;
    color: var(--text); font-size: 13px; font-weight: 700; letter-spacing: 0.02em; user-select: none;
}
.lang-menu summary::-webkit-details-marker, .lang-menu summary::marker { display: none; content: ""; }
.lang-menu summary:hover, .lang-menu[open] summary { border-color: var(--accent); }
.lang-flag { width: 20px; height: 14px; border-radius: 3px; object-fit: cover; display: block; flex-shrink: 0;
    box-shadow: 0 0 0 1px rgba(127, 127, 127, 0.25); }
.lang-chevron { color: var(--text-muted); transition: transform 0.15s ease; }
.lang-menu[open] .lang-chevron { transform: rotate(180deg); }
.lang-menu-list {
    position: absolute; top: calc(100% + 8px); right: 0; z-index: 60; margin: 0;
    display: none; flex-direction: column; gap: 2px; min-width: 190px; padding: 6px;
    background: var(--bg-raised); border: 1px solid var(--border); border-radius: 12px;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
}
.lang-menu[open] .lang-menu-list { display: flex; }
.lang-menu-list button {
    display: flex; align-items: center; gap: 12px; width: 100%; margin: 0; padding: 9px 10px;
    border: none; border-radius: 8px; background: transparent; color: var(--text);
    font: inherit; font-size: 14px; font-weight: 500; text-align: left; cursor: pointer; min-height: 0; height: auto;
}
.lang-menu-list button:hover { background: var(--bg-input); }
.lang-menu-list button.current { background: var(--bg-input); color: var(--accent); font-weight: 700; }
.lang-menu-list button span { flex: 1; }
.lang-check { color: var(--accent); flex-shrink: 0; }
/* The bar's two copies: next to Log in / the account menu on a wide screen, next to the menu button on a phone
   (where the links fold away behind it). */
.lang-menu-bar { display: none; margin-left: auto; margin-right: 8px; }
@media (max-width: 700px) {
    .lang-menu-end { display: none; }
    .lang-menu-bar { display: block; }
    /* On a phone just the flag, no outline, at the right with the bell and the menu button. */
    .lang-menu-bar summary { border-color: transparent; padding: 0 8px; }
    .lang-menu-bar .lang-code, .lang-menu-bar .lang-chevron { display: none; }
    .lang-menu-bar .lang-flag { width: 22px; height: 16px; }
    /* The bell moves up here (notifications.js); a second margin-left: auto would split the space and leave the
       flag in the middle of the bar. */
    .lang-menu-bar ~ .nav-bell { margin-left: 0; }
}
/* Sign-in pages have no top bar: the menu sits in the page's top right corner instead. */
.auth-language { position: absolute; top: 18px; right: 24px; z-index: 60; }
.top-nav-end .lang-menu-end { margin-right: 6px; }
.placement-missed { display: flex; flex-direction: column; gap: 6px; }
.placement-missed-row { display: flex; align-items: flex-start; gap: 10px; margin: 0; font-weight: 400; cursor: pointer; }
.placement-missed-row input { margin-top: 4px; width: auto; }
.placement-missed button { align-self: flex-start; margin-top: 8px; }

/* Inside the Android app or the site installed as an app (_pwa_head.html): what's only for a browser on a PC. */
html.in-app .app-hide { display: none !important; }

/* Admin > Chat slang (admin_chat_slang.html): one row per entry, wrapping on a phone. */
.slang-form { display: grid; grid-template-columns: 1.2fr 1fr auto 1.4fr auto; gap: 8px; align-items: end; margin: 0 0 8px; }
.slang-form label { margin: 0; display: flex; flex-direction: column; gap: 4px; font-size: 13px; }
.slang-form input, .slang-form select, .slang-form button { margin: 0; width: 100%; }
.slang-row { padding: 8px 0; border-top: 1px solid var(--border); margin: 0; align-items: center; }
.slang-actions { display: flex; gap: 6px; }
.slang-actions button { width: auto; }
@media (max-width: 800px) {
    .slang-form { grid-template-columns: 1fr 1fr; }
    .slang-form .slang-note, .slang-form > button, .slang-actions,
    .slang-form label:has(select), .slang-form > select { grid-column: 1 / -1; }
}

/* Paying: the website's Stripe buttons, or Google Play's inside the Android app (html.play-billing, _pwa_head.html). */
html.play-billing .web-pay, html.play-missing .web-pay { display: none !important; }
html:not(.play-billing) .play-pay { display: none !important; }
html:not(.play-missing) .play-missing-note { display: none !important; }
html:not(.play-billing):not(.play-missing) .android-only { display: none !important; }
.play-error { color: var(--error); margin-top: 10px; }

/* Settings > Billing's Plan box: its buttons fill the box, two plans side by side when there's room. */
.plan-actions { display: grid; gap: 10px; margin: 6px 0 0; }
.plan-actions-two { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
.plan-actions form { margin: 0; }
.plan-actions button, .plan-actions .button-link { width: 100%; margin: 0; box-sizing: border-box; text-align: center; justify-content: center; }
.plan-actions + .field-hint { margin-top: 10px; }

/* Library > Uploads: adding pages from files */
.upload-pages { margin-bottom: 24px; }
.upload-pages summary { cursor: pointer; }
.upload-pages form { margin-top: 12px; }
.upload-pages input[type="file"] { display: block; margin: 6px 0 12px; }
/* The bell's number pops in; a new notification rings the bell (notifications.js ring()). */
.nav-bell-count { animation: bell-count-in 0.4s cubic-bezier(.3, 1.6, .5, 1) both; }
.nav-bell-count.is-ringing { animation: bell-count-bump 0.5s cubic-bezier(.3, 1.6, .5, 1); }
.nav-bell-button.is-ringing svg { transform-origin: 50% 3px; animation: bell-ring 0.9s ease-in-out; }
@keyframes bell-count-in { from { transform: scale(0); } }
@keyframes bell-count-bump { 40% { transform: scale(1.35); } }
@keyframes bell-ring { 10%, 50% { transform: rotate(14deg); } 30%, 70% { transform: rotate(-12deg); } 85% { transform: rotate(5deg); } }
@media (prefers-reduced-motion: reduce) { .nav-bell-count, .nav-bell-count.is-ringing, .nav-bell-button.is-ringing svg { animation: none; } }
