/*
 * PTM.Social marketing styles — the placetomingle.com apex, server-rendered.
 *
 * The color / spacing / radius / type ramp comes ENTIRELY from tokens.css (the
 * cross-repo single source, copied from PTM.UX at build time). This file only
 * consumes var(--c-*) / var(--sp-*) etc. — it never hard-codes a brand hex, so a
 * token change in PTM.UX flows here with no edit. Light + dark both work: the tokens
 * flip under <html data-theme="dark">.
 *
 * No webfont is loaded — placetomingle.com renders in Arial, matching the SPA. Do not
 * "improve" this or PTM.Social visibly diverges from the signed-in app.
 *
 * Fresh CSS, not extracted from Angular: the SPA's marketing styles live inside
 * component styles:[] arrays with _nghost scoping and are not portable. The visual
 * intent (purple design-token system, marketing-shell chrome, feature rows, plan
 * cards) is reproduced here.
 */

/* ─────────────────────────────── Reset / base ─────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }

/* Which way round the page is, so form controls, scrollbars and the caret
   follow the tokens instead of staying stubbornly light on a dark page. The
   dark RAMP itself is not here: it lives in tokens.css under
   :root[data-theme='dark'], shared verbatim with PTM.UX.

   Stated ONCE. There was a second, identical pair 2,470 lines further down. */
:root, :root[data-theme='light'] { color-scheme: light; }
:root[data-theme='dark'] { color-scheme: dark; }

html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

html, body {
    margin: 0;
    padding: 0;
    max-width: 100%;
    font-family: 'Roboto', 'Arial', sans-serif;
    -webkit-font-smoothing: antialiased;
    line-height: var(--lh-normal);
    color: var(--c-text);
    background: var(--c-surface);
}

body { display: flex; flex-direction: column; min-height: 100vh; }
main { flex: 1 0 auto; }

img, svg, video { max-width: 100%; height: auto; }
a { color: var(--c-accent); }

h1, h2, h3 { line-height: var(--lh-tight); color: var(--c-text); }

:focus-visible { outline: 2px solid var(--c-focus-ring); outline-offset: 2px; border-radius: var(--r-sm); }

/* Keyboard-only skip affordance (WCAG 2.1 A). */
.skip-link {
    position: absolute;
    top: -48px;
    left: 0;
    background: var(--c-accent);
    color: var(--c-accent-ink);
    padding: var(--sp-2) var(--sp-4);
    text-decoration: none;
    border-radius: 0 0 var(--r-sm) 0;
    transition: top .15s;
    z-index: var(--z-sheet);
}
.skip-link:focus { top: 0; outline: 2px solid var(--c-focus-hi); }

/* ─────────────────────────────── Layout ─────────────────────────────── */
.mk-container {
    width: 100%;
    max-width: var(--w-content);
    margin-inline: auto;
    padding-inline: var(--page-pad-x);
}

/* ─────────────────────────────── Buttons ─────────────────────────────── */
.mk-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-2);
    padding: 12px 20px;
    border-radius: var(--r-pill);
    /* font: inherit FIRST, then weight/size re-applied below. .mk-btn sits on <button>
       as well as <a> (the chrome's Sign out, the portal's forms), and the UA stylesheet
       gives buttons their own font-family and line-height: normal — so without this the
       Sign out button pill rendered ~5px shorter, in a different typeface, than the
       "Get started" anchor beside it in the same flex row. */
    font: inherit;
    font-weight: var(--fw-semibold);
    font-size: var(--fs-md);
    text-decoration: none;
    border: 1px solid transparent;
    cursor: pointer;
    transition: background .15s, color .15s, border-color .15s, transform .05s;
    white-space: nowrap;
}
.mk-btn:active { transform: translateY(1px); }

.mk-btn--solid { background: var(--c-primary); color: var(--c-primary-ink); }
.mk-btn--solid:hover { background: var(--c-primary-hover); }

.mk-btn--accent { background: var(--c-accent); color: var(--c-accent-ink); }
.mk-btn--accent:hover { background: var(--c-accent-hover); }

.mk-btn--ghost { background: transparent; color: var(--c-text); border-color: var(--c-line-strong); }
.mk-btn--ghost:hover { background: var(--c-surface-2); border-color: var(--c-text-faint); }

.mk-btn--lg { padding: 15px 28px; font-size: var(--fs-lg); }

/* Buttons that sit ON a purple/dark band. */
.mk-btn--onAccent { background: rgba(255,255,255,.14); color: #fff; border-color: rgba(255,255,255,.5); }
.mk-btn--onAccent:hover { background: rgba(255,255,255,.24); }
/* ⚠ THE INK IS DERIVED SO IT STAYS DARK ON THE WHITE FILL. --c-accent-active INVERTS
   (#4b2a75 light, #d9c2f4 dark), so pairing it with a hard #fff background made this button's
   label near-white-on-white for every dark-mode visitor — and it is the primary CTA on the
   hero and the closing band. color-mix with black lands dark from either end of the ramp. */
.mk-btn--onLight {
    background: #fff;
    color: color-mix(in srgb, var(--c-accent) 30%, #000);
    border-color: #fff;
}
.mk-btn--onLight:hover { background: var(--c-accent-wash); }

/* ─────────────────────────────── Brand mark ─────────────────────────────── */
.mk-brand {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    text-decoration: none;
    color: var(--c-text);
    font-weight: var(--fw-bold);
    font-size: var(--fs-lg);
}
.mk-brand__mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: var(--r-sm);
    background: var(--c-accent);
    color: var(--c-accent-ink);
    font-size: 18px;
    flex: 0 0 auto;
}
.mk-brand__name { letter-spacing: -0.01em; }
.mk-brand--onDark { color: #fff; }

/* ─────────────────────────────── Header / nav ─────────────────────────────── */
.mk-header {
    position: sticky;
    top: 0;
    /* tokens.css ships a z-ladder; three hard-coded numbers were shadowing it. */
    z-index: var(--z-nav);
    background: color-mix(in srgb, var(--c-surface) 92%, transparent);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    border-bottom: 1px solid var(--c-line);
}
.mk-header__inner {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--sp-5);
    min-height: 60px;
}
.mk-nav {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    margin-left: var(--sp-4);
    flex: 1 1 auto;
    min-width: 0;
}
.mk-nav__link {
    padding: 8px 12px;
    border-radius: var(--r-sm);
    color: var(--c-text-muted);
    text-decoration: none;
    font-weight: var(--fw-medium);
    font-size: var(--fs-md);
}
.mk-nav__link {
    /* A nav link is a primary target; 8px of padding on a --fs-md line is ~36px. */
    display: inline-flex;
    align-items: center;
    min-height: 40px;
}
.mk-nav__link:hover { color: var(--c-text); background: var(--c-surface-2); }
/* THREE channels, not one. Colour alone fails anyone who cannot separate this
   purple from this grey, and it is the only thing the header says about where
   you are. */
.mk-nav__link.is-active {
    color: var(--c-accent);
    font-weight: var(--fw-semibold);
    box-shadow: inset 0 -2px 0 var(--c-accent);
}
.mk-menu__link.is-active {
    color: var(--c-accent);
    font-weight: var(--fw-semibold);
    background: var(--c-accent-wash);
}

.mk-cta { display: flex; align-items: center; gap: var(--sp-3); }

/* Mobile disclosure menu (native <details> — no JS). */
.mk-menu { position: relative; display: none; margin-left: auto; }
.mk-menu__toggle {
    list-style: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border-radius: var(--r-sm);
    cursor: pointer;
    font-size: 22px;
    color: var(--c-text);
}
.mk-menu__toggle::-webkit-details-marker { display: none; }
.mk-menu__toggle:hover { background: var(--c-surface-2); }
/* THE BOUNDS ARE ON THE BASE RULE, NOT IN A MEDIA QUERY. They used to sit in
   the <=700px block, which left the whole 701-1080px band - landscape phones,
   split-screen panes, small tablets - with an unbounded absolutely-positioned
   panel inside a position:sticky header. Its content is about 420px tall, so on
   a short viewport "Sign in" and the primary "Get started" CTA fell below the
   fold and could not be scrolled to: genuinely unreachable, not merely awkward. */
.mk-menu__panel {
    position: absolute;
    right: 0;
    top: calc(100% + 8px);
    min-width: 220px;
    max-height: calc(100dvh - 88px);
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    display: flex;
    flex-direction: column;
    gap: var(--sp-1);
    padding: var(--sp-3);
    /* --c-surface-2 rather than --c-surface: in dark theme a --c-surface panel
       over a --c-surface page is one hairline away from invisible. */
    background: var(--c-surface-2);
    border: 1px solid var(--c-line-strong);
    border-radius: var(--r-md);
    box-shadow: var(--shadow-2);
    z-index: calc(var(--z-nav) + 10);
}
/* The glyph says which way the control goes. Without it the only feedback that
   the menu is open is the menu itself, which is fine until it is scrolled. */
.mk-menu[open] > .mk-menu__toggle > .pi::before { content: "\e90b"; }
.mk-menu__link {
    padding: 10px 12px;
    border-radius: var(--r-sm);
    color: var(--c-text);
    text-decoration: none;
    font-weight: var(--fw-medium);
}
.mk-menu__link:hover { background: var(--c-surface-2); }
.mk-menu__cta { margin-top: var(--sp-1); }

/* ─────────────────────────────── Footer ─────────────────────────────── */
/* The footer paints its own dark ground in BOTH themes, so its ink is stated
   once here as a local rather than guessed at eighteen separate call sites.
   That also stops a PTM.UX retune of --c-text from silently changing what the
   footer's text is sitting on without changing the text. */
.mk-footer {
    flex-shrink: 0;
    margin-top: var(--sp-6);
    background: var(--c-text);
    color: var(--mk-footer-ink-2);

    --mk-footer-ink: #fff;
    --mk-footer-ink-1: rgba(255, 255, 255, .82);
    --mk-footer-ink-2: rgba(255, 255, 255, .72);
    --mk-footer-ink-3: rgba(255, 255, 255, .58);
    --mk-footer-rule: rgba(255, 255, 255, .16);
}
:root[data-theme='dark'] .mk-footer { background: var(--c-surface-2); }
.mk-footer__inner {
    display: grid;
    grid-template-columns: 1.4fr 1fr;
    gap: var(--sp-6);
    padding-block: clamp(32px, 6vw, 56px);
}
.mk-footer__tagline {
    margin: var(--sp-3) 0 0;
    max-width: 42ch;
    color: var(--mk-footer-ink-3);
    font-size: var(--fs-md);
}
.mk-footer__nav { display: flex; flex-wrap: wrap; gap: var(--sp-4); align-content: start; }
.mk-footer__nav a, .mk-footer__legal a {
    color: var(--mk-footer-ink-1);
    text-decoration: none;
    font-size: var(--fs-md);
}
.mk-footer__nav a:hover, .mk-footer__legal a:hover { color: var(--mk-footer-ink); text-decoration: underline; }
.mk-footer__legal {
    grid-column: 1 / -1;
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: var(--sp-3);
    margin-top: var(--sp-5);
    padding-top: var(--sp-4);
    border-top: 1px solid var(--mk-footer-rule);
    font-size: var(--fs-sm);
    color: var(--mk-footer-ink-3);
}
/* flex-wrap is on the BASE rule, not inside a media query. The parent
   .mk-footer__legal wraps, but a non-wrapping child cannot be rescued by a
   wrapping parent - at 320px the four items overflow before any breakpoint
   that could have fixed it has a chance to apply. */
.mk-footer__legal-links { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4); }

/* ─────────────────────────────── Section bands ─────────────────────────────── */
.msec { padding-block: clamp(40px, 8vw, 80px); }
.msec--muted { background: var(--c-bg); }
.msec--accent { background: var(--c-accent-wash); }
.msec__head { max-width: 56ch; margin-inline: auto; text-align: center; }
.msec__head--start { margin-inline: 0; text-align: left; }
.msec__eyebrow {
    margin: 0 0 var(--sp-2);
    text-transform: uppercase;
    letter-spacing: .08em;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--c-accent);
}
.msec__heading { margin: 0; font-size: clamp(1.6rem, 3.5vw, 2.3rem); }
.msec__lead {
    margin: var(--sp-4) auto 0;
    max-width: 60ch;
    color: var(--c-text-muted);
    font-size: var(--fs-lg);
}
.msec__head--start .msec__lead { margin-inline: 0; }
.msec__body { margin-top: clamp(28px, 5vw, 48px); }

/* ─────────────────────────────── Hero (home) ─────────────────────────────── */
.mk-hero {
    position: relative;
    overflow: hidden;
    /* ⚠ The far stop was a hard-coded #3a1f63 — the one brand hex left in this file, and the
       reason it mattered is not tidiness: tokens.css is overwritten from PTM.UX on every
       build, so a retuned accent would have left this stop behind and the hero would have
       ended in the OLD purple. It is now derived, so it tracks the ramp. In dark theme it
       also stops being wrong in a second way: the dark accent ramp is light, and a fixed
       near-black stop turned the hero into a light-to-black wash that nothing else on the
       page echoed. */
    background: linear-gradient(135deg,
        var(--mk-hero-a) 0%,
        var(--mk-hero-b) 60%,
        var(--mk-hero-c) 100%);
    color: var(--mk-hero-ink);
    padding-block: clamp(56px, 10vw, 104px);

    /* Hero-local, because the band CANNOT be derived from the accent ramp
       directly. tokens.css inverts that ramp for dark theme by design
       (--c-accent #6b3fa0 -> #b892e6, --c-accent-active #4b2a75 -> #d9c2f4), so
       a gradient built straight from it becomes a PALE band in dark - and the
       white headline, the white lead and the white-on-transparent "Sign in"
       button were then near-invisible on the first screen a dark-mode visitor
       sees. The band has to stay a dark band in both themes; only the route to
       it differs, so the three stops are named here and re-derived below. */
    --mk-hero-a: var(--c-accent);
    --mk-hero-b: var(--c-accent-active);
    --mk-hero-c: color-mix(in srgb, var(--c-accent-active) 72%, #000);
    --mk-hero-ink: #fff;
    --mk-hero-ink-soft: rgba(255, 255, 255, .92);
    --mk-hero-veil: rgba(255, 255, 255, .14);
}

:root[data-theme='dark'] .mk-hero {
    --mk-hero-a: color-mix(in srgb, var(--c-accent) 40%, #000);
    --mk-hero-b: color-mix(in srgb, var(--c-accent-active) 28%, #000);
    --mk-hero-c: color-mix(in srgb, var(--c-accent-active) 16%, #000);
}

/* --c-focus-ring IS the accent, so a focus ring drawn on the hero is the same
   purple the hero is made of - invisible on every control in the band. Same on
   the near-black footer, which holds the site's only theme toggle and the
   footer sign-out. --c-focus-hi exists for exactly this and is identical in
   both ramps; .skip-link already uses it for the same reason. */
.mk-hero :focus-visible,
.mk-footer :focus-visible { outline-color: var(--c-focus-hi); }
.mk-hero__inner { max-width: 46rem; }
.mk-hero__eyebrow {
    display: inline-flex; align-items: center; gap: var(--sp-2);
    padding: 6px 14px;
    border-radius: var(--r-pill);
    background: var(--mk-hero-veil);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    letter-spacing: .02em;
}
.mk-hero h1 {
    margin: var(--sp-4) 0 0;
    color: var(--mk-hero-ink);
    font-size: clamp(2.2rem, 6vw, 3.6rem);
    letter-spacing: -0.02em;
}
.mk-hero__lead {
    margin: var(--sp-4) 0 0;
    max-width: 40rem;
    font-size: clamp(1.05rem, 2.4vw, 1.35rem);
    color: var(--mk-hero-ink-soft);
}
.mk-hero__actions { display: flex; flex-wrap: wrap; gap: var(--sp-3); margin-top: var(--sp-6); }
.mk-hero__note { margin-top: var(--sp-5); font-size: var(--fs-md); color: rgba(255,255,255,.8); }
.mk-hero__note a { color: #fff; font-weight: var(--fw-semibold); }

/* ─────────────────────────────── Card grids (home) ─────────────────────────────── */
.mk-cardgrid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: var(--sp-5);
}
.mk-card {
    background: var(--c-surface);
    border: 1px solid var(--c-line);
    border-radius: var(--r-lg);
    padding: var(--sp-6);
    box-shadow: var(--shadow-1);
}
.mk-card__icon {
    display: inline-flex; align-items: center; justify-content: center;
    width: 56px; height: 56px;
    border-radius: var(--r-md);
    background: var(--c-accent-wash);
    color: var(--c-accent);
    font-size: 26px;
    margin-bottom: var(--sp-4);
}
.mk-card h3 { margin: 0 0 var(--sp-2); font-size: var(--fs-xl); }
.mk-card p { margin: 0; color: var(--c-text-muted); }

/* Numbered "how it works" step cards. */
.mk-steps { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: var(--sp-5); }
.mk-step {
    position: relative;
    background: var(--c-surface);
    border: 1px solid var(--c-line);
    border-radius: var(--r-lg);
    padding: var(--sp-6);
    overflow: hidden;
}
.mk-step__num {
    position: absolute;
    top: 8px; right: 16px;
    font-size: 3.5rem;
    font-weight: var(--fw-bold);
    line-height: 1;
    color: var(--c-accent-wash);
}
.mk-step__icon {
    display: inline-flex; align-items: center; justify-content: center;
    width: 48px; height: 48px;
    border-radius: var(--r-pill);
    background: var(--c-surface);
    border: 2px solid var(--c-accent);
    color: var(--c-accent);
    font-size: 20px;
    margin-bottom: var(--sp-4);
}
.mk-step h3 { margin: 0 0 var(--sp-2); font-size: var(--fs-lg); }
.mk-step p { margin: 0; color: var(--c-text-muted); }

/* ─────────────────────────────── Feature rows ─────────────────────────────── */
.frow {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: clamp(24px, 5vw, 56px);
    align-items: center;
    padding-block: clamp(24px, 4vw, 40px);
}
.frow + .frow { border-top: 1px solid var(--c-line); }
.frow--reverse .frow__media { order: -1; }
.frow__icon {
    display: inline-flex; align-items: center; justify-content: center;
    width: 48px; height: 48px;
    border-radius: var(--r-md);
    background: var(--c-accent-wash);
    color: var(--c-accent);
    font-size: 22px;
    margin-bottom: var(--sp-3);
}
.frow__eyebrow {
    margin: 0 0 var(--sp-1);
    text-transform: uppercase;
    letter-spacing: .07em;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    color: var(--c-accent);
}
.frow__heading { margin: 0 0 var(--sp-3); font-size: clamp(1.3rem, 2.6vw, 1.7rem); }
.frow__copy { margin: 0; color: var(--c-text-muted); font-size: var(--fs-lg); }
.frow__bullets { list-style: none; margin: var(--sp-4) 0 0; padding: 0; display: grid; gap: var(--sp-2); }
.frow__bullets li { display: flex; align-items: flex-start; gap: var(--sp-2); color: var(--c-text); }
.frow__bullets li::before {
    content: "\e909"; /* pi pi-check */
    font-family: 'primeicons';
    color: var(--c-success);
    font-size: var(--fs-sm);
    margin-top: 3px;
    flex: 0 0 auto;
}

/* ─────────────────────────────── Marketing "shot" (mock frames) ───────────────────────────────
   No real product screenshots exist yet (brand-blocked). These are decorative CSS
   device frames with a skeleton interior — a faithful placeholder for the SPA's
   marketing-shot mocks. Real PNGs drop in later behind the same .mshot frame. */
.mshot { display: grid; gap: var(--sp-2); justify-items: center; }
.mshot__frame {
    width: 100%;
    background: var(--c-surface);
    border: 1px solid var(--c-line-strong);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-2);
    overflow: hidden;
}
.mshot--phone .mshot__frame { max-width: 280px; border-radius: 28px; padding: 10px; }
.mshot--browser .mshot__frame { max-width: 520px; }
.mshot__bar {
    display: flex; gap: 6px; align-items: center;
    padding: 10px 14px;
    background: var(--c-surface-2);
    border-bottom: 1px solid var(--c-line);
}
.mshot__bar span { width: 10px; height: 10px; border-radius: 50%; background: var(--c-line-strong); }
.mshot__screen { padding: var(--sp-4); display: grid; gap: var(--sp-3); background: var(--c-bg); }
.mshot--phone .mshot__screen { border-radius: 20px; min-height: 360px; }
.mshot--browser .mshot__screen { min-height: 300px; }
.mshot__avatars { display: flex; gap: var(--sp-2); }
.mshot__avatars i {
    width: 34px; height: 34px; border-radius: 50%;
    background: linear-gradient(135deg, var(--c-accent) 0%, var(--c-accent-active) 100%);
    flex: 0 0 auto; opacity: .85;
}
.mshot__cardline { height: 84px; border-radius: var(--r-md); background: var(--c-surface); border: 1px solid var(--c-line); }
.mshot__line { height: 12px; border-radius: var(--r-pill); background: var(--c-line); }
.mshot__line--short { width: 55%; }
.mshot__line--accent { background: var(--c-accent-wash); }
.mshot__grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px; }
.mshot__grid i { aspect-ratio: 1; border-radius: 5px; background: var(--c-surface); border: 1px solid var(--c-line); }
.mshot__grid i.on { background: var(--c-accent-wash); border-color: var(--c-accent); }
.mshot__caption { color: var(--c-text-muted); font-size: var(--fs-sm); text-align: center; max-width: 34ch; }

/* ─────────────────────────────── Messaging planes table ─────────────────────────────── */
.planes {
    margin-top: var(--sp-5);
    width: 100%;
    border-collapse: collapse;
    background: var(--c-surface);
    border: 1px solid var(--c-line);
    border-radius: var(--r-lg);
    overflow: hidden;
    font-size: var(--fs-md);
}
.planes th, .planes td { padding: 14px 16px; text-align: left; border-bottom: 1px solid var(--c-line); vertical-align: top; }
.planes thead th { background: var(--c-surface-2); font-weight: var(--fw-semibold); }
.planes thead th i { color: var(--c-accent); margin-right: 6px; }
.planes tbody th { font-weight: var(--fw-semibold); color: var(--c-text-muted); width: 22%; }
.planes tr:last-child th, .planes tr:last-child td { border-bottom: 0; }
.planes__note { margin-top: var(--sp-3); color: var(--c-text-muted); font-size: var(--fs-sm); }

/* ─────────────────────────────── Callouts (tutorial) ─────────────────────────────── */
.mk-callouts { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--sp-4); margin-top: var(--sp-5); }
.mk-callout { background: var(--c-surface); border: 1px solid var(--c-line); border-radius: var(--r-md); padding: var(--sp-5); }
.mk-callout i { color: var(--c-accent); font-size: 22px; }
.mk-callout h3 { margin: var(--sp-2) 0; font-size: var(--fs-lg); }
.mk-callout p { margin: 0; color: var(--c-text-muted); }

/* ─────────────────────────────── Tutorial steps ─────────────────────────────── */
.mk-toc {
    background: var(--c-surface);
    border: 1px solid var(--c-line);
    border-radius: var(--r-lg);
    padding: var(--sp-5) var(--sp-6);
    margin-top: var(--sp-5);
    max-width: 44rem;
}
.mk-toc h2 { margin: 0 0 var(--sp-3); font-size: var(--fs-lg); }
.mk-toc ol { margin: 0; padding-left: 1.4em; display: grid; gap: var(--sp-2); }
.mk-toc a { color: var(--c-accent); text-decoration: none; }
.mk-toc a:hover { text-decoration: underline; }

.mk-art { scroll-margin-top: 80px; }
.mk-art__eyebrow { text-transform: uppercase; letter-spacing: .07em; font-size: var(--fs-xs); font-weight: var(--fw-semibold); color: var(--c-accent); }
.steps { list-style: none; counter-reset: s; margin: var(--sp-4) 0 0; padding: 0; display: grid; gap: var(--sp-3); }
.steps li { position: relative; padding-left: 44px; color: var(--c-text); }
.steps li::before {
    counter-increment: s;
    content: counter(s);
    position: absolute; left: 0; top: -2px;
    width: 30px; height: 30px;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: 50%;
    background: var(--c-accent); color: var(--c-accent-ink);
    font-weight: var(--fw-bold); font-size: var(--fs-sm);
}
.mk-tip {
    display: flex; gap: var(--sp-3); align-items: flex-start;
    margin-top: var(--sp-4);
    padding: var(--sp-4);
    border-radius: var(--r-md);
    background: var(--c-accent-wash);
    color: var(--c-text);
    font-size: var(--fs-md);
}
.mk-tip i { color: var(--c-accent); font-size: 20px; margin-top: 2px; flex: 0 0 auto; }

/* ─────────────────────────────── Pricing ─────────────────────────────── */
.trial-band {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3);
    margin-top: var(--sp-5);
    padding: var(--sp-4) var(--sp-5);
    background: var(--c-accent-wash);
    border: 1px solid color-mix(in srgb, var(--c-accent) 25%, transparent);
    border-radius: var(--r-md);
}
.trial-band__pill {
    display: inline-flex; align-items: center; gap: var(--sp-2);
    padding: 6px 14px; border-radius: var(--r-pill);
    background: var(--c-accent); color: var(--c-accent-ink);
    font-weight: var(--fw-semibold); font-size: var(--fs-sm);
    flex: 0 0 auto;
}
.trial-band p { margin: 0; color: var(--c-text); }

.plan-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: var(--sp-5); margin-top: clamp(28px, 5vw, 48px); }
.plan-card {
    position: relative;
    display: flex; flex-direction: column;
    background: var(--c-surface);
    border: 1px solid var(--c-line);
    border-radius: var(--r-lg);
    padding: var(--sp-6);
}
.plan-card--featured { border-color: var(--c-accent); box-shadow: 0 0 0 1px var(--c-accent), var(--shadow-1); }
.plan-card__ribbon {
    position: absolute; top: -12px; left: 50%; transform: translateX(-50%);
    padding: 5px 14px; border-radius: var(--r-pill);
    background: var(--c-accent); color: var(--c-accent-ink);
    font-size: var(--fs-xs); font-weight: var(--fw-semibold);
    text-transform: uppercase; letter-spacing: .05em;
    white-space: nowrap;
}
.plan-card__name { margin: 0; font-size: var(--fs-xl); }
.plan-card__price { display: flex; align-items: baseline; gap: 6px; margin-top: var(--sp-2); }
.plan-card__amount { font-size: 2.2rem; font-weight: var(--fw-bold); letter-spacing: -0.02em; }
.plan-card__interval { color: var(--c-text-muted); font-size: var(--fs-md); }
.plan-card__yearly { margin-top: var(--sp-1); color: var(--c-text-muted); font-size: var(--fs-sm); }
.plan-card__save {
    display: inline-block; margin-top: var(--sp-1);
    padding: 3px 10px; border-radius: var(--r-pill);
    background: var(--c-success-wash); color: var(--c-success);
    font-size: var(--fs-xs); font-weight: var(--fw-semibold);
}
.plan-card__scopenote { margin: var(--sp-3) 0 0; color: var(--c-text-faint); font-size: var(--fs-sm); }
.plan-card__section-title { margin: var(--sp-4) 0 var(--sp-2); font-size: var(--fs-sm); text-transform: uppercase; letter-spacing: .06em; color: var(--c-text-faint); }
.plan-card__limits { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-2); }
.plan-card__limits li { display: flex; justify-content: space-between; gap: var(--sp-3); border-bottom: 1px solid var(--c-line); padding-bottom: var(--sp-2); }
.plan-card__limits .val { font-weight: var(--fw-semibold); }
.plan-card__limits .val--unl { color: var(--c-accent-active); }
.plan-card__features { list-style: none; margin: var(--sp-2) 0 0; padding: 0; display: grid; gap: var(--sp-2); }
.plan-card__features li { display: flex; align-items: flex-start; gap: var(--sp-2); color: var(--c-text); }
.plan-card__features i { color: var(--c-success); margin-top: 3px; }
.plan-card__cta { margin-top: auto; padding-top: var(--sp-5); }
.plan-card__cta .mk-btn { width: 100%; }

.tiers__status { text-align: center; color: var(--c-text-muted); margin-top: var(--sp-6); }
.tiers__foot { margin-top: var(--sp-5); text-align: center; color: var(--c-text-muted); font-size: var(--fs-md); max-width: 60ch; margin-inline: auto; }
.tiers__fallback { text-align: center; margin-top: var(--sp-6); }

/* ─────────────────────────────── FAQ ─────────────────────────────── */
.faq { max-width: 46rem; margin: clamp(28px,5vw,48px) auto 0; display: grid; gap: var(--sp-3); }
.faq details {
    background: var(--c-surface);
    border: 1px solid var(--c-line);
    border-radius: var(--r-md);
    padding: var(--sp-4) var(--sp-5);
}
.faq summary {
    display: flex; justify-content: space-between; align-items: center; gap: var(--sp-3);
    cursor: pointer; list-style: none;
    font-weight: var(--fw-semibold); color: var(--c-text);
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary i { color: var(--c-accent); transition: transform .18s; flex: 0 0 auto; }
.faq details[open] summary i { transform: rotate(180deg); }
.faq p { margin: var(--sp-3) 0 0; color: var(--c-text-muted); }

/* ─────────────────────────────── CTA band ─────────────────────────────── */
.mk-ctaband { text-align: center; }
.mk-ctaband h2 { margin: 0; font-size: clamp(1.6rem, 3.5vw, 2.3rem); }
.mk-ctaband p { margin: var(--sp-4) auto 0; max-width: 50ch; color: var(--c-text-muted); font-size: var(--fs-lg); }
.mk-ctaband__actions { display: flex; flex-wrap: wrap; gap: var(--sp-3); justify-content: center; margin-top: var(--sp-6); }

/* Mobile funnel (Q7, 2026-08-09). Rendered only when a store URL is configured, so these rules
   are inert until an operator supplies one. Deliberately understated: the store badges sit BELOW
   the primary CTA and read as a secondary affordance, because the web sign-up is still the
   conversion path we control end-to-end. */
.mk-ctaband__apps { margin-top: var(--sp-6); }
.mk-ctaband__apps-lead { font-size: .875rem; opacity: .75; margin: 0 0 var(--sp-2); }
.mk-ctaband__badges { display: flex; flex-wrap: wrap; gap: var(--sp-3); justify-content: center; }
.mk-badge {
    display: inline-flex; align-items: center; gap: var(--sp-2);
    padding: var(--sp-2) var(--sp-4);
    border: 1px solid currentColor; border-radius: var(--r-md);
    text-decoration: none; color: inherit; font-size: .9375rem; line-height: 1.2;
}
.mk-badge:hover, .mk-badge:focus-visible { opacity: .85; }
.mk-badge .pi { font-size: 1.25rem; }
.mk-ctaband__legal { margin-top: var(--sp-4); font-size: var(--fs-sm); color: var(--c-text-muted); }
.mk-ctaband__legal a { color: inherit; text-decoration: underline; }

/* ─────────────────────────────── Responsive ─────────────────────────────── */
/* THE CHROME COLLAPSES AT 1080px, NOT 860px. Measured: brand mark + "Place to
   Mingle" at --fs-lg bold + six nav links at --fs-md/500 + two white-space:
   nowrap CTA pills is roughly 1040-1060px of min-content on one line, and
   neither .mk-header__inner nor .mk-nav set flex-wrap. At 861px - every
   tablet-landscape and 1024x768-class laptop - the nav overran the CTA and the
   document gained horizontal scroll. The two belt-and-braces properties below
   mean a longer SiteName or a seventh nav item degrades instead of overflowing. */
@media (max-width: 1080px) {
    .mk-nav, .mk-cta { display: none; }
    .mk-menu { display: block; }
}
@media (max-width: 768px) {
    .frow { grid-template-columns: 1fr; }
    .frow--reverse .frow__media { order: 0; }
    .frow__media { margin-top: var(--sp-4); }
    .mk-footer__inner { grid-template-columns: 1fr; }
}
@media (max-width: 560px) {
    .mk-hero__actions .mk-btn, .mk-ctaband__actions .mk-btn { width: 100%; }
    .planes, .planes thead, .planes tbody, .planes th, .planes td, .planes tr { display: block; }
    .planes thead { display: none; }
    .planes tbody th { width: auto; color: var(--c-accent); }
    .planes td { border-bottom: 0; padding-top: 2px; }
    .planes tr { border-bottom: 1px solid var(--c-line); padding: var(--sp-3) 0; }

    /* THE COLUMN, RE-INJECTED. .pf-table's stacked treatment works because each of its
       cells belongs to exactly one row-label; this table has THREE value cells per row,
       so hiding <thead> left "Best for" followed by three unattributed lines and the
       densest block on /features conveyed nothing at phone width. data-plane names the
       column each value belongs to. */
    .planes td::before {
        content: attr(data-plane) " — ";
        color: var(--c-text-muted);
        font-weight: var(--fw-semibold);
    }
}

/* ─────────────────────────────── Developer portal ───────────────────────────────
 * The one auth-gated surface on this host (/portal). Same tokens, same zero-script
 * discipline as the marketing pages — every control here is a plain form.
 */
.pf-head {
    display: flex; align-items: flex-start; justify-content: space-between;
    gap: var(--sp-4); flex-wrap: wrap;
}
.pf-head__titles { min-width: 0; }
.pf-head .msec__heading { margin-top: var(--sp-1); }

.pf-lead { max-width: 70ch; margin: var(--sp-4) 0 0; color: var(--c-text-muted); font-size: var(--fs-lg); }
/* One step up from .pf-card__title and ruled, so the portal's four <h2>s stop
   rendering at the same size as the card headings nested under them. */
.pf-section-title {
    margin: clamp(28px,5vw,44px) 0 var(--sp-4);
    padding-bottom: var(--sp-2);
    border-bottom: 2px solid var(--c-accent-wash);
    font-size: clamp(1.35rem, 2.4vw, 1.6rem);
}
.pf-footnote { margin-top: var(--sp-4); color: var(--c-text-muted); font-size: var(--fs-sm); max-width: 70ch; }
.pf-empty { color: var(--c-text-muted); }
.pf-muted { color: var(--c-text-muted); }

/* Screen-reader-only, for table-action labels that would be noise on screen. */
.pf-visually-hidden {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* ── Alerts ── */
.pf-alert {
    margin-top: var(--sp-5);
    padding: var(--sp-4) var(--sp-5);
    border: 1px solid var(--c-line);
    border-left-width: 4px;
    border-radius: var(--r-md);
    background: var(--c-surface);
}
.pf-alert p { margin: 0 0 var(--sp-2); }
.pf-alert p:last-child { margin-bottom: 0; }
.pf-alert .mk-btn { margin-top: var(--sp-3); }
/* An info alert used to change only its LEFT BORDER, keeping .pf-alert's
   background: var(--c-surface) - which on a plain .msec is the page's own
   ground. So the most common alert on the host was a white box on white, and
   the only thing marking it was a 4px grey edge. */
.pf-alert--info { border-left-color: var(--c-accent); background: var(--c-surface-2); }
.pf-alert--warn { border-left-color: var(--c-warn); background: var(--c-warn-wash); }
.pf-alert--error { border-left-color: var(--c-danger); background: var(--c-danger-wash); }
/* No .validation-summary-valid guard is needed, and one used to sit here with a comment
   asserting the tag helper "still emits an empty <ul> when the model is valid". MEASURED
   and false: with no model-level errors and no surrounding form context the helper calls
   SuppressOutput, so the <div> is not rendered at all. Pinned both ways by
   PortalViewRenderTests.ACleanPageRendersNoEmptyErrorBox and
   .ModelLevelErrorsAreActuallyRenderedOnThePage. */
.pf-alert--error ul { margin: 0; padding-left: var(--sp-5); }

/* ── The one-time key panel ── */
.pf-newkey {
    margin-top: var(--sp-5);
    padding: var(--sp-5);
    border: 2px solid var(--c-accent);
    border-radius: var(--r-lg);
    background: var(--c-accent-wash);
}
.pf-newkey__title { display: flex; align-items: center; gap: var(--sp-2); margin: 0; font-size: var(--fs-xl); }
.pf-newkey__title i { color: var(--c-accent); }
.pf-newkey__lead { margin: var(--sp-3) 0 0; max-width: 70ch; }
.pf-newkey__value {
    margin: var(--sp-4) 0 0;
    padding: var(--sp-4);
    border: 1px solid var(--c-line-strong);
    border-radius: var(--r-sm);
    background: var(--c-surface);
    overflow-x: auto;
    font-size: var(--fs-sm);
}
/* THE WRAP RULE WAS INERT. .pf-newkey__value is a <pre>, so the UA gives it
   white-space: pre - there are no soft-wrap opportunities at all, which makes
   word-break on the <code> inside do nothing. A ~70-character credential
   therefore scrolled sideways inside a ~290px panel on a phone, on the one
   page that states plainly the value will never be shown again. Scoped to the
   key panel so /reference's curl blocks keep scrolling, which is right for a
   command line. */
.pf-newkey .pf-newkey__value { white-space: pre-wrap; overflow-x: visible; }
.pf-newkey__value code { word-break: break-all; user-select: all; }
.pf-newkey__hint { margin: var(--sp-3) 0 0; color: var(--c-text-muted); font-size: var(--fs-sm); }

/* A worked EXAMPLE payload on /reference (the usage-limit problem body). Scrolls
   sideways rather than wrapping, deliberately and unlike .pf-newkey__value above: JSON
   re-flowed at an arbitrary column stops being copy-pasteable and stops looking like
   JSON, whereas a credential must never scroll out of sight. Different content, opposite
   rule — see the wrap-rule note above for why that one went the other way. */
.pf-code {
    margin: var(--sp-4) 0 0;
    padding: var(--sp-4);
    border: 1px solid var(--c-line-strong);
    border-radius: var(--r-sm);
    background: var(--c-surface);
    overflow-x: auto;
    font-size: var(--fs-sm);
    line-height: 1.5;
}

/* ── Cards / the create form ── */
/* --c-surface-2, not --c-surface: a card painted the same colour as the page
   it sits on is a card only by its 1px border, in both ramps. */
.pf-card {
    margin-top: var(--sp-5);
    padding: var(--sp-5);
    border: 1px solid var(--c-line);
    border-radius: var(--r-lg);
    background: var(--c-surface-2);
    box-shadow: var(--shadow-1);
}
.pf-card__title { margin: 0 0 var(--sp-4); font-size: var(--fs-xl); }
/* A fieldset legend and a single field's label were both .pf-label, so a group
   of four checkboxes announced itself exactly like one more text input. */
.pf-legend {
    display: block;
    margin-bottom: var(--sp-1);
    padding: 0;
    font-size: var(--fs-lg);
    font-weight: var(--fw-semibold);
}
.pf-card__actions { margin-top: var(--sp-5); }

.pf-field { margin-top: var(--sp-5); }
/* ⚠ NOT :first-of-type — .pf-field is on a <div> AND on the scopes <fieldset>, so
   :first-of-type matches the first of EACH, silently zeroing the fieldset's top margin
   too and leaving the form's gaps asymmetric. Anchor on the heading instead: exactly one
   element can follow it. */
.pf-card__title + .pf-field { margin-top: 0; }
.pf-field--narrow { max-width: 22rem; }
.pf-fieldset { border: 0; padding: 0; margin-inline: 0; }
.pf-label { display: block; font-weight: var(--fw-semibold); padding: 0; }
.pf-help { margin: var(--sp-1) 0 var(--sp-3); color: var(--c-text-muted); font-size: var(--fs-sm); max-width: 70ch; }
.pf-input {
    width: 100%;
    margin-top: var(--sp-2);
    padding: 10px var(--sp-3);
    font: inherit;
    color: var(--c-text);
    background: var(--c-surface);
    border: 1px solid var(--c-line-strong);
    border-radius: var(--r-sm);
}
.pf-input--tiny { width: auto; margin-top: 0; padding: 6px var(--sp-3); font-size: var(--fs-sm); min-height: 40px; }
/* ⚠ NO .pf-field__error. The portal renders MODEL-level errors only (the note twenty
   lines up explains why the empty-box guard is unnecessary); there is no per-field
   validation span anywhere on this host, so the rule had nothing to style. */

/* Separator on the ADJACENT SIBLING, not a first-of-type override. This is
   exactly the fragility the comment twelve lines above warns about: .pf-check
   would need :first-of-type to reset a border it should never have had, and
   :first-of-type matches per element TYPE, so one <fieldset> among <div>s
   silently resets a second time. */
.pf-check { display: flex; gap: var(--sp-3); padding: var(--sp-3) 0; }
.pf-check + .pf-check { border-top: 1px solid var(--c-line); }
.pf-check input { margin-top: 4px; flex: 0 0 auto; width: 20px; height: 20px; accent-color: var(--c-accent); }
.pf-check label { display: grid; gap: 2px; cursor: pointer; }
.pf-check__title { font-weight: var(--fw-semibold); }
.pf-check__desc { color: var(--c-text-muted); font-size: var(--fs-sm); }
.pf-check__token { color: var(--c-text-faint); font-size: var(--fs-xs); }

/* ── The key table ── */
.pf-tablewrap { overflow-x: auto; }
.pf-table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
/* display:block, not the UA default table-caption: at the 860px breakpoint
   below, .pf-table itself switches to display:block, and a table-caption
   box whose parent is no longer display:table/inline-table gets wrapped in
   its own anonymous single-child table by the CSS box-fixup algorithm —
   which sizes to shrink-to-fit content instead of the .pf-table width,
   rendering the caption text in an extremely narrow, heavily word-wrapped
   column beside empty space. Forcing block here keeps it an ordinary
   full-width block box at every viewport, table-shaped parent or not. */
.pf-table__caption { display: block; width: 100%; text-align: left; color: var(--c-text-muted); padding-bottom: var(--sp-3); }
.pf-table th, .pf-table td { text-align: left; padding: var(--sp-3); border-bottom: 1px solid var(--c-line); vertical-align: top; }
.pf-table thead th { color: var(--c-text-muted); font-weight: var(--fw-semibold); white-space: nowrap; }
.pf-keyid { word-break: break-all; }
.pf-scopelist { margin: 0; padding: 0; list-style: none; display: grid; gap: 2px; }
.pf-scopelist code { font-size: var(--fs-xs); }

.pf-badge {
    display: inline-block; padding: 2px var(--sp-2);
    border-radius: var(--r-pill); font-size: var(--fs-xs); font-weight: var(--fw-semibold);
    white-space: nowrap;
}
.pf-badge--ok { background: var(--c-success-wash); color: var(--c-success); }
.pf-badge--warn { background: var(--c-warn-wash); color: var(--c-warn); }
.pf-badge--off { background: var(--c-surface-2); color: var(--c-text-muted); }
.pf-badge__note { display: block; margin-top: 2px; font-size: var(--fs-xs); }

.pf-actions { white-space: nowrap; }
.pf-inline-form { display: inline-flex; align-items: center; gap: var(--sp-2); margin-right: var(--sp-2); }
.pf-btn--sm { padding: 6px var(--sp-3); font-size: var(--fs-sm); min-height: 40px; }
.pf-btn--danger { color: var(--c-danger); border-color: var(--c-danger); }
.pf-btn--danger:hover { background: var(--c-danger-wash); border-color: var(--c-danger-hover); }

/* ── Refusal page ── */
.pf-denied { max-width: 60ch; }
.pf-denied__actions { display: flex; flex-wrap: wrap; gap: var(--sp-3); margin-top: var(--sp-6); }
.pf-denied__signout { margin-top: var(--sp-5); }

/* 860, not 720. The host had four unaligned numbers doing one job (980 post
   reflow, 860 listing reflow, 720 portal table, 700 panel/chrome), so between
   721 and 860 a forum listing was stacked while a portal table beside it was
   still a table. Two forum numbers now: 860 = the major reflow, 700 = the phone
   floor. 980 stays, because the post's author column runs out of room before a
   table does. */
@media (max-width: 860px) {
    .pf-table, .pf-table thead, .pf-table tbody, .pf-table th, .pf-table td, .pf-table tr { display: block; }
    .pf-table thead { display: none; }
    .pf-table tr { border-bottom: 1px solid var(--c-line-strong); padding: var(--sp-3) 0; }
    .pf-table td { border-bottom: 0; padding: 2px 0; }
    /* The column header moves into the cell, so a stacked row still says what it is. */
    .pf-table td::before {
        content: attr(data-label) ": ";
        color: var(--c-text-muted);
        font-weight: var(--fw-semibold);
    }
    /* attr() on a MISSING attribute resolves to the empty string, so a <td>
       with no data-label rendered a bare ": value" once the header was hidden.
       /reference's scopes table was exactly that - eight bare cells - and it is
       the table three RFC 7807 refusal middlewares deep-link a developer to.
       The attributes are added now, but the rule degrades to nothing anyway, so
       the next table to forget them is merely unlabelled rather than wrong. */
    .pf-table td::before { content: ""; }
    .pf-table td[data-label]::before { content: attr(data-label) ": "; }

    /* AFTER the generic rule, not before it. Both selectors carry identical
       specificity, so source order is the only thing that decides — and with the
       suppressor first the generic rule won and put "Actions: " back above the
       Rotate/Revoke buttons on every stacked key row. */
    .pf-table td[data-label="Actions"]::before { content: ""; }
    .pf-actions { white-space: normal; padding-top: var(--sp-3); }
    .pf-inline-form { margin-top: var(--sp-2); }
}

/* AI assistant access — the MCP lane's connection panel. */
.pf-assistant p { margin: 0 0 var(--sp-3); max-width: 70ch; }
.pf-assistant p:last-of-type { margin-bottom: var(--sp-4); }
.pf-assistant__state { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }

/* ==========================================================================
   Shared-kit completions
   ==========================================================================
   Every rule below names a class that was ALREADY in the markup and had no
   declaration anywhere — the views were rendering with browser defaults and
   nobody noticed, because a missing CSS class fails silently. The audit that
   produced this list cross-referenced every `class="…"` token under Views/
   against every selector in this file; 100+ forum classes came back unmatched,
   which is the whole of the "the forum looks plain" complaint.

   🔴 THE FOUR THAT WERE NOT MERELY PLAIN BUT WRONG:
     .msec__title / .msec__lede — every forum <h1> and lede used these; the kit
       only ever defined .msec__heading / .msec__lead. So every forum page title
       rendered at the UA default and no forum page had a lede style at all.
     .fm-sr-only — used on <label>s in Drafts, Member and Moderate that are
       meant to be screen-reader-only. With no rule they were VISIBLE, so the
       moderation console printed stray "Name"/"Reason"/"Warning" labels.
     .mk-container--narrow — the FAQ, Compose and Denied pages asked for a
       narrow measure and got the full 1200px column.
     .pf-input--sm / .mk-btn--sm — every "compact" control in the moderation
       console and on the topic page rendered full size, which is why those
       pages read as a wall of full-width inputs.
   ========================================================================== */

/* Section heading pair used by the FORUM views (left-aligned page titles), as
   distinct from .msec__heading/.msec__lead which centre inside .msec__head for
   the marketing bands. */
.msec__title {
    margin: 0 0 var(--sp-2);
    font-size: clamp(1.5rem, 3vw, 2rem);
    font-weight: var(--fw-bold);
    letter-spacing: -0.01em;
}
.msec__lede {
    margin: 0 0 var(--sp-5);
    max-width: 70ch;
    color: var(--c-text-muted);
    font-size: var(--fs-lg);
}

/* A reading measure for prose pages (FAQ entries, compose forms, refusals). */
.mk-container--narrow { max-width: 860px; }

/* Compact control sizes. .mk-btn keeps its pill shape and its font stack; only
   the box shrinks, so a row of small buttons still reads as the same family as
   the page's primary action. */
/* .mk-btn is already inline-flex, so a min-height here is a floor rather than
   a layout change. 7px on a --fs-sm line computes to ~34px, which is under
   every touch guideline - and these are the forum's action rows. */
.mk-btn--sm { padding: 7px 14px; font-size: var(--fs-sm); min-height: 40px; }

/* Screen-reader-only. Same technique as .pf-visually-hidden — kept as a second
   name rather than renamed in eight views, because the two namespaces are
   deliberately independent and a forum view should not have to know the portal
   kit exists to hide a label. */
.fm-sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
    padding: 0;
    margin: -1px;
}

/* Portal-kit completions. */
.pf-input--sm {
    width: auto;
    max-width: 100%;
    margin-top: 0;
    padding: 6px var(--sp-3);
    font-size: var(--fs-sm);
    min-height: 40px;
}
.pf-field--check { display: flex; align-items: flex-start; gap: var(--sp-3); }
.pf-field--check input[type="checkbox"] {
    margin-top: 3px;
    flex: 0 0 auto;
    width: 18px;
    height: 18px;
    accent-color: var(--c-accent);
}
.pf-subsection-title {
    margin: var(--sp-5) 0 var(--sp-3);
    font-size: var(--fs-lg);
    font-weight: var(--fw-semibold);
}
.pf-create { max-width: 46rem; }
/* NO .pf-alert--warning ALIAS. Two views spelled the modifier that way and it had no
   rule at all, so the moderator-warning banner and the "your forum account is still
   being set up" notice both rendered as neutral grey boxes. The fix is the two markup
   sites, not a second selector: an alias leaves both spellings alive in one file and the
   next view picks whichever it happens to read first. ForumStyleCoverageTests now fails
   on the misspelling instead of silently accepting it. */

.mk-footer__brand { min-width: 0; }

/* ==========================================================================
   The application surface — <body data-surface="app">
   ==========================================================================
   The forum, the FAQ, the developer portal and the API reference are
   APPLICATIONS sitting on the marketing host, and they were rendering on the
   same edge-to-edge white page the landing pages use. phpBB's whole sense of
   structure comes from the opposite arrangement: a light ground with the board
   sitting on it as a distinct panel (prosilver's `body` #f5f5f5 vs `.wrap`
   #ffffff). Reproduced here with tokens, so it inverts correctly in dark.

   The attribute is set in _Layout from the request path, so no view has to
   remember it and a new /forum/** page gets it for free.
   ========================================================================== */
body[data-surface='app'] { background: var(--c-bg); }

body[data-surface='app'] > main > .msec { padding-block: clamp(16px, 3vw, 28px); }

body[data-surface='app'] > main > .msec > .mk-container {
    padding-block: clamp(20px, 3vw, 32px);
    background: var(--c-surface);
    border: 1px solid var(--c-line);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-1);
}

/* On a phone the panel goes edge to edge and drops its corners — exactly what
   prosilver's responsive.css does to .wrap/.forabg/.forumbg below 750px. The
   gutter is worth more to the content than the frame is to the composition. */
@media (max-width: 700px) {
    body[data-surface='app'] > main > .msec { padding-block: 0; }
    body[data-surface='app'] > main > .msec > .mk-container {
        border-radius: 0;
        border-inline: 0;
        border-top: 0;
        box-shadow: none;
    }
}

/* ==========================================================================
   fm-*  —  the community forum (/forum) and the FAQ (/faq)
   ==========================================================================
   A THIRD namespace in this file, deliberately, beside mk-* (marketing) and
   pf-* (portal).

   🔴 EVERY RULE BELOW CONSUMES var(--…) ONLY AND HARD-CODES NO BRAND HEX. That
   is what makes dark theme work: the tokens flip under <html data-theme="dark">
   and nothing here needs a second definition.

   🔴 NEVER hand-edit wwwroot/css/tokens.css: an MSBuild target overwrites it
   from PTM.UX/src/styles/tokens.css on every build, with an <Error> guard.

   ⚠ THE PREVIOUS VERSION OF THIS BLOCK REFERENCED FIVE CUSTOM PROPERTIES THAT
     TOKENS.CSS HAS NEVER DEFINED — --c-border, --radius-sm, --radius-md,
     --lh-relaxed and --ff-mono. Four carried a fallback and were merely
     hand-copied values pretending to be tokens; --c-border carried NONE, so
     "border-bottom: 1px solid var(--c-border)" on .fm-member__post was invalid
     at computed-value time and the profile page's post separators simply did
     not exist. All five are gone: colours and radii come from the real ramp,
     and the two non-colour locals are declared once, below, as fm-scoped
     properties so they cannot be mistaken for cross-repo tokens.

   The visual language is phpBB prosilver's, expressed in PTM's palette: a
   light ground with the board as a white panel, accent-filled category bars
   with light-on-dark titles, zebra-striped rows with a hover wash, a folder
   glyph in front of every forum, and posts split into an author column and a
   body panel. Nothing here needs JavaScript — this host renders no <script>
   on any page, so every disclosure is a <details> and every control is a form.
   ========================================================================== */

:root {
    /* Local, non-colour, fm-scoped. NOT design tokens: tokens.css owns the
       cross-repo contract and carries neither of these. Named with an fm-
       prefix so a future grep for hand-copied tokens can tell them apart. */
    --fm-mono: ui-monospace, "Cascadia Mono", "Segoe UI Mono", "Roboto Mono", Menlo, Consolas, monospace;
    --fm-lh-body: 1.65;

    /* The author column on a post. One number, used by the grid and by the
       reflow breakpoint, so the two cannot drift. */
    --fm-profile-w: 190px;
}

/* ── The PRG flash ─────────────────────────────────────────────────────────
   Every forum write redirects to a permalink and leaves its message here. It
   must read as a result, not as chrome, so it gets the success wash rather
   than the neutral info box it used to share with the public-forum notice. */
.fm-flash {
    margin: 0 0 var(--sp-4);
    border-left-color: var(--c-success);
    background: var(--c-success-wash);
}

/* ── Warnings ──────────────────────────────────────────────────────────────
   First thing on every page until acknowledged. Loud on purpose. */
.fm-warnings {
    margin: 0 0 var(--sp-5);
    padding: var(--sp-4) var(--sp-5);
    border: 1px solid var(--c-danger);
    border-left-width: 4px;
    border-radius: var(--r-md);
    background: var(--c-danger-wash);
}
.fm-warnings__lead { margin: 0 0 var(--sp-3); font-weight: var(--fw-semibold); color: var(--c-danger); }
.fm-warnings__list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-3); }
.fm-warnings__item {
    padding: var(--sp-3);
    border-radius: var(--r-sm);
    background: var(--c-surface);
    border: 1px solid var(--c-line);
}
.fm-warnings__text { margin: 0 0 var(--sp-2); }
.fm-warnings__item form { margin-top: var(--sp-2); }

/* ── Breadcrumbs ──────────────────────────────────────────────────────────
   prosilver's navlinks strip: small, quiet, and always the first line of the
   board. */
.fm-crumbs { margin: 0 0 var(--sp-3); }
.fm-crumbs__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2);
    font-size: var(--fs-sm);
}
.fm-crumbs__list li { display: inline-flex; align-items: center; gap: var(--sp-2); min-width: 0; }
/* A chevron, never a slash: a slash reads as a filesystem path. */
.fm-crumbs__list li + li::before {
    content: "\203A";
    color: var(--c-icon-faint);
    font-size: var(--fs-md);
    line-height: 1;
}
.fm-crumbs__list a {
    color: var(--c-text-muted);
    text-decoration: none;
    max-width: 26ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.fm-crumbs__list a:hover { color: var(--c-accent); text-decoration: underline; }
.fm-crumbs__list li:first-child a { color: var(--c-accent); font-weight: var(--fw-medium); }
.fm-crumbs__list li:last-child a { color: var(--c-text); }

/* ── The identity / quick-links strip ─────────────────────────────────────
   prosilver's navbar: who you are on the left, the board-wide quick links on
   the right. It carries EVERY cross-board destination (Unread, My forum,
   Drafts, Search, Active topics, Unanswered, The team, the moderation queue),
   so it is the most-used control on the board and used to render as an
   undifferentiated run of underlined text with no hit area worth the name.

   The links are pills with a 36px-plus target, they wrap onto as many lines as
   they need, and the strip has its own wash so it separates from the content
   below without a heavy rule. */
.fm-identity {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--sp-1) var(--sp-2);
    margin: 0 0 var(--sp-5);
    padding: var(--sp-2) var(--sp-3);
    border: 1px solid var(--c-line);
    border-radius: var(--r-md);
    background: var(--c-surface-2);
    font-size: var(--fs-sm);
    color: var(--c-text-muted);
}
.fm-identity__who {
    display: inline-flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--sp-1);
    min-width: 0;
    padding-inline: var(--sp-1);
}
.fm-identity__who strong { color: var(--c-text); font-weight: var(--fw-semibold); }

/* The quick links. margin-left:auto on the FIRST of them pushes the whole run
   right on a wide screen; the rest sit beside it. */
.fm-identity__action {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    padding: 8px var(--sp-3);
    min-height: 36px;
    border-radius: var(--r-pill);
    color: var(--c-text);
    text-decoration: none;
    font-weight: var(--fw-medium);
    white-space: nowrap;
}
.fm-identity__action:hover { background: var(--c-accent-wash); color: var(--c-accent); }
.fm-identity > .fm-identity__action:first-of-type { margin-left: auto; }
.fm-identity__action--mod { color: var(--c-danger); font-weight: var(--fw-semibold); }
.fm-identity__action--mod:hover { background: var(--c-danger-wash); color: var(--c-danger); }

/* ⚠ THE BELL'S UNREAD BADGE IS `pf-badge fm-badge--new`, NOT `.fm-count`. An earlier draft
   of this block gave .fm-count a crimson pill treatment on the assumption that it WAS the
   badge — and .fm-count is in fact the item-count LINE ("412 posts · page 1 of 7",
   "18 waiting"), declared further down this file. Two components under one name: the second
   declaration won on colour and font-size while the first kept the red fill, so the count
   line rendered as muted grey text inside a danger pill. One component, one class. */
.fm-bell { position: relative; }

/* The handle beside every display name — the anti-impersonation control made
   visible. Muted and monospaced so it reads as an identifier, not a name. */
.fm-handle {
    color: var(--c-text-muted);
    font-family: var(--fm-mono);
    font-size: var(--fs-xs);
    text-decoration: none;
    word-break: break-all;
}
a.fm-handle:hover { color: var(--c-accent); text-decoration: underline; }

.fm-muted { color: var(--c-text-muted); }
.fm-hint { color: var(--c-text-muted); font-size: var(--fs-sm); }

/* ── Badges ────────────────────────────────────────────────────────────────
   .fm-badge had NO base rule: every badge in the forum leaned on .pf-badge for
   its shape and on a modifier for its colour, so the Team marker in the
   identity strip — class="fm-badge fm-badge--staff", carrying no pf-badge —
   rendered as bare text. The base is defined here and the modifiers extend it.
   fm-badge--new and fm-badge--moved had no rule at all under either name. */
.fm-badge,
.fm-badge--staff, .fm-badge--mod, .fm-badge--qa, .fm-badge--announce,
.fm-badge--sticky, .fm-badge--locked, .fm-badge--solved, .fm-badge--pending,
.fm-badge--readonly, .fm-badge--new {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px var(--sp-2);
    border-radius: var(--r-sm);
    background: var(--c-surface-2);
    color: var(--c-text-muted);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    line-height: 1.5;
    letter-spacing: .01em;
    vertical-align: middle;
    white-space: nowrap;
}
.fm-badge--staff   { background: var(--c-accent-wash); color: var(--c-accent); }
.fm-badge--mod     { background: var(--c-accent-wash); color: var(--c-accent); }
.fm-badge--qa      { background: var(--c-cal-personal-wash); color: var(--c-cal-personal-ink); }
.fm-badge--announce{ background: var(--c-warn-wash); color: var(--c-warn); }
.fm-badge--sticky  { background: var(--c-warn-wash); color: var(--c-warn); }
.fm-badge--locked  { background: var(--c-surface-2); color: var(--c-text-muted); }
.fm-badge--readonly{ background: var(--c-surface-2); color: var(--c-text-muted); }
.fm-badge--solved  { background: var(--c-success-wash); color: var(--c-success); }
.fm-badge--pending { background: var(--c-warn-wash); color: var(--c-warn); }
/* "New" is the one badge that must catch the eye — it is the only thing on a
   listing that says "this changed since you last looked". */
.fm-badge--new     { background: var(--c-danger); color: var(--c-accent-ink); }

/* ══════════════════════════════════════════════════════════════════════════
   THE BOARD LISTINGS — categories, forum rows, topic rows
   ══════════════════════════════════════════════════════════════════════════
   prosilver's .forabg/.forumbg: a category is an accent-filled bar with the
   category name in light-on-dark, the column labels immediately under it in a
   quieter wash, then zebra-striped rows with a hover state and a folder glyph
   in front of every forum.

   The listings USED TO BORROW .pf-table, the portal's key table. That was a
   reasonable first move and it is why the forum has data-label attributes
   everywhere — but the portal's mobile treatment stacks EVERY cell with its
   header ("Topic: … / Replies: 3 / Views: 40 / Last post: …"), which turns a
   twenty-topic listing into eighty lines of label-value pairs on a phone. The
   fm-table treatment below keeps the data-labels (they are still the accessible
   name of a stacked cell) but reflows a row the way prosilver does: the title
   block keeps the full width, the numeric columns collapse into one small meta
   line, and the last-post cell becomes a footer line.
   ══════════════════════════════════════════════════════════════════════════ */

/* -- Row and chrome colour roles --------------------------------------------
   prosilver runs the whole board on TWO hues: a blue family for chrome, read
   state and active state, and exactly ONE crimson for unread, hover and error.
   The moment a third appears the page stops reading as phpBB. The map below is
   declared once, from tokens, so no rule after it ever reaches for a colour. */
:root {
    --fm-chrome: var(--c-accent);
    --fm-chrome-ink: var(--c-accent-ink);
    --fm-unread: var(--c-danger);
    --fm-rows: color-mix(in srgb, var(--c-accent) 8%, var(--c-surface));
    --fm-row-alt: color-mix(in srgb, var(--c-accent) 14%, var(--c-surface));
    --fm-row-hover: color-mix(in srgb, var(--c-accent) 22%, var(--c-surface));
}

.fm-category { margin: 0 0 var(--sp-5); }
.fm-category:last-of-type { margin-bottom: 0; }

/* -- The chrome wrapper: prosilver's .forabg / .forumbg ----------------------
   THE MOST-MISREAD DETAIL IN PROSILVER, and the one that decides whether a
   clone reads as phpBB. The blue "Category | Topics | Posts | Latest" bar is
   NOT a background on the header row: `li.header` carries no background rule
   anywhere in prosilver's stylesheets. It is the WRAPPER's fill showing
   through a 5px gutter above a paler rows card:

       .forabg   { background:#4688ce; border-radius:7px; padding:5px }
       ul.forums { background:#d2e4ec; border-radius:4px }
       li.header dt, li.header dd { color:#ffffff }   <- text only, no box

   So: the fill goes on the wrapper, the rows card goes on the <tbody>, and the
   <thead> is TRANSPARENT with light ink. Painting the thead instead produces a
   two-tone bar phpBB never has, which is exactly what the first draft of this
   block did (an accent <h2> sitting above an accent-wash <thead>).

   It doubles as the scroll container. Ten forum tables had none, and a <table>
   cannot scroll itself, so between the desktop layout and the reflow
   breakpoint one long last-post cell could widen the whole document. */
.fm-bg {
    margin-bottom: var(--sp-2);
    padding: 5px;
    border-radius: var(--r-sm);
    background: var(--fm-chrome);
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

.fm-category__title {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    margin: 0;
    padding: 5px 10px 3px;
    color: var(--fm-chrome-ink);
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    letter-spacing: .01em;
}
.fm-category__title a { color: inherit; text-decoration: none; }
.fm-category__title a:hover { text-decoration: underline; }
.fm-category__desc {
    margin: 0;
    padding: 0 10px 6px;
    /* Chrome ink at reduced strength, never --c-text-muted: that token is tuned
       for a surface and would disappear into the accent fill. */
    color: color-mix(in srgb, var(--fm-chrome-ink) 78%, transparent);
    font-size: var(--fs-xs);
}

/* -- The table ------------------------------------------------------------ */
.fm-table {
    width: 100%;
    border-collapse: separate;
    border-spacing: 0;
    font-size: var(--fs-sm);
    background: transparent;
    border: 0;
}

/* prosilver's `li.header dt`: Arial, UPPERCASE, 12px, while everything around
   it is Verdana or Trebuchet. No webfont is loaded on this host by design, so
   the family contrast is bought with CASE AND TRACKING alone, which is most of
   what the eye is actually reading anyway. */
.fm-table thead th {
    padding: 4px var(--sp-3) 6px;
    background: transparent;
    color: var(--fm-chrome-ink);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    letter-spacing: .06em;
    text-transform: uppercase;
    text-align: left;
    white-space: nowrap;
}

/* The rows card. 7px outer radius, 4px inner: prosilver's nested pair. Applied
   to the corner CELLS, because border-radius on a <tbody> is not honoured. */
.fm-table tbody { background: var(--fm-rows); }
.fm-table tbody tr:first-child > td:first-child { border-top-left-radius: 4px; }
.fm-table tbody tr:first-child > td:last-child { border-top-right-radius: 4px; }
.fm-table tbody tr:last-child > td:first-child { border-bottom-left-radius: 4px; }
.fm-table tbody tr:last-child > td:last-child { border-bottom-right-radius: 4px; }

/* An ENGRAVED separator: a light hairline sitting on a saturated one. Copying
   that pair rather than using one neutral rule is a surprising amount of the
   "feel", and it is why the rows card is tinted rather than white: a white top
   border on a white ground separates nothing. */
.fm-table tbody td {
    padding: var(--sp-3);
    border-top: 1px solid var(--c-surface);
    border-bottom: 1px solid color-mix(in srgb, var(--c-accent) 26%, transparent);
    vertical-align: top;
}
.fm-table tbody tr:hover > td { background: var(--fm-row-hover); }

/* ZEBRA STRIPING IS ON TOPIC ROWS ONLY, AND THE ASYMMETRY IS DELIBERATE:
   viewforum_body.html emits bg1|bg2 per topic, forumlist_body.html emits a bare
   <li class="row">. Striping both makes the board index and a forum feel like
   the same page, which is precisely what phpBB avoids. */
.fm-table--zebra tbody tr:nth-child(even) > td { background: var(--fm-row-alt); }
.fm-table--zebra tbody tr:nth-child(even):hover > td { background: var(--fm-row-hover); }

/* prosilver spends its boldness budget on exactly two things, an unread link
   and a last-post subject, and explicitly un-bolds <strong> inside a row to
   protect that. Copying the budget matters more than copying any single rule. */
.fm-table tbody strong { font-weight: var(--fw-medium); }

/* Numeric columns: narrow, centred, quiet. prosilver's dd.topics / dd.posts. */
.fm-table th.fm-col--num,
.fm-table td.fm-col--num {
    width: 5.5rem;
    text-align: center;
    color: var(--c-text-muted);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.fm-table th.fm-col--last,
.fm-table td.fm-col--last { width: 17rem; }
/* The read/unread state column. */
.fm-table th.fm-col--icon,
.fm-table td.fm-col--icon { width: 52px; padding-right: 0; text-align: center; }

/* ── The folder glyph ────────────────────────────────────────────────────
   prosilver's forum_read / forum_unread / forum_locked images, as an icon-font
   glyph in a tinted disc. Decorative — every state it encodes is also carried
   in words by the badges beside the title, so a reader who cannot see it loses
   nothing. */
/* STATE IS CARRIED BY THE FILL, NOT BY THE GLYPH, and getting that backwards
   is the second-commonest way a clone stops looking like phpBB. This build of
   prosilver ships NO folder sprites at all (theme/images holds a logo, an
   avatar placeholder and a plupload folder, nothing else). The marker is a
   36px circular badge with a white glyph, and read-vs-unread is the badge's
   BACKGROUND: blue when read, crimson when not.

     .row-item-link        { background:#0059b3 }   read
     .row-item-link.unread { background:#d41142 }   unread
     .c-forum-row-icon     { color:#ffffff }        both

   The glyph inside says what KIND of row it is, never whether it is read. */
.fm-row__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border-radius: var(--r-pill);
    background: var(--fm-chrome);
    color: var(--fm-chrome-ink);
    font-size: 16px;
    flex: 0 0 auto;
}
.fm-row__icon--unread { background: var(--fm-unread); }
/* A closed forum or a locked topic is chrome-coloured but muted: it is still a
   read state, just an inert one. */
/* ⚠ THE INK MOVES WITH THE FILL. A 45% tint of the accent is a PALE ground, and the base
   rule's --fm-chrome-ink is white in light theme — the lock glyph came out at about 2:1 on
   it. Recolouring a variant means recolouring both halves or neither. */
.fm-row__icon--locked {
    background: color-mix(in srgb, var(--c-accent) 45%, var(--c-surface));
    color: color-mix(in srgb, var(--c-accent) 40%, var(--c-text));
}
.fm-row__icon--announce { background: var(--c-warn); }
.fm-row__icon--solved { background: var(--c-success); }

/* ── Forum rows ─────────────────────────────────────────────────────────── */
.fm-forum__name {
    display: inline-block;
    color: var(--c-accent);
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    text-decoration: none;
}
.fm-forum__name:hover { text-decoration: underline; }
.fm-forum__desc {
    display: block;
    margin-top: 2px;
    color: var(--c-text-muted);
    font-size: var(--fs-sm);
    max-width: 68ch;
}
/* The per-forum "Moderators:" line — phpBB's load_moderators. */
.fm-forum__mods {
    display: block;
    margin-top: var(--sp-1);
    color: var(--c-text-muted);
    font-size: var(--fs-xs);
}
.fm-forum__mods a { color: var(--c-text-muted); }
/* The subforum run under a category forum — phpBB's "Subforums: a, b, c". */
.fm-forum__subs {
    display: block;
    margin-top: var(--sp-1);
    color: var(--c-text-muted);
    font-size: var(--fs-xs);
}
.fm-forum__subs a { color: var(--c-accent); text-decoration: none; }
.fm-forum__subs a:hover { text-decoration: underline; }

/* The last-post cell, on forum rows and topic rows alike. */
.fm-forum__lastby,
.fm-topic__when {
    display: block;
    margin-top: 2px;
    color: var(--c-text-muted);
    font-size: var(--fs-xs);
    line-height: 1.5;
}
.fm-lastpost__title {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 100%;
}

/* ── Topic rows ─────────────────────────────────────────────────────────── */
.fm-topic__title {
    display: inline-block;
    margin-top: 2px;
    color: var(--c-text);
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    text-decoration: none;
}
.fm-topic__title:hover { color: var(--c-accent); text-decoration: underline; }
.fm-topic__by {
    display: block;
    margin-top: 2px;
    color: var(--c-text-muted);
    font-size: var(--fs-xs);
}

/* ── Empty states ───────────────────────────────────────────────────────── */
.fm-empty {
    padding: var(--sp-6) var(--sp-4);
    border: 1px dashed var(--c-line-strong);
    border-radius: var(--r-md);
    background: var(--fm-rows);
    color: var(--c-text-muted);
    text-align: center;
}
.fm-empty p { margin: 0 0 var(--sp-2); }
.fm-empty p:last-child { margin-bottom: 0; }

/* ── The action bar ─────────────────────────────────────────────────────
   prosilver's .action-bar: the primary button on the left, the pagination on
   the right, above AND below the list. Having it in both places is the point —
   on a full page of topics the bottom copy is the one people actually use. */
.fm-actionbar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--sp-3);
    margin: var(--sp-4) 0;
}
.fm-actionbar__spacer { flex: 1 1 auto; }
.fm-actionbar .fm-pager { margin: 0; }

/* ── Pagination ─────────────────────────────────────────────────────────
   No infinite scroll — better for SEO, and the only option with zero JS. */
.fm-pager {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-1);
    margin: var(--sp-5) 0;
    font-size: var(--fs-sm);
}
.fm-pager__page,
.fm-pager__step {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 38px;
    min-height: 38px;
    padding: 0 var(--sp-3);
    border: 1px solid var(--c-line-strong);
    border-radius: var(--r-sm);
    background: var(--c-surface);
    color: var(--c-text);
    text-decoration: none;
    font-weight: var(--fw-medium);
}
.fm-pager__page:hover,
.fm-pager__step:hover { background: var(--c-accent-wash); border-color: var(--c-accent); color: var(--c-accent); }
.fm-pager__page.is-active {
    background: var(--c-accent);
    border-color: var(--c-accent);
    color: var(--c-accent-ink);
    font-weight: var(--fw-semibold);
}
.fm-pager__gap { padding: 0 var(--sp-1); color: var(--c-text-muted); }
/* ⚠ --c-text-muted, not --c-text-faint, on anything sitting on --fm-rows or --fm-row-alt.
   The faint token's own definition promises >=4.5:1 ON WHITE; the tinted grounds this pass
   introduced are darker than white, so it lands under AA there. The muted token has the
   headroom. */
.fm-minipager__gap, .fm-post__perma { color: var(--c-text-muted); }

/* ── Board statistics ───────────────────────────────────────────────────
   Muted and small: context, not content — a visitor should read the forum list
   first and the size of the place second. */
.fm-board-stats {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2);
    margin-top: var(--sp-5);
    padding: var(--sp-3) var(--sp-4);
    border: 1px solid var(--c-line);
    border-radius: var(--r-md);
    background: var(--c-surface-2);
    color: var(--c-text-muted);
    font-size: var(--fs-sm);
}
.fm-board-stats strong { color: var(--c-text); font-weight: var(--fw-semibold); }

/* ── Row-level call-to-action clusters ─────────────────────────────────── */
.fm-cta-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-3);
    margin-top: var(--sp-4);
}
.fm-follow-row { margin-top: var(--sp-5); padding-top: var(--sp-4); border-top: 1px solid var(--c-line); }
.fm-forum__actions { margin: var(--sp-4) 0; }
.fm-inline-form, .fm-form--inline, .fm-inline { display: inline-flex; align-items: center; gap: var(--sp-2); margin: 0; }
.fm-inline__check { display: inline-flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-sm); }
.fm-signin-cta { margin: var(--sp-5) 0; }
.fm-public-notice { margin-bottom: var(--sp-5); }
.fm-locked-note { margin-top: var(--sp-5); }
.fm-mark-read { margin: var(--sp-4) 0; }
/* .fm-forum__search is gone with the link it styled — "Search this forum" is a real inline
   form now (it borrows .fm-topic__search, which is the same control in the same shape). */
.fm-member__more { margin: 0 0 var(--sp-4); font-size: var(--fs-sm); }

/* ══════════════════════════════════════════════════════════════════════════
   THE TOPIC PAGE AND THE POST BLOCK
   ══════════════════════════════════════════════════════════════════════════
   prosilver's .post: a poster-profile column beside a body panel, the two
   sharing one framed block with a subject bar across the top and a button row
   along the bottom.

   🔴 The profile column is FIRST IN THE DOM and placed into the second grid
      column, exactly as prosilver's .postprofile is first in the markup and
      floated right. That ordering is what makes the mobile reflow free: drop
      the grid and the profile becomes the horizontal strip ABOVE the body,
      which is precisely what phpBB's responsive.css produces at 750px. Placing
      it second in the DOM and pulling it up with `order` would give the same
      picture and the wrong reading order for a screen reader.

   ⚠ The post body is the ONE element user-authored HTML is rendered into. The
      typography below therefore has to cover everything the Markdown renderer
      can emit — headings, lists, quotes, code, tables, images — because until
      now it covered NONE of it and a quoted reply rendered as an unstyled
      browser-default <blockquote>: an indent, no rule, no ground, nothing to
      say where the quote stopped and the reply began.
   ══════════════════════════════════════════════════════════════════════════ */

.fm-topic__meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2);
    margin: 0 0 var(--sp-4);
    color: var(--c-text-muted);
    font-size: var(--fs-sm);
}
.fm-topic__forum { font-weight: var(--fw-medium); }

/* "Search this topic" — a GET form, so the result page stays a shareable URL. */
.fm-topic__search {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2);
    margin: 0 0 var(--sp-5);
}
.fm-search__input {
    flex: 1 1 14rem;
    min-width: 0;
    max-width: 26rem;
    padding: 9px var(--sp-3);
    font: inherit;
    font-size: var(--fs-sm);
    color: var(--c-text);
    background: var(--c-surface);
    border: 1px solid var(--c-line-strong);
    border-radius: var(--r-sm);
}
.fm-search__scope { margin: calc(var(--sp-2) * -1) 0 var(--sp-4); color: var(--c-text-muted); font-size: var(--fs-sm); }

/* ── The post block ─────────────────────────────────────────────────────── */
/* prosilver's .post: 7px radius, 5px/10px padding, 4px between posts,
   alternating bg1/bg2, and NEITHER A BORDER NOR A SHADOW. The alternation is
   what separates one post from the next; adding a frame on top of it makes
   every post read as a card, which is the modern-forum look, not this one. */
.fm-post {
    margin: 0 0 4px;
    border: 0;
    border-radius: var(--r-sm);
    background: var(--fm-rows);
    overflow: hidden;
}
.fm-post:nth-of-type(even) { background: var(--fm-row-alt); }
/* :target — arriving on a permalink must show you WHICH post you arrived at.
   prosilver does the same with .post:target. */
.fm-post:target { box-shadow: 0 0 0 2px var(--c-accent); }

/* Under a sticky 60px header an untreated fragment target lands behind the
   chrome. One rule covers every anchor destination on the host: the three
   /reference sections the RFC 7807 refusal middlewares build their type URIs
   against, /faq#{slug}, the #p{id} the post-permalink redirect appends, and the
   /#how the header, the mobile menu and the footer all emit on every page. */
:target { scroll-margin-top: 76px; }

.fm-post__inner {
    display: grid;
    grid-template-columns: minmax(0, 1fr) var(--fm-profile-w);
    align-items: stretch;
}

/* The author column. Second grid column (prosilver floats .postprofile RIGHT
   and .postbody left - bidi.css confirms it by flipping both for RTL), first in
   the DOM so the mobile reflow needs no reordering.

   ITS ONLY CHROME IS A 1px LEFT BORDER IN THE ROW'S OWN BACKGROUND COLOUR - a
   carved groove, not a panel edge. DO NOT give it a fill: a filled sidebar
   beside an unfilled post is the commonest single tell that a board is a phpBB
   imitation rather than phpBB. */
.fm-post__profile {
    grid-column: 2;
    grid-row: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-1);
    padding: var(--sp-4) var(--sp-3);
    border-left: 1px solid var(--c-surface);
    background: none;
    text-align: center;
    min-width: 0;
}
.fm-post__main {
    grid-column: 1;
    grid-row: 1;
    display: flex;
    flex-direction: column;
    min-width: 0;
    padding: var(--sp-4);
}

/* The initials disc. phpBB's .avatar-placeholder, and the same answer to the
   same problem: this board stores no avatar, and a row of identical generic
   silhouettes is worse than nothing. Initials at least distinguish people. */
.fm-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 56px;
    height: 56px;
    border-radius: var(--r-pill);
    background: var(--c-accent);
    color: var(--c-accent-ink);
    font-size: 1.25rem;
    font-weight: var(--fw-bold);
    letter-spacing: .02em;
    line-height: 1;
    flex: 0 0 auto;
    user-select: none;
}
.fm-avatar--erased { background: var(--c-surface); color: var(--c-text-faint); border: 1px dashed var(--c-line-strong); }

.fm-post__author {
    display: block;
    max-width: 100%;
    color: var(--c-accent);
    font-weight: var(--fw-semibold);
    text-decoration: none;
    overflow-wrap: anywhere;
}
.fm-post__author:hover { text-decoration: underline; }

/* Posts / Joined. prosilver's postprofile dd pair, and the same two facts —
   deliberately NOT last-seen or last-posted, which are presence signals this
   product refuses to publish (it also runs dating). */
.fm-post__stats {
    display: grid;
    grid-template-columns: auto auto;
    justify-content: center;
    gap: 2px var(--sp-2);
    margin: var(--sp-2) 0 0;
    font-size: var(--fs-xs);
    color: var(--c-text-muted);
}
.fm-post__stats dt { font-weight: var(--fw-medium); }
.fm-post__stats dd { margin: 0; color: var(--c-text); }

/* The post's meta line. NOT a bar: prosilver's post subject is a bare <h3>
   with a 180px right gutter for the button row, and its author line is a plain
   <p class="author">. A tinted or ruled header strip is an instant tell, so
   this has neither - only spacing separates it from the body. */
.fm-post__head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--sp-2) var(--sp-3);
    margin: 0 0 var(--sp-3);
    color: var(--c-text-muted);
    font-size: var(--fs-xs);
}
.fm-post__subject {
    margin: 0;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--c-text);
    min-width: 0;
    overflow-wrap: anywhere;
}
.fm-post__when { color: var(--c-text-muted); font-size: var(--fs-xs); }
.fm-post__perma {
    display: inline-flex;
    align-items: center;
    min-height: 32px;
    margin-left: auto;
    padding: 2px var(--sp-2);
    border-radius: var(--r-sm);
    color: var(--c-text-faint);
    font-family: var(--fm-mono);
    font-size: var(--fs-xs);
    text-decoration: none;
}
.fm-post__perma:hover { background: var(--c-accent-wash); color: var(--c-accent); }

/* ── The rendered body ───────────────────────────────────────────────────
   Everything the Markdown pipeline can emit. Nothing here was styled before,
   so a post with a heading, a list and a quote rendered as three different
   browser defaults sitting on top of each other. */
.fm-post__body {
    line-height: var(--fm-lh-body);
    overflow-wrap: anywhere;
}
.fm-post__body > :first-child { margin-top: 0; }
.fm-post__body > :last-child { margin-bottom: 0; }
.fm-post__body p { margin: 0 0 var(--sp-3); }
.fm-post__body h1,
.fm-post__body h2,
.fm-post__body h3,
.fm-post__body h4 {
    margin: var(--sp-5) 0 var(--sp-2);
    line-height: var(--lh-tight);
    font-weight: var(--fw-semibold);
}
.fm-post__body h1 { font-size: 1.35rem; }
.fm-post__body h2 { font-size: 1.2rem; }
.fm-post__body h3 { font-size: 1.05rem; }
.fm-post__body h4 { font-size: var(--fs-md); }
.fm-post__body ul,
.fm-post__body ol { margin: 0 0 var(--sp-3); padding-left: var(--sp-6); }
.fm-post__body li { margin-bottom: var(--sp-1); }
.fm-post__body a { color: var(--c-accent); overflow-wrap: anywhere; }
.fm-post__body hr { margin: var(--sp-5) 0; border: 0; border-top: 1px solid var(--c-line); }
.fm-post__body img { max-width: 100%; height: auto; border-radius: var(--r-sm); }

/* A quote. phpBB's blockquote is the single most recognisable thing inside a
   forum post, and this one had no styling at all. */
.fm-post__body blockquote {
    margin: 0 0 var(--sp-3);
    padding: var(--sp-3) var(--sp-4);
    border-left: 3px solid var(--c-accent);
    border-radius: 0 var(--r-sm) var(--r-sm) 0;
    background: var(--c-surface-2);
    color: var(--c-text-muted);
    font-size: var(--fs-sm);
}
.fm-post__body blockquote > :last-child { margin-bottom: 0; }
.fm-post__body blockquote blockquote { background: var(--c-surface); }

/* Code. prosilver's .codebox. `pre` scrolls on its own so a long line cannot
   widen the page — the single most common cause of horizontal overflow on a
   phone. */
.fm-post__body code {
    padding: 1px 5px;
    border-radius: 4px;
    background: var(--c-surface-2);
    font-family: var(--fm-mono);
    font-size: 0.9em;
}
.fm-post__body pre {
    margin: 0 0 var(--sp-3);
    padding: var(--sp-3);
    border: 1px solid var(--c-line);
    border-radius: var(--r-sm);
    background: var(--c-surface-2);
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}
.fm-post__body pre code { padding: 0; background: none; font-size: var(--fs-sm); }

/* A table inside a post scrolls inside itself, never widening the page. */
.fm-post__body table {
    display: block;
    width: max-content;
    max-width: 100%;
    margin: 0 0 var(--sp-3);
    overflow-x: auto;
    border-collapse: collapse;
    font-size: var(--fs-sm);
}
.fm-post__body th,
.fm-post__body td { padding: 6px var(--sp-3); border: 1px solid var(--c-line); text-align: left; }
.fm-post__body thead th { background: var(--c-surface-2); }

/* The never-rendered fallback: a seeded row the sanitizer sweep has not reached. Razor
   escapes it and <pre> keeps the author's line breaks.

   ⚠ QUALIFIED WITH ITS ANCESTOR. `.fm-post__source` alone is outranked by
   `.fm-post__body pre` twenty lines up, so this element — whose whole purpose is to render as
   plain prose rather than as a code block — got the codebox border, tint and padding anyway. */
.fm-post__body pre.fm-post__source {
    margin: 0;
    padding: 0;
    border: 0;
    background: none;
    font-family: inherit;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

/* The blocked-author collapse (the platform block bridge): a native <details>,
   the same zero-JS reveal as the nav menus and the FAQ. The summary reads as a
   quiet notice, not an alert — the member chose this state themselves. */
.fm-post__blocked {
    padding: var(--sp-3);
    border: 1px dashed var(--c-line-strong);
    border-radius: var(--r-sm);
    background: var(--c-surface-2);
}
.fm-post__blocked-note {
    cursor: pointer;
    color: var(--c-text-muted);
    font-size: var(--fs-sm);
    font-style: italic;
    min-height: 44px;
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    /* Every other <details> on this host hides the native triangle and supplies
       its own affordance; this one did not, so the board had two different
       disclosure widgets. */
    list-style: none;
}
.fm-post__blocked-note::-webkit-details-marker { display: none; }
/* \e902 is pi-chevron-DOWN and \e903 is pi-chevron-UP — verified against
   primeicons.css, because an earlier draft had them the wrong way round and a
   disclosure whose arrow points up while it is closed is worse than no arrow. */
.fm-post__blocked-note::before {
    content: "\e902";
    font-family: "primeicons";
    font-style: normal;
    color: var(--c-icon-faint);
}
.fm-post__blocked[open] > .fm-post__blocked-note::before { content: "\e903"; }
.fm-post__blocked[open] > .fm-post__blocked-note { margin-bottom: var(--sp-3); }

/* Attachments — every one a LINK, never an <img src>. */
.fm-post__attachments {
    list-style: none;
    margin: var(--sp-4) 0 0;
    padding: var(--sp-3);
    border: 1px solid var(--c-line);
    border-radius: var(--r-sm);
    background: var(--c-surface-2);
    font-size: var(--fs-sm);
}
.fm-post__attachments li { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-2); }
.fm-post__attachments li + li { margin-top: var(--sp-2); }
.fm-post__attachments a { overflow-wrap: anywhere; }

/* The footer notice line: edited-by, edit reason, moderation state. */
.fm-post__foot {
    margin-top: var(--sp-3);
    padding-top: var(--sp-2);
    border-top: 1px dashed var(--c-line);
    color: var(--c-text-muted);
    font-size: var(--fs-xs);
}
.fm-edit-reason { font-style: italic; }

/* The post's button row — phpBB's .post-buttons, bottom-right of the panel. */
.fm-post__actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: var(--sp-2);
    margin-top: var(--sp-4);
    padding-top: var(--sp-3);
    border-top: 1px solid color-mix(in srgb, var(--c-accent) 20%, transparent);
}
.fm-post__actions form { margin: 0; }

.fm-reply-row { margin: var(--sp-5) 0; }

/* ── The poll panel ─────────────────────────────────────────────────────
   Bars are native <progress>: the forum CSP is style-src 'self', so per-option
   inline widths are off the table, and <progress> is the semantic element
   anyway. */
.fm-poll {
    margin: 0 0 var(--sp-5);
    border: 1px solid var(--c-line);
    border-radius: var(--r-md);
    background: var(--c-surface);
    overflow: hidden;
}
.fm-poll__title {
    margin: 0;
    padding: 10px var(--sp-4);
    background: var(--c-accent-wash);
    border-bottom: 1px solid var(--c-line);
    color: var(--c-accent);
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
}
.fm-poll__ballot, .fm-poll__results, .fm-poll__foot { padding: var(--sp-4); margin: 0; }
.fm-poll__foot { padding-top: 0; color: var(--c-text-muted); font-size: var(--fs-sm); }
.fm-poll__option {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-2) var(--sp-3);
    border: 1px solid transparent;
    border-radius: var(--r-sm);
    cursor: pointer;
}
.fm-poll__option:hover { background: var(--c-surface-2); border-color: var(--c-line); }
.fm-poll__option input { width: 18px; height: 18px; accent-color: var(--c-accent); flex: 0 0 auto; }
.fm-poll__option-label { margin-top: var(--sp-3); font-weight: var(--fw-semibold); }
.fm-poll__option-label:first-child { margin-top: 0; }
.fm-poll__tally {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--sp-2) var(--sp-3);
    margin: var(--sp-1) 0 0;
}
.fm-poll__tally progress {
    flex: 1 1 12rem;
    max-width: 22rem;
    height: 10px;
    border: 0;
    border-radius: var(--r-pill);
    background: var(--c-surface-2);
    accent-color: var(--c-accent);
    overflow: hidden;
}
.fm-poll__tally progress::-webkit-progress-bar { background: var(--c-surface-2); border-radius: var(--r-pill); }
.fm-poll__tally progress::-webkit-progress-value { background: var(--c-accent); border-radius: var(--r-pill); }
.fm-poll__tally progress::-moz-progress-bar { background: var(--c-accent); border-radius: var(--r-pill); }
.fm-poll__actions { margin-top: var(--sp-3); }

/* ══════════════════════════════════════════════════════════════════════════
   FORMS — compose, reply, report, split, search, preferences
   ══════════════════════════════════════════════════════════════════════════
   Every control on this board is a plain form: there is no <script> on any
   page. That makes the form styling load-bearing rather than decorative — the
   compose page IS the editor, and it was rendering as bare UA controls.
   ══════════════════════════════════════════════════════════════════════════ */

/* ⚠ THERE IS NO .fm-input / .fm-label / .fm-field / .fm-select, DELIBERATELY. Every forum
   form wears the PORTAL kit — pf-input, pf-label, pf-field — and an fm-* twin of each was
   written here speculatively and used by nothing. Two kits for one control is how a form
   ends up half-styled by each. .fm-textarea survives because it genuinely differs: a
   Markdown composer wants a monospace measure. */
.fm-form { margin: 0; }
.fm-form fieldset { border: 0; margin: 0; padding: 0; }
.fm-textarea:focus { border-color: var(--c-accent); }
.fm-textarea {
    /* ⚠ NO min-height. An earlier draft set 16rem, which is right for the compose body and
       wrong for the two other textareas wearing this class: the poll's options box is
       rows="4" and the quick-reply box is rows="6", and both were forced to ~256px — a
       "quick" reply that opens a full-page editor is not a quick reply. `rows` already
       states the intended height per element, which is where that decision belongs. */
    line-height: var(--fm-lh-body);
    resize: vertical;
    /* A composer is where a monospace measure genuinely helps: the body is
       Markdown, and alignment of lists, tables and fenced code is the thing
       the author is trying to see. */
    font-family: var(--fm-mono);
    font-size: var(--fs-sm);
}

/* The Markdown / plain-text format switch, and the report form's reason list.

   ⚠ .fm-radio IS THE <label>, not a container around one. An earlier draft styled
     `.fm-radio label`, which matches nothing — the markup is
     `<label class="fm-radio"><input …> Text <span class="fm-hint">…</span></label>` in both
     places that use it. Reading the markup is the only way to get this right; the class
     name reads like a group and is a single option.

   A selectable ROW rather than a pill: half these options carry a one-line explanation
   underneath (Compose's two formats do), and a pill cannot hold a second line. */
.fm-format { display: grid; gap: var(--sp-2); margin-top: var(--sp-2); }

.fm-radio {
    display: grid;
    grid-template-columns: auto 1fr;
    align-items: baseline;
    gap: 2px var(--sp-3);
    padding: var(--sp-3);
    border: 1px solid var(--c-line);
    border-radius: var(--r-sm);
    background: var(--c-surface);
    font-size: var(--fs-sm);
    cursor: pointer;
}
.fm-radio + .fm-radio { margin-top: var(--sp-2); }
.fm-radio:hover { background: var(--c-accent-wash); border-color: var(--c-accent); }
/* :has() is the zero-JS way to show which option is chosen. Where it is unsupported the
   row simply stays unhighlighted — the native radio dot still says which one is set, so
   nothing is lost, which is what makes this safe to use without a fallback. */
.fm-radio:has(input:checked) { border-color: var(--c-accent); background: var(--c-accent-wash); }
.fm-radio input {
    grid-row: 1;
    width: 18px;
    height: 18px;
    margin: 0;
    accent-color: var(--c-accent);
}
/* The explanation drops under the label text, aligned with it rather than with the dot. */
.fm-radio .fm-hint { grid-column: 2; font-size: var(--fs-xs); }

/* Compose: where this post is going, above the form. */
.fm-compose__where {
    margin: 0 0 var(--sp-4);
    padding: var(--sp-3) var(--sp-4);
    border-left: 3px solid var(--c-accent);
    border-radius: 0 var(--r-sm) var(--r-sm) 0;
    background: var(--c-accent-wash);
    color: var(--c-text);
    font-size: var(--fs-sm);
}

/* A horizontal rule between form regions. */
.fm-rule { margin: var(--sp-5) 0; border: 0; border-top: 1px solid var(--c-line); }

/* The poll builder. */
.fm-poll-compose {
    margin-top: var(--sp-5);
    padding: var(--sp-4);
    border: 1px solid var(--c-line);
    border-radius: var(--r-md);
    background: var(--c-surface-2);
}
.fm-poll-compose__knobs {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-4);
    margin-top: var(--sp-3);
}
.fm-poll-compose__knobs label { display: inline-flex; flex-direction: column; gap: var(--sp-1); font-size: var(--fs-sm); }

/* Attachments on the composer. */
.fm-attach { margin-top: var(--sp-5); }
.fm-attach__list { list-style: none; margin: var(--sp-3) 0 0; padding: 0; display: grid; gap: var(--sp-2); }
.fm-attach__item {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-2) var(--sp-3);
    border: 1px solid var(--c-line);
    border-radius: var(--r-sm);
    background: var(--c-surface);
    font-size: var(--fs-sm);
}
/* ⚠ The file input is a direct child of the <fieldset>, NOT of .fm-attach__item — so a
   `flex: 1 1 auto` here (an earlier draft had one) is inert. It needs a real treatment
   instead: pf-input gives it a bordered box, and this keeps a long chosen filename from
   widening the form. */
.fm-attach__file { display: block; margin-top: var(--sp-3); max-width: 100%; overflow-wrap: anywhere; }
.fm-attach__item .fm-hint { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.fm-attach__note { margin-top: var(--sp-2); color: var(--c-text-muted); font-size: var(--fs-sm); }

/* The server-rendered preview — the zero-JS answer to phpBB's live preview. */
.fm-preview {
    margin: 0 0 var(--sp-5);
    border: 1px solid var(--c-accent);
    border-radius: var(--r-md);
    background: var(--c-surface);
    overflow: hidden;
}
.fm-preview__title {
    margin: 0;
    padding: 8px var(--sp-4);
    background: var(--c-accent-wash);
    border-bottom: 1px solid var(--c-line);
    color: var(--c-accent);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    letter-spacing: .04em;
    text-transform: uppercase;
}
.fm-preview .fm-post__body { padding: var(--sp-4); }

/* The report form and the split page both quote source text. Escaped in <pre>
   at the Razor layer; wrapped and bounded here. */
.fm-report__excerpt,
.fm-search__excerpt,
.fm-modlist__excerpt,
.fm-split-post__excerpt,
.fm-draft__excerpt,
.fm-snapshot,
.fm-note__text {
    margin: var(--sp-2) 0 0;
    padding: var(--sp-3);
    border: 1px solid var(--c-line);
    border-left: 3px solid var(--c-line-strong);
    border-radius: 0 var(--r-sm) var(--r-sm) 0;
    background: var(--c-surface-2);
    color: var(--c-text-muted);
    font-family: inherit;
    font-size: var(--fs-sm);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    max-height: 14rem;
    overflow-y: auto;
}

/* The split page: a checkbox per post. */
.fm-split-form__fields { display: flex; flex-wrap: wrap; gap: var(--sp-4); margin-bottom: var(--sp-4); }
.fm-split-posts { list-style: none; margin: 0 0 var(--sp-4); padding: 0; display: grid; gap: var(--sp-2); }
.fm-split-post {
    padding: var(--sp-3);
    border: 1px solid var(--c-line);
    border-radius: var(--r-sm);
    background: var(--c-surface);
}
.fm-split-post__head { color: var(--c-text-muted); font-size: var(--fs-sm); }
.fm-merge-target { width: 7em; }

/* ── Search ─────────────────────────────────────────────────────────────── */
/* ⚠ A GRID, BECAUSE THE LABELS ARE SIBLINGS OF THEIR FIELDS, NOT WRAPPERS AROUND THEM.
   Search.cshtml emits `<label for="fm-q">Search terms</label>` followed by a separate
   `<input id="fm-q">`, three pairs deep. An earlier draft made the form a wrap-flex and gave
   .fm-search__label `flex: 1 1 12rem` on the assumption it contained its input — which turns
   every LABEL into a 12rem box competing for width with the fields, and scatters six
   independent items across two ragged rows.

   Two columns instead: label, then field, one pair per row. It needs no markup change and it
   is what the for=/id= pairing already implies. */
.fm-search__form {
    display: grid;
    grid-template-columns: max-content minmax(0, 1fr);
    align-items: center;
    gap: var(--sp-2) var(--sp-3);
    margin: 0 0 var(--sp-5);
    padding: var(--sp-4);
    border: 1px solid var(--c-line);
    border-radius: var(--r-md);
    background: var(--c-surface-2);
}
/* A hidden input is still a grid ITEM and would open an empty row. */
.fm-search__form input[type="hidden"] { display: none; }
.fm-search__form > button { grid-column: 2; justify-self: start; margin-top: var(--sp-2); }
.fm-search__label {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
}
.fm-search__form .fm-search__input,
.fm-search__form .fm-search__select { width: 100%; max-width: none; }
.fm-search__select {
    padding: 9px var(--sp-3);
    font: inherit;
    font-size: var(--fs-sm);
    color: var(--c-text);
    background: var(--c-surface);
    border: 1px solid var(--c-line-strong);
    border-radius: var(--r-sm);
    max-width: 100%;
}
.fm-search__summary { margin: 0 0 var(--sp-4); color: var(--c-text-muted); font-size: var(--fs-sm); }
/* ⚠ NO display/gap on the list itself. It is always `class="fm-list fm-search__results"`,
   and .fm-list is a bordered box with hairline-separated rows — turning it into a gapped grid
   puts empty stripes between rows that still carry their own top borders.

   ⚠ THE OPT-OUT IS WRITTEN WITH BOTH CLASSES. `.fm-search__results > li` alone ties with
   `.fm-list > li { display: flex }` on specificity, and .fm-list is declared LATER — so the
   opt-out lost and every search hit laid its title, excerpt and byline out in a row. */
.fm-list.fm-search__results > li { display: block; }
.fm-search__who { margin-top: var(--sp-1); }

/* ══════════════════════════════════════════════════════════════════════════
   LISTS — unread, active, unanswered, my forum, drafts, notifications, team
   ══════════════════════════════════════════════════════════════════════════
   Seven pages shared one class, .fm-list, which had no rule, so all seven
   rendered as UA-default bulleted <ul>s with a marker in the gutter.
   ══════════════════════════════════════════════════════════════════════════ */

.fm-list {
    list-style: none;
    margin: 0;
    padding: 0;
    border: 1px solid var(--c-line);
    border-radius: var(--r-sm);
    overflow: hidden;
}
.fm-list__item,
.fm-search__results > li,
.fm-member__post,
.fm-modlist__item,
.fm-modtree__item,
.fm-warnings__list > li {
    min-width: 0;
}
/* ⚠ flex-wrap IS LOAD-BEARING. The row is a flex line so a leading checkbox sits beside the
   title; every secondary line in it (`.fm-member__where`, `.fm-notification__who`, the draft
   excerpt) is therefore a flex ITEM, and `display: block` on a flex item does not put it on a
   new line — it renders BESIDE the title. Wrapping the line and giving those spans a
   full-width basis below is what actually makes "under" mean under. */
.fm-list > li {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: var(--sp-2);
    padding: var(--sp-3);
    border-top: 1px solid var(--c-line);
    background: var(--c-surface);
}
.fm-list > li:first-child { border-top: 0; }
.fm-list > li:nth-child(even) { background: var(--c-surface-2); }
.fm-list > li:hover { background: var(--c-accent-wash); }
.fm-list__item > input[type="checkbox"],
.fm-modlist__item > input[type="checkbox"] {
    margin: 3px var(--sp-1) 0 0;
    flex: 0 0 auto;
    width: 18px;
    height: 18px;
    accent-color: var(--c-accent);
}
.fm-list a { font-weight: var(--fw-medium); text-decoration: none; }
.fm-list a:hover { text-decoration: underline; }

/* The secondary line under a list item: where it lives, who wrote it, when. */
.fm-member__where,
.fm-notification__who,
.fm-notification__what,
.fm-draft__excerpt,
.fm-modlist__meta,
.fm-search__who {
    display: block;
    /* See .fm-list > li: these live inside a wrapping flex row, and a full-width basis is what
       drops them onto their own line. display:block alone does nothing to a flex item. */
    flex: 1 1 100%;
    color: var(--c-text-muted);
    font-size: var(--fs-xs);
}
.fm-notification__what { font-size: var(--fs-sm); color: var(--c-text); font-weight: var(--fw-medium); }
.fm-draft__subject { font-weight: var(--fw-semibold); }

/* Bulk clearing on /forum/me and the moderation console. The form is a SIBLING
   of the list it acts on (the rows carry their own single-item forms and
   nesting forms is invalid HTML), associated by the HTML5 form="id" attribute
   — so it needs its own spacing rather than inheriting the list's. */
.fm-bulk-form { display: flex; justify-content: flex-end; margin: var(--sp-3) 0 var(--sp-5); }
.fm-bulk {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-3);
    margin: var(--sp-3) 0 var(--sp-5);
    padding: var(--sp-3);
    border: 1px solid var(--c-line);
    border-radius: var(--r-md);
    background: var(--c-surface-2);
}
.fm-bulk__opt { display: inline-flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-sm); }

/* Drafts / preferences tables reuse the forum table treatment. */
.fm-drafts__table, .fm-prefs__table { width: 100%; }

/* The team page. The per-member "moderates:" list is a <ul> inside a table cell, so it
   needs its markers and its indent removed explicitly — inheriting .fm-muted's colour is
   not enough to stop it rendering as a bulleted list in the middle of a row. */
.fm-team__forums { list-style: none; margin: 0; padding: 0; }
.fm-team__forums li { display: inline; }
.fm-team__forums li + li::before { content: ", "; }

/* An unread notification row. Marked by an accent edge rather than only by weight, so it
   survives the row's zebra striping. */
.fm-list__item--unread { border-left: 3px solid var(--c-accent); }
.fm-list__item--unread .fm-notification__what { color: var(--c-text); font-weight: var(--fw-semibold); }

/* ⚠ .fm-team__list is on a <table>, NOT a <ul>. An earlier draft of this block gave it
   `display: grid`, which would have collapsed the team page's three columns into a single
   stack at every width — the class name reads like a list and the element is not one. */
.fm-team__who {
    color: var(--c-accent);
    font-weight: var(--fw-semibold);
    text-decoration: none;
}
.fm-team__who:hover { text-decoration: underline; }

/* The public profile card. Deliberately plain: it is a "who is this?" answer,
   not a second search surface. */
.fm-member__meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2);
    margin-top: var(--sp-2);
    color: var(--c-text-muted);
    font-size: var(--fs-sm);
}
.fm-member__meta .fm-handle { margin-left: 0; }
.fm-member__posts { list-style: none; padding: 0; }

/* ══════════════════════════════════════════════════════════════════════════
   THE MODERATION CONSOLE
   ══════════════════════════════════════════════════════════════════════════ */
.fm-moderate__heading { margin: 0 0 var(--sp-2); }
.fm-moderate__scope {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    margin: 0 0 var(--sp-5);
    padding: 4px var(--sp-3);
    border-radius: var(--r-pill);
    background: var(--c-accent-wash);
    color: var(--c-accent);
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
}
.fm-moderate__sub, .fm-section__title {
    margin: var(--sp-6) 0 var(--sp-3);
    padding-bottom: var(--sp-2);
    border-bottom: 2px solid var(--c-accent-wash);
    font-size: var(--fs-lg);
    font-weight: var(--fw-semibold);
}

/* The moderator bar that rides a topic page. Danger-tinted so it can never be
   mistaken for an ordinary member control. */
.fm-modbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2) var(--sp-3);
    margin: 0 0 var(--sp-5);
    padding: var(--sp-3);
    border: 1px solid var(--c-danger);
    border-radius: var(--r-md);
    background: var(--c-danger-wash);
}
.fm-modbar__label {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    color: var(--c-danger);
    font-weight: var(--fw-semibold);
    font-size: var(--fs-sm);
}
.fm-modbar form { display: inline-flex; align-items: center; gap: var(--sp-2); margin: 0; }

.fm-modlist { list-style: none; margin: 0 0 var(--sp-5); padding: 0; display: grid; gap: var(--sp-3); }
.fm-modlist__item {
    padding: var(--sp-4);
    border: 1px solid var(--c-line);
    border-radius: var(--r-md);
    background: var(--c-surface);
}
.fm-modlist__item--tight { padding: var(--sp-3); }
.fm-modlist--reports .fm-modlist__item { border-left: 3px solid var(--c-warn); }
.fm-modlist--decided .fm-modlist__item { border-left: 3px solid var(--c-success); opacity: .85; }
.fm-modlist--log .fm-modlist__item { border-left: 3px solid var(--c-line-strong); }
.fm-modlist__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2);
    margin-top: var(--sp-3);
    padding-top: var(--sp-3);
    border-top: 1px solid var(--c-line);
}
.fm-modlist__actions form { display: inline-flex; align-items: center; gap: var(--sp-2); margin: 0; }
.fm-modlist__note, .fm-modlist__sanctions { margin-top: var(--sp-2); color: var(--c-text-muted); font-size: var(--fs-sm); }
.fm-list--notes { margin-top: var(--sp-3); }

/* The forum tree on the administration tab. */
.fm-modtree { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-2); }
.fm-modtree__item {
    padding: var(--sp-3);
    border: 1px solid var(--c-line);
    border-radius: var(--r-sm);
    background: var(--c-surface);
}
.fm-modtree__head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2);
    font-weight: var(--fw-semibold);
}
.fm-modtree__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2);
    margin-top: var(--sp-3);
}
.fm-modtree__actions form { display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); margin: 0; }

/* ══════════════════════════════════════════════════════════════════════════
   THE REFUSAL / EMPTY / UNAVAILABLE PAGES
   ══════════════════════════════════════════════════════════════════════════ */
.fm-denied, .fm-notfound, .fm-unavailable { max-width: 62ch; }
.fm-denied__options { display: flex; flex-wrap: wrap; gap: var(--sp-3); margin-top: var(--sp-5); }
.fm-provisioning-note { margin-top: var(--sp-4); color: var(--c-text-muted); font-size: var(--fs-sm); }
/* ==========================================================================
   Navigation affordances phpBB has and this board did not
   ==========================================================================
   Every one of these replaces a phpBB control that is JavaScript in prosilver.
   This host renders no <script> on any page, so the primitive is always a
   <details> or a plain form - and in two cases that is genuinely BETTER than
   parity, because phpBB's own control is inert with JS off.
   ========================================================================== */

/* -- The jumpbox -----------------------------------------------------------
   prosilver includes jumpbox.html on EIGHT page types. It is the only control
   that lets a reader move SIDEWAYS - out of a topic in forum A straight into
   forum F - and this board had no equivalent at all, so forum-to-forum cost
   three navigations. phpBB's is a <select> that needs JS or a submit button;
   a <details> of plain anchors is one click either way and needs neither. */
.fm-jump { margin: var(--sp-4) 0 0; }
.fm-jump > summary {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    min-height: 40px;
    padding: 6px var(--sp-3);
    border: 1px solid var(--c-line-strong);
    border-radius: var(--r-pill);
    background: var(--c-surface);
    color: var(--c-text);
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    cursor: pointer;
    list-style: none;
}
.fm-jump > summary::-webkit-details-marker { display: none; }
.fm-jump > summary:hover { background: var(--c-accent-wash); border-color: var(--c-accent); color: var(--c-accent); }
.fm-jump__list {
    list-style: none;
    margin: var(--sp-2) 0 0;
    padding: var(--sp-2);
    border: 1px solid var(--c-line);
    border-radius: var(--r-md);
    background: var(--c-surface);
    max-height: 22rem;
    overflow-y: auto;
    columns: 2 16rem;
}
.fm-jump__list a {
    display: block;
    padding: 7px var(--sp-3);
    border-radius: var(--r-sm);
    color: var(--c-text);
    text-decoration: none;
    font-size: var(--fs-sm);
    break-inside: avoid;
}
.fm-jump__list a:hover { background: var(--c-accent-wash); color: var(--c-accent); }
/* The indent is a CLASS, not a style attribute: the CSP is style-src 'self',
   and <option> text cannot be indented by CSS at all - which is one more reason
   the <details> shape is the right one here. */
.fm-jump__sub a { padding-inline-start: var(--sp-6); color: var(--c-text-muted); }

/* -- Bottom-of-page navigation --------------------------------------------
   phpBB ends every long page with THREE separate exits (return-to-forum, the
   jumpbox, and a footer navbar repeating the board-index crumb). The
   redundancy is the point: after twenty-five posts the top breadcrumb is a
   screen and a half away. Every forum page here ended on whatever form
   happened to be last. */
.fm-footnav {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-3);
    margin-top: var(--sp-6);
    padding-top: var(--sp-4);
    border-top: 1px solid var(--c-line);
    font-size: var(--fs-sm);
}
.fm-footnav__spacer { flex: 1 1 auto; }
/* ⚠ DIRECT CHILD. `.fm-footnav a` also matched every anchor inside the jumpbox, which is a
   <details> nested in this same <nav> — equal specificity to `.fm-jump__list a` and later in
   source order, so the pill treatment won and turned a two-column list of forum links into a
   column of 40px pills. */
.fm-footnav > a {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    min-height: 40px;
    padding: 6px var(--sp-3);
    border-radius: var(--r-pill);
    color: var(--c-text);
    text-decoration: none;
    font-weight: var(--fw-medium);
}
.fm-footnav > a:hover { background: var(--c-accent-wash); color: var(--c-accent); }

/* -- The timezone statement -----------------------------------------------
   phpBB gives {S_TIMEZONE} a permanent footer slot precisely BECAUSE every
   timestamp is rendered in the viewer's own zone with no per-timestamp label.
   Here the signed-out branch appends " UTC" and the signed-in branch appends
   the empty string - so a member saw times with no zone stated anywhere on the
   page. On a board where people arrange to meet, that is ambiguous
   information, not a missing label. */
.fm-tz { margin: 0; color: var(--c-text-faint); font-size: var(--fs-xs); }

/* -- Back to top ----------------------------------------------------------
   A plain anchor to #main, not a scroll script. */
.fm-totop { margin-left: auto; }

/* -- The item count line --------------------------------------------------
   phpBB prints "N posts - Page X of Y" above every pager AND in the single-page
   case, where a pager is not rendered at all. _Pager is gated on
   TotalPages > 1, so a one-page topic said nothing about its own size. */
.fm-count {
    color: var(--c-text-muted);
    font-size: var(--fs-sm);
    font-variant-numeric: tabular-nums;
}
.fm-count strong { color: var(--c-text); font-weight: var(--fw-semibold); }

/* -- Topic tools ----------------------------------------------------------
   NOTE THE DIRECTION OF THIS ONE. The moderator controls here are already
   plain zero-JS forms, which is AHEAD of phpBB, whose quickmod is a JavaScript
   dropdown of anchors. Only the presentation was wrong: eight permanently
   expanded forms in one row, burying Reply and putting a destructive control a
   mis-tap away from a benign one. Collapsed by default; Reply stays outside. */
.fm-modtools { margin: 0 0 var(--sp-5); }
.fm-modtools > summary {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    min-height: 40px;
    padding: 6px var(--sp-3);
    border: 1px solid var(--c-danger);
    border-radius: var(--r-pill);
    background: var(--c-danger-wash);
    color: var(--c-danger);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    cursor: pointer;
    list-style: none;
}
.fm-modtools > summary::-webkit-details-marker { display: none; }
.fm-modtools[open] > summary { margin-bottom: var(--sp-2); }

/* -- Display options ------------------------------------------------------
   phpBB's display_options.html is ALREADY a plain form with three <select>s;
   the dropdown around it is decoration the JS toggles. Stripping the
   decoration yields a working control, which makes this the cheapest parity
   win on the board.

   Two deliberate divergences from phpBB: method="get", so a sorted view is
   linkable, bookmarkable and cacheable; and a reset to page 1 on change, so
   "sort by oldest" from page 6 does not land on an unrelated slice. */
.fm-displayopts {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--sp-3);
    margin: 0 0 var(--sp-3);
}
.fm-displayopts label {
    display: inline-flex;
    flex-direction: column;
    gap: 2px;
    font-size: var(--fs-xs);
    color: var(--c-text-muted);
    font-weight: var(--fw-semibold);
    text-transform: uppercase;
    letter-spacing: .04em;
}

/* -- The per-topic mini pager --------------------------------------------
   phpBB renders "1 2 3 ... 9" inside the topic ROW in five templates, so a
   reader can enter a long thread at the page they want. Chips are 18x22 at
   12px with a 2px radius - deliberately smaller than the page pager, because
   they are a secondary control inside a row. */
.fm-minipager {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 3px;
    margin-left: var(--sp-2);
    vertical-align: middle;
}
.fm-minipager__chip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* 24x24 is WCAG 2.5.8's floor, and these sit three-abreast inside a row where a mis-tap
       lands on the topic title. phpBB's own chips are 18x22 and predate that criterion. */
    min-width: 24px;
    min-height: 24px;
    padding: 1px 4px;
    border: 1px solid var(--c-line-strong);
    border-radius: 2px;
    background: var(--c-surface);
    color: var(--c-text-muted);
    font-size: var(--fs-xs);
    text-decoration: none;
    line-height: 1.4;
}
.fm-minipager__chip:hover { background: var(--c-accent); border-color: var(--c-accent); color: var(--c-accent-ink); }
.fm-minipager__gap { color: var(--c-text-faint); font-size: var(--fs-xs); }

/* -- Quick reply ----------------------------------------------------------
   The most-repeated interaction on a support board, and it cost a full page
   navigation to /forum/t/{id}/reply and back. A <details> holding a plain POST
   to the action that already exists - no controller change, no script. */
.fm-quickreply { margin: var(--sp-5) 0; }
.fm-quickreply > summary {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    min-height: 44px;
    padding: 10px var(--sp-4);
    border-radius: var(--r-pill);
    background: var(--c-primary);
    color: var(--c-primary-ink);
    font-weight: var(--fw-semibold);
    cursor: pointer;
    list-style: none;
}
.fm-quickreply > summary::-webkit-details-marker { display: none; }
.fm-quickreply__body {
    margin-top: var(--sp-3);
    padding: var(--sp-4);
    border: 1px solid var(--c-line);
    border-radius: var(--r-md);
    background: var(--c-surface);
}

/* -- The FAQ answer body --------------------------------------------------
   /faq/{slug} renders sanitized answer HTML into .faq-entry__answer with no
   .faq ancestor, so - unlike the FAQ index, which is sheltered by the pricing
   page's .faq p rule - every paragraph, list and heading in a linked answer had
   nothing but UA defaults. The "Link to this answer" affordance therefore led
   to the worst-formatted page on the host. */
.faq-entry { max-width: 70ch; }
/* ⚠ THIS RENDERS THE SAME SANITIZED AUTHOR HTML AS .fm-post__body, so it needs the same
   containment. Without it a long URL or a wide table in an FAQ answer widens the document,
   on a page whose whole job is to be readable. */
.faq-entry__answer { line-height: var(--fm-lh-body); overflow-wrap: anywhere; }
.faq-entry__answer img { max-width: 100%; height: auto; }
.faq-entry__answer table {
    display: block;
    width: max-content;
    max-width: 100%;
    overflow-x: auto;
}
.faq-entry__answer > :first-child { margin-top: 0; }
.faq-entry__answer p { margin: 0 0 var(--sp-3); }
.faq-entry__answer ul, .faq-entry__answer ol { margin: 0 0 var(--sp-3); padding-left: var(--sp-6); }
.faq-entry__answer li { margin-bottom: var(--sp-1); }
.faq-entry__answer h2, .faq-entry__answer h3, .faq-entry__answer h4 {
    margin: var(--sp-5) 0 var(--sp-2);
    font-size: var(--fs-lg);
    line-height: var(--lh-tight);
}
.faq-entry__answer code {
    padding: 1px 5px;
    border-radius: 4px;
    background: var(--c-surface-2);
    font-family: var(--fm-mono);
    font-size: 0.9em;
}
.faq-entry__answer pre {
    margin: 0 0 var(--sp-3);
    padding: var(--sp-3);
    border: 1px solid var(--c-line);
    border-radius: var(--r-sm);
    background: var(--c-surface-2);
    overflow-x: auto;
}
.faq-entry__answer blockquote {
    margin: 0 0 var(--sp-3);
    padding: var(--sp-3) var(--sp-4);
    border-left: 3px solid var(--c-accent);
    background: var(--c-surface-2);
    color: var(--c-text-muted);
}
.faq-entry__meta { margin-top: var(--sp-5); color: var(--c-text-muted); font-size: var(--fs-sm); }
.faq-category__name {
    margin: clamp(28px,5vw,44px) 0 var(--sp-3);
    padding-bottom: var(--sp-2);
    border-bottom: 2px solid var(--c-accent-wash);
    font-size: var(--fs-xl);
}
.faq-category__desc { margin: 0 0 var(--sp-4); color: var(--c-text-muted); }
.faq-entry__permalink { margin-left: var(--sp-2); color: var(--c-text-faint); font-size: var(--fs-xs); text-decoration: none; }
.faq-entry__permalink:hover { color: var(--c-accent); }

/* -- The marketing "shot" line modifiers ----------------------------------
   Six of the host's nine inline style="" attributes were here. They render
   today only because the strict CSP is installed on the /forum branch alone -
   which makes the stated rule a convention rather than a control. A closed set
   of modifiers is what makes it a control. */
.mshot__line--w70 { width: 70%; }
.mshot__line--w60 { width: 60%; }
.mshot__line--w45 { width: 45%; }
.mshot__line--out { margin-left: auto; }
.mshot__cardline--composer { height: 40px; margin-top: auto; }
.mshot__cardline--hero { height: 180px; background: var(--c-accent-wash); border-color: var(--c-accent); }

/* Which way round the page is: see the single declaration at the top of this
   file. A second, identical pair used to sit here, 2,500 lines away from it. */

/* The footer toggle and the footer sign-out are submit buttons that have to read as
   one of the legal links beside them — button elements are used (not links) because
   they POST. */
.mk-footer__theme, .mk-footer__signout { display: inline; }

.mk-footer__theme-btn, .mk-footer__linklike-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 40px;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  text-decoration: underline;
}

/* The site's ONLY theme control was an underlined word in a row of legal links,
   with no icon, no border and no state. A pill reads as a control; the icon
   says which state it will move you to. */
.mk-footer__theme-btn {
  padding: 6px 12px;
  border: 1px solid var(--mk-footer-rule);
  border-radius: var(--r-pill);
  text-decoration: none;
}
.mk-footer__theme-btn:hover { background: var(--mk-footer-rule); }

.mk-footer__theme-btn:hover, .mk-footer__linklike-btn:hover { color: var(--mk-footer-ink); }

.mk-footer__theme-btn:focus-visible, .mk-footer__linklike-btn:focus-visible {
  outline: 2px solid var(--c-focus-ring);
  outline-offset: 2px;
}

/* The header/menu sign-out form is a zero-box wrapper: display:contents lifts the
   button into the surrounding flex row, so it sits exactly where the sign-in link it
   replaces would. */

/* ══════════════════════════════════════════════════════════════════════════
   RESPONSIVE — the forum and the portal on a phone
   ══════════════════════════════════════════════════════════════════════════
   This file previously carried FOUR media queries (860 / 768 / 720 / 560), all
   of them for the marketing pages and the portal's key table. The forum had
   NONE: on a 360px phone the board index rendered a four-column table with a
   17rem "Latest" cell, so the page scrolled sideways and the topic titles were
   unreadable.

   The breakpoints below mirror prosilver's, because they encode a real answer
   rather than a round number:

     ≤ 980px  the post's author column is no longer affordable beside a
              readable body measure, so it becomes a strip above it.
     ≤ 860px  a four-column listing no longer fits; rows reflow to a stacked
              block with one meta line. (phpBB does this at 750px, with a
              narrower base column; ours starts wider.)
     ≤ 700px  the panel goes edge-to-edge and drops its frame — the gutter is
              worth more to the content than the border is to the composition.
     ≤ 560px  button rows go full-width so a thumb cannot miss, and the
              identity strip stops trying to right-align.

   🔴 NOTHING BELOW DEPENDS ON JAVASCRIPT. There is no <script> on any page of
      this host, so a "mobile menu" here can only be a <details>, a collapsed
      column can only be a display change, and a sticky bar can only be CSS.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── The post reflow ─────────────────────────────────────────────────────
   prosilver's 750px rule, at the width where OUR profile column starts eating
   the body measure. The profile is first in the DOM, so dropping the grid puts
   it above the body with no reordering — and it becomes a horizontal strip,
   not a stacked card, because a full-height author panel above every post
   would push the actual writing off the screen. */
@media (max-width: 980px) {
    .fm-post__inner { display: block; }
    .fm-post__profile {
        flex-direction: row;
        align-items: center;
        justify-content: flex-start;
        gap: var(--sp-3);
        padding: var(--sp-2) var(--sp-3);
        border-left: 0;
        border-bottom: 1px solid var(--c-line);
        text-align: left;
        flex-wrap: wrap;
    }
    .fm-avatar { width: 36px; height: 36px; font-size: var(--fs-sm); }
    /* Posts / Joined go — they are reference facts, and the strip has to stay
       one line tall or it is not a strip. phpBB hides exactly these dd's. */
    .fm-post__stats { display: none; }
    .fm-post__main { padding: var(--sp-3); }
}

/* ── The listing reflow ──────────────────────────────────────────────────
   A row becomes a block: the title cell keeps the full width, the numeric
   cells collapse onto one small meta line, and the last-post cell becomes a
   footer line. The data-label attributes that used to drive the portal's
   stacked treatment still name each stacked cell. */
@media (max-width: 860px) {
    .fm-table,
    .fm-table tbody,
    .fm-table tr,
    .fm-table td { display: block; width: auto; }
    .fm-table thead { display: none; }
    .fm-table { border-radius: var(--r-sm); }

    .fm-table tbody tr {
        padding: var(--sp-3);
        border-top: 1px solid var(--c-line);
    }
    .fm-table tbody tr:first-child { border-top: 0; }
    .fm-table tbody td { border: 0; padding: 0; background: none; }
    .fm-table tbody td + td { margin-top: var(--sp-1); }

    /* ⚠ THE STRIPE MOVES TO THE ROW. Above this breakpoint it is painted on `> td`, which is
       correct for a real table; here the cells are blocks with zero padding, so a fill on them
       covers only the text and the stripe breaks into ragged bands with gaps between the
       cells. The row is the thing with the padding once it reflows, so the row is the thing
       that carries the fill. */
    .fm-table tbody tr:hover { background: var(--fm-row-hover); }
    .fm-table--zebra tbody tr:nth-child(even) { background: var(--fm-row-alt); }
    .fm-table--zebra tbody tr:nth-child(even):hover { background: var(--fm-row-hover); }

    /* The icon cell rides beside the title rather than owning a row of its own. */
    .fm-table td.fm-col--icon { display: none; }

    /* Numeric columns become one inline run of "Topics 12 · Posts 340". The
       label comes from data-label, so a cell that loses its column header is
       still self-describing — phpBB does the same with its <dfn> labels, which
       are visually hidden on desktop and shown inline on mobile. */
    .fm-table td.fm-col--num {
        display: inline-block;
        width: auto;
        margin: var(--sp-2) var(--sp-3) 0 0;
        text-align: left;
        font-size: var(--fs-xs);
    }
    /* ⚠ EVERY stacked cell names itself, not only the numeric ones. The first cut labelled
       .fm-col--num and .fm-col--last alone, so a "Forum" cell on /forum/active, and every
       column of the team, drafts and notification-preference tables, lost its header outright
       and rendered as a bare value under the row's title. The suppressor below is what keeps
       the TITLE cell from announcing its own column name, which would be noise — it is the
       row's name, not a labelled field. */
    .fm-table tbody td[data-label]::before {
        content: attr(data-label) " ";
        color: var(--c-text-muted);
        font-weight: var(--fw-semibold);
    }

    /* AFTER the rule above — identical specificity, so only source order separates them. Two
       shapes because a listing may or may not carry the glyph column. */
    .fm-table tbody tr > td:first-child::before,
    .fm-table tbody td.fm-col--icon + td::before { content: ""; }

    .fm-table td.fm-col--num::before,
    .fm-table td.fm-col--last::before { font-weight: var(--fw-medium); }
    .fm-table td.fm-col--last {
        width: auto;
        margin-top: var(--sp-2);
        padding-top: var(--sp-2);
        border-top: 1px dashed var(--c-line) !important;
        font-size: var(--fs-xs);
    }

    .fm-lastpost__title { display: inline; white-space: normal; }

    /* A stacked row is a tap target: give the whole thing a hover/active wash
       rather than only the link inside it. */
    .fm-table tbody tr:hover { background: var(--c-accent-wash); }
}

/* ── The identity strip and action bars ─────────────────────────────────── */
@media (max-width: 700px) {
    .fm-identity {
        gap: var(--sp-1);
        padding: var(--sp-2);
        border-radius: var(--r-sm);
    }
    /* Stop pushing the link run to the right: on a narrow strip that leaves a
       ragged first line and a full second one. */
    .fm-identity > .fm-identity__action:first-of-type { margin-left: 0; }
    .fm-identity__who { flex-basis: 100%; margin-bottom: var(--sp-1); }
    .fm-identity__action { padding: 8px var(--sp-2); font-size: var(--fs-xs); }

    .fm-actionbar { gap: var(--sp-2); }
    .fm-actionbar__spacer { display: none; }

    .fm-post__actions { justify-content: flex-start; }
    .fm-post__perma { margin-left: 0; }

    /* One column on a phone: a max-content label column plus a field is too tight below
       ~500px, and the labels here are full phrases ("Search terms", "Author", "Forum"). */
    .fm-search__form { grid-template-columns: minmax(0, 1fr); padding: var(--sp-3); }
    .fm-search__form > button { grid-column: 1; width: 100%; justify-self: stretch; }
    .fm-search__label { margin-top: var(--sp-2); }

    .fm-crumbs__list a { max-width: 14ch; }

    /* A 44px pager target is worth more than a compact row on a touch screen. */
    .fm-pager__page, .fm-pager__step { min-width: 44px; min-height: 44px; }
}

@media (max-width: 560px) {
    /* One button per line beats two half-buttons that both get mis-tapped. */
    .fm-cta-row > .mk-btn,
    .fm-cta-row > form,
    .fm-cta-row > form > .mk-btn,
    .fm-denied__options > .mk-btn { width: 100%; }
    .fm-cta-row { gap: var(--sp-2); }

    .fm-modbar { gap: var(--sp-2); }
    .fm-modbar form, .fm-modlist__actions form { width: 100%; }
    .fm-modbar form > .mk-btn, .fm-modlist__actions form > .mk-btn { width: 100%; }

    .fm-poll__tally progress { flex-basis: 100%; max-width: none; }
    .fm-post__stats { grid-template-columns: auto auto; }
    .fm-list > li { flex-wrap: wrap; }
    .fm-table td.fm-col--num { margin-right: var(--sp-2); }
}

/* ── Very small screens (320px) ───────────────────────────────────────────
   The floor prosilver still supports. Nothing may overflow horizontally here. */
@media (max-width: 380px) {
    .fm-avatar { width: 28px; height: 28px; font-size: var(--fs-xs); }
    .fm-post__profile { gap: var(--sp-2); padding: var(--sp-2); }
    .fm-forum__name, .fm-topic__title { font-size: var(--fs-sm); }
    .fm-badge, .fm-badge--staff, .fm-badge--mod, .fm-badge--qa, .fm-badge--announce,
    .fm-badge--sticky, .fm-badge--locked, .fm-badge--solved, .fm-badge--pending,
    .fm-badge--readonly, .fm-badge--new { padding: 1px 6px; }
    .fm-crumbs__list a { max-width: 10ch; }
}

/* ── Portal + marketing responsive completions ───────────────────────────
   The portal's table already stacked at 720px. What it did NOT do was give the
   stacked rows any separation from each other beyond a hairline, keep the
   one-time key panel from overflowing, or stop the header row from wrapping
   into an unreadable pile. */
@media (max-width: 860px) {
    .pf-head { flex-direction: column; align-items: stretch; gap: var(--sp-3); }
    .pf-table tr {
        margin-bottom: var(--sp-3);
        padding: var(--sp-3);
        border: 1px solid var(--c-line);
        border-radius: var(--r-sm);
        background: var(--c-surface);
    }
    .pf-table tbody tr:nth-child(even) { background: var(--c-surface-2); }
    .pf-card, .pf-newkey { padding: var(--sp-4); }
    .pf-newkey__value { padding: var(--sp-3); font-size: var(--fs-xs); }
    .pf-check { align-items: flex-start; }
    .pf-inline-form { display: flex; width: 100%; margin-right: 0; }
    .pf-inline-form .mk-btn { width: 100%; }
    .pf-actions { display: flex; flex-direction: column; gap: var(--sp-2); }
    .pf-denied__actions > .mk-btn { width: 100%; }

    /* Rotate and Revoke sit side by side on a stacked card, and the page's own
       footnote describes Revoke as irreversible. Desktop keeps the compact size. */
    .pf-btn--sm, .pf-input--tiny, .pf-input--sm { min-height: 44px; padding-block: 10px; }

    /* The key's NAME is the field a developer scans a stacked list for, and it
       was rendering at --fs-sm behind a muted "Name: " prefix like every other
       cell. Promote it and drop the prefix; the row then leads with the answer. */
    .pf-table td[data-label="Name"] {
        font-size: var(--fs-lg);
        font-weight: var(--fw-semibold);
        padding-bottom: var(--sp-2);
    }
    .pf-table td[data-label="Name"]::before { content: ""; }
}

/* The marketing shell. .mk-nav/.mk-cta already hide at 860px in favour of the
   <details> menu; these rules make the rest of the chrome behave at phone
   widths — the header was allowed to keep a 60px minimum and a 20px gap it
   could not afford, and the footer's two-column grid collapsed without ever
   tightening its own spacing. */
@media (max-width: 700px) {
    .mk-header__inner { gap: var(--sp-3); min-height: 56px; }
    .mk-brand__name { font-size: var(--fs-md); }
    .mk-menu__panel {
        position: fixed;
        left: var(--page-pad-x);
        right: var(--page-pad-x);
        top: 64px;
        /* dvh, NOT vh — the base rule uses dvh for a reason. On a mobile browser vh is the
           LARGEST viewport height (toolbars retracted), so a vh-based max-height leaves the
           panel taller than the space actually on screen while the toolbars are showing:
           exactly the unreachable "Sign in" / "Get started" the base rule exists to fix,
           reintroduced by its own override. */
        max-height: calc(100dvh - 80px);
        overflow-y: auto;
        -webkit-overflow-scrolling: touch;
    }
    .mk-menu__link { padding: 12px; min-height: 44px; display: flex; align-items: center; }
    .mk-footer__inner { gap: var(--sp-5); padding-block: clamp(24px, 6vw, 40px); }
    .mk-footer__legal { flex-direction: column; align-items: flex-start; }
    .mk-footer__legal-links { flex-wrap: wrap; gap: var(--sp-3); }
}

/* -- The narrow end of the ladder ----------------------------------------
   prosilver's own floor is 320px and it spends three more steps getting there.
   Nothing on this host targeted below 380px at all. */
@media (max-width: 430px) {
    /* phpBB shrinks the action-bar search box hard at this width rather than
       letting it push the buttons beside it off the row. */
    .fm-topic__search .fm-search__input { flex-basis: 100%; }
    .fm-topic__search .mk-btn { width: 100%; }
    .fm-actionbar > .mk-btn { width: 100%; }
    .fm-jump__list { columns: 1; }
}

@media (max-width: 380px) {
    /* phpBB drops the last-post column one step before its major reflow
       (responsive.css: ul.topiclist.forums dd.lastpost { display:none } at
       550px). Here the stacked row carries title + description + a numeric run
       + a last-post line, and the last-post line is the tallest and the least
       scanned of them. Dropping it is what buys the row back. Nothing is lost:
       the row's own title link reaches the same conversation, and the last
       poster is one tap further on. */
    .fm-table td.fm-col--last { display: none; }
    .fm-forum__desc { display: none; }
}

@media (max-width: 320px) {
    /* prosilver caps every select and text input at 240px here. An uncapped
       <select> holding a long forum name is the last remaining way this board
       can widen a 320px document. */
    /* ⚠ NOT textarea. .pf-input is on the compose body and the quick-reply box too, and
       capping a composer at 240px on a 320px screen is the opposite of the intent — the cap
       exists to stop a long <option> or a wide text field widening the document. */
    select, input[type="text"], input[type="search"], .fm-search__input,
    .pf-input:not(textarea) {
        max-width: 240px;
    }
    .mk-container, body[data-surface='app'] > main > .msec > .mk-container {
        padding-inline: var(--sp-3);
    }
}

/* ── Print ────────────────────────────────────────────────────────────────
   phpBB ships a print stylesheet and offers "Print view" on every topic. This
   host cannot offer a separate print URL without another route, but it can
   make Ctrl-P produce something worth reading: chrome off, posts unframed,
   links resolved. */
@media print {
    .mk-header, .mk-footer, .fm-identity, .fm-crumbs, .fm-actionbar,
    .fm-post__actions, .fm-modbar, .fm-topic__search, .fm-pager,
    .skip-link, .fm-bulk, .fm-bulk-form { display: none !important; }
    body, body[data-surface='app'] { background: #fff; color: #000; }
    body[data-surface='app'] > main > .msec > .mk-container {
        border: 0; border-radius: 0; box-shadow: none; padding: 0; max-width: none;
    }
    .fm-post { border: 0; border-bottom: 1px solid #999; border-radius: 0; page-break-inside: avoid; }
    .fm-post__inner { display: block; }
    .fm-post__profile { border: 0; background: none; padding: 0 0 4px; flex-direction: row; gap: 8px; }
    .fm-avatar { display: none; }
    .fm-post__main { padding: 0; }
    .fm-post__body a::after { content: " (" attr(href) ")"; font-size: 0.8em; color: #555; }
}

/* ── Motion ───────────────────────────────────────────────────────────────
   Everything here is a colour or a 1px transform; honour the preference
   anyway, because the marketing pages animate on scroll and a reader who asked
   for less motion asked for it site-wide. */
@media (prefers-reduced-motion: reduce) {
    .mk-btn, .fm-identity__action, .fm-pager__page, .fm-pager__step,
    .fm-table tbody tr, .fm-list > li { transition: none !important; }
    .mk-btn:active { transform: none; }
}

/* The header/menu sign-out form is a zero-box wrapper: display:contents lifts the
   button into the surrounding flex row, so it sits exactly where the sign-in link it
   replaces would. */
.mk-chrome-signout { display: contents; }

.fm-split-form { margin: 0; }

/* ⚠ .fm-modlist__snapshot IS A <details>, NOT A <pre> — the moderation console's "what was
   reported" disclosure, whose <pre class="fm-snapshot"> child already carries the excerpt
   treatment from the shared quoted-content rule above. An earlier draft gave THIS element the
   excerpt look, which is the same class-name-reads-like-the-wrong-element mistake as
   .fm-team__list and .fm-radio: white-space:pre-wrap on the <details> would have made the
   Razor indentation inside the <summary> significant, and max-height would have clipped the
   summary along with the snapshot.

   Styled as a disclosure, matching .fm-post__blocked — the board's other collapse over
   content somebody chose not to see by default. */
.fm-modlist__snapshot { margin: var(--sp-3) 0 0; }
.fm-modlist__snapshot > summary {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    min-height: 40px;
    cursor: pointer;
    color: var(--c-text-muted);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    list-style: none;
}
.fm-modlist__snapshot > summary::-webkit-details-marker { display: none; }
.fm-modlist__snapshot > summary::before {
    content: "\e902";
    font-family: "primeicons";
    font-weight: normal;
    color: var(--c-icon-faint);
}
.fm-modlist__snapshot[open] > summary::before { content: "\e903"; }

/* Two marketing-page modifiers that replace inline style attributes (2026-08-22).
   The host's stated rule is style-src 'self' with no inline style attributes; nine
   survived because the strict CSP is installed on the /forum branch alone, so they
   rendered by accident of scope rather than by permission. */
.frow__media--centred { margin-top: var(--sp-6); max-width: 520px; margin-inline: auto; }
.frow__copy--spaced { margin-top: var(--sp-5); }
.mshot__cardline--tall { height: 120px; }

/* The FAQ question is an <h3> inside its <summary> so it appears in the heading outline;
   these rules keep it looking exactly as it did. */
.faq summary h3 { margin: 0; font-size: inherit; font-weight: inherit; line-height: inherit; }
.faq__answer p:first-child { margin-top: 0; }
.faq__permalink { font-size: var(--fs-sm); }
.faq-entry__crumb { margin: 0 0 var(--sp-3); color: var(--c-text-muted); font-size: var(--fs-sm); }

/* ---------------------------------------------------------------------------
   Share landings — /share/group/{guid}, /share/activity/{guid}, /o/{guid}
   ---------------------------------------------------------------------------
   The first thing a person sees after following a shared link, and the page a
   link-preview bot reads. Centred single column: there is exactly one thing to
   do here, which is go to the app.

   Colour comes only from tokens.css (shared verbatim with PTM.UX) — never a
   literal hex, per the layout's standing rule.
   --------------------------------------------------------------------------- */
.share-lander {
  max-width: 44rem;
  margin-inline: auto;
  text-align: center;
}

/* Wide, short crop so a portrait cover cannot push the name below the fold on a
   phone. object-fit keeps it from distorting whatever aspect the organizer
   uploaded. */
.share-lander__cover {
  display: block;
  width: 100%;
  max-height: 20rem;
  object-fit: cover;
  border-radius: 12px;
  margin-bottom: clamp(16px, 3vw, 28px);
  background: var(--c-surface);
}

.share-lander__meta {
  margin: 8px 0 0;
  color: var(--c-text-muted);
  font-size: 0.95rem;
}

.share-lander__tags {
  list-style: none;
  margin: 16px 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: center;
}

.share-lander__tags li {
  padding: 4px 12px;
  border: 1px solid var(--c-line);
  border-radius: 999px;
  color: var(--c-text-muted);
  font-size: 0.85rem;
}

.share-lander__actions { margin-top: clamp(24px, 4vw, 36px); }

.share-lander__empty { padding-block: clamp(16px, 4vw, 40px); }
