/*
 * One vocabulary. Every colour on the public site resolves to a token defined
 * here — there is no second palette and no bare hex in the views.
 * Definitions must stay literal hex; a var() here would be circular.
 */
:root {
    /* Brand */
    --brand-primary: #7a5abf;      /* royal purple — every interactive element */
    --brand-primary-hover: #6a4aad;
    --brand-dark: #0b1220;     /* deep indigo-black */
    --brand-bg: #f2f0fe;       /* soft lavender-white */
    --brand-white: #ffffff;
    --brand-border: #e2dffa;   /* lavender-grey border */
    --brand-text: #2a3240;     /* slate-indigo body text */
    --brand-muted: #6d6a85;
    --brand-accent: #bb69e1;       /* magenta-violet — highlights and kickers */
    --brand-accent-hover: #a855d0;

    /* Dark-surface counterparts. Body text and dividers on dark tiles need their
       own values — the light-surface muted tones vanish against --brand-dark. */
    --brand-muted-inverse: #a8a29e;
    --brand-divider-dark: #292524;

    /* Neutral behind an image while it loads, so the box does not flash white. */
    --brand-img-placeholder: #1c1917;

    /* Status. Deliberately outside the brand ramp — a success message must not
       be purple. Three semantic pairs, nothing more. */
    --status-danger: #b91c1c;
    --status-danger-bg: #fef2f2;
    --status-danger-border: #fecaca;
    --status-success: #166534;
    --status-success-bg: #dcfce7;
    --status-warning: #b45309;


    /* Fonts */
    --font-serif: 'Fraunces', Georgia, serif;
    --font-sans: 'Inter', system-ui, -apple-system, sans-serif;
    --font-mono: 'JetBrains Mono', ui-monospace, monospace;

    /*
     * Type ladder. Fifteen steps, clustered from the 38 arbitrary sizes the site
     * had grown. Steps sit where usage actually clusters, so applying the ladder
     * resizes nothing — with one deliberate exception below. Every font-size on
     * the public site resolves to one of these; a literal anywhere else is a bug.
     *
     * --text-xs is the legibility floor. Nine values between 0.55rem and 0.75rem
     * collapse into it. Those were uppercase, letter-spaced kickers and meta
     * labels; under 12px they are unreadable on the mid-range Android screens
     * most of this audience reads on.
     *
     * Font weight has no tokens on purpose — the numbers are already the
     * vocabulary. The ladder is 400/500/600/700/900.
     */
    --text-xs: 0.75rem;    /* floor — kickers, badges, meta, uppercase labels */
    --text-sm: 0.8rem;     /* small buttons, nav, dense meta */
    --text-base: 0.85rem;  /* secondary text, card summaries */
    --text-md: 0.9rem;     /* form inputs, table text */
    --text-lg: 0.95rem;    /* card excerpts */
    --text-xl: 1.05rem;    /* lead paragraphs */
    --text-2xl: 1.15rem;   /* article body */
    --text-3xl: 1.25rem;   /* subheads */
    --text-4xl: 1.4rem;    /* card titles, blockquote */
    --text-5xl: 1.5rem;    /* section headings */
    --text-6xl: 1.8rem;    /* panel headings, wordmark */
    --text-7xl: 2.2rem;    /* page title, mobile */
    --text-8xl: 2.5rem;    /* archive page title */
    --text-9xl: 3rem;      /* page title, desktop */
    --text-10xl: 3.5rem;   /* article drop cap — the only user */

    /*
     * Breakpoints. Four, and no more. CSS custom properties do not work in
     * media queries, so these cannot be tokens — this comment is the contract:
     *
     *   640px   phone → small tablet. Grids go 1→2 columns; header search and
     *           subscribe button appear; mobile search drawer hides.
     *   768px   tablet. Footer grid and a few two-column layouts.
     *   900px   nav breakpoint. Burger ↔ full nav, auth menu, sidebar splits.
     *   1024px  desktop. 3- and 4-column grids, wide article layouts.
     *
     * Anything else (992px etc.) has been migrated to the nearest member.
     * max-width values on elements (760px article measure, 500px form) are
     * layout constraints, not breakpoints, and are exempt.
     */

    /* Elevation. Two levels only — separation elsewhere comes from surface
       colour, not chrome. */
    --shadow-sm: 0 1px 2px rgba(11, 18, 32, 0.06);
    --shadow-md: 0 8px 24px rgba(11, 18, 32, 0.08);
    --radius-sm: 0px;
    --radius-md: 0px;
    --radius-lg: 0px;
}

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

body {
    font-family: var(--font-sans);
    background-color: var(--brand-bg);
    color: var(--brand-text);
    line-height: 1.6;
    overflow-x: hidden;
    -webkit-font-smoothing: antialiased;
}

a { color: inherit; text-decoration: none; transition: color 0.2s ease; }

h1, h2, h3, h4, h5, h6 {
    font-family: var(--font-serif);
    font-weight: 700;
    line-height: 1.2;
    color: var(--brand-dark);
}

/* Custom scrollbar */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: var(--brand-bg); }
::-webkit-scrollbar-thumb { background: var(--brand-border); }
::-webkit-scrollbar-thumb:hover { background: var(--brand-muted); }

.wrap { max-width: 80rem; margin: 0 auto; padding-left: 1rem; padding-right: 1rem; }
@media (min-width: 640px) { .wrap { padding-left: 1.5rem; padding-right: 1.5rem; } }
@media (min-width: 1024px) { .wrap { padding-left: 2rem; padding-right: 2rem; } }

/* ---------- Header ---------- */
header#site-header {
    position: sticky;
    top: 0;
    z-index: 40;
    width: 100%;
    background-color: rgba(255, 255, 255, 0.95);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    border-bottom: 1px solid var(--brand-border);
}

/* Three tracks with equal outer columns is what actually centres the logo on a
   phone: the burger and the action cluster are different widths, so a flex row
   would park the wordmark off-centre by the difference. */
.hdr {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    height: 4rem;
    gap: 0.5rem;
}
.hdr > .hdr-actions { justify-self: end; }

@media (min-width: 900px) {
    .hdr { display: flex; justify-content: space-between; gap: 1rem; }
}

.brand { display: inline-flex; align-items: center; }
.brand img {
    display: block;
    height: 1.75rem;
    width: auto;
    transition: opacity 0.15s ease;
}
.brand:hover img { opacity: 0.78; }
@media (min-width: 900px) { .brand img { height: 2rem; } }

.hdr-nav { display: none; align-items: center; gap: 0.25rem; }
@media (min-width: 900px) { .hdr-nav { display: flex; } }

.hdr-nav a {
    /* Two-word categories ("Creator Culture", "Travel & Diaspora") were breaking
       onto a second line and pushing the 4rem header out of shape. */
    white-space: nowrap;
    padding: 0.5rem 0.6rem;
    font-size: var(--text-base);
    font-weight: 500;
    letter-spacing: 0.01em;
    color: var(--brand-text);
    border-bottom: 2px solid transparent;
    transition: all 0.15s ease;
}
.hdr-nav a:hover { color: var(--brand-dark); border-bottom-color: var(--brand-border); }
.hdr-nav a.is-active { color: var(--brand-primary); border-bottom-color: var(--brand-primary); font-weight: 600; }

/* Dropdown for a menu item that has children. No JS: :hover opens it for a
   mouse, :focus-within opens it when tabbed into. */
.hdr-drop { position: relative; display: inline-flex; }
.hdr-drop-menu {
    position: absolute;
    top: 100%;
    left: 0;
    display: none;
    flex-direction: column;
    min-width: 190px;
    padding: 0.35rem 0;
    background: var(--brand-white);
    border: 1px solid var(--brand-border);
    box-shadow: 0 12px 32px rgba(11, 18, 32, 0.12);
    z-index: 50;
}
.hdr-drop:hover .hdr-drop-menu,
.hdr-drop:focus-within .hdr-drop-menu { display: flex; }

.hdr-drop-menu a {
    padding: 0.5rem 0.9rem;
    font-family: var(--font-sans);
    font-size: var(--text-sm);
    color: var(--brand-text);
    white-space: nowrap;
}
.hdr-drop-menu a:hover { background: var(--brand-bg); color: var(--brand-primary); }

.hdr-actions { display: flex; align-items: center; gap: 0.4rem; }
@media (min-width: 900px) { .hdr-actions { gap: 0.75rem; } }

/* Log In / Logout / the signed-in name are the first things to go when the bar
   runs out of room — they are also in the burger drawer, while Join, Subscribe
   and search are not. 1024 rather than 900 because between those widths the nav
   itself already fills the row and Log In wrapped onto a second line. */
.hdr-desktop-only { display: none; }
@media (min-width: 1024px) { .hdr-desktop-only { display: inline-flex; align-items: center; } }

.hdr-search-btn { color: var(--brand-dark); }
.hdr-search-btn[aria-expanded="true"] {
    color: var(--brand-primary);
    background-color: rgba(226, 223, 250, 0.6);
}

.btn-join {
    display: inline-flex;
    align-items: center;
    padding: 0.5rem 0.9rem;
    font-family: var(--font-sans);
    font-size: var(--text-sm);
    font-weight: 700;
    white-space: nowrap;
    color: var(--brand-white);
    background-color: var(--brand-primary);
    border: 1px solid var(--brand-primary);
    transition: background-color 0.15s ease;
}
.btn-join:hover { background-color: var(--brand-primary-hover); border-color: var(--brand-primary-hover); }
@media (min-width: 900px) {
    /* Subscribe is the primary CTA once there is room for both, so Join steps
       back to an outline and stops competing with it. */
    .btn-join { color: var(--brand-dark); background-color: transparent; border-color: var(--brand-border); }
    .btn-join:hover { color: var(--brand-dark); background-color: transparent; border-color: var(--brand-dark); }
}

.icon-btn {
    padding: 0.6rem; background: none; border: none; cursor: pointer;
    color: var(--brand-text); border-radius: 999px; display: inline-flex;
    transition: all 0.2s ease;
}
.icon-btn:hover { color: var(--brand-primary); background-color: rgba(226, 223, 250, 0.5); }

.btn-subscribe {
    display: none; align-items: center; gap: 0.35rem;
    padding: 0.55rem 1rem; font-size: var(--text-xs); font-weight: 600;
    text-transform: uppercase; letter-spacing: 0.08em;
    background-color: var(--brand-primary); color: var(--brand-white); border: none;
    cursor: pointer; transition: background-color 0.2s ease;
}
@media (min-width: 640px) { .btn-subscribe { display: inline-flex; } }
.btn-subscribe:hover { background-color: var(--brand-primary-hover); }

/* .auth-menu wrapper is gone — its children sit directly in .hdr-actions now
   and carry .hdr-desktop-only, so there is one width rule instead of two. */
.hdr-actions .link { font-size: var(--text-base); color: var(--brand-text); font-weight: 500; white-space: nowrap; }
.hdr-actions .link:hover { color: var(--brand-primary); }
.btn-outline {
    padding: 0.45rem 0.9rem; border: 1px solid var(--brand-border);
    background: transparent; color: var(--brand-dark); font-weight: 600;
    font-size: var(--text-sm); cursor: pointer; transition: all 0.2s ease;
}
.btn-outline:hover { border-color: var(--brand-dark); }

/* Shared button + input utilities (used across inner pages) */
.btn {
    display: inline-flex; align-items: center; justify-content: center;
    padding: 0.55rem 1.2rem; font-weight: 600; font-size: var(--text-base);
    border: none; cursor: pointer; transition: background-color 0.2s ease, border-color 0.2s ease;
}
.btn-gold, .btn-green { background-color: var(--brand-primary); color: var(--brand-white); }
.btn-gold:hover, .btn-green:hover { background-color: var(--brand-primary-hover); }

.search-input {
    font-family: var(--font-sans); font-size: var(--text-md);
    padding: 0.6rem 1rem; border: 1px solid var(--brand-border);
    background-color: var(--brand-white); color: var(--brand-dark); outline: none;
    transition: border-color 0.2s ease;
}
.search-input:focus { border-color: var(--brand-primary); }

.hdr-burger { display: inline-flex; padding: 0.5rem; background: none; border: none; cursor: pointer; color: var(--brand-dark); }
@media (min-width: 900px) { .hdr-burger { display: none; } }

/* Mobile menu */
.hdr-mobile {
    display: none; border-bottom: 1px solid var(--brand-border);
    background: var(--brand-white); padding: 0.5rem 0 1.5rem;
}
.hdr-mobile.open { display: block; }
@media (min-width: 900px) { .hdr-mobile, .hdr-mobile.open { display: none; } }
.hdr-mobile a {
    display: block; padding: 0.75rem 1rem; font-size: var(--text-xl); font-weight: 600;
    color: var(--brand-dark); border-left: 4px solid transparent;
}
.hdr-mobile a:hover { color: var(--brand-primary); background: var(--brand-bg); border-left-color: var(--brand-primary); }

/* ---------- Main ---------- */
main#site-main {
    min-height: 70vh;
    max-width: 80rem;
    margin: 0 auto;
    padding: 2.5rem 1rem;
}
@media (min-width: 640px) { main#site-main { padding: 2.5rem 1.5rem; } }
@media (min-width: 1024px) { main#site-main { padding: 2.5rem 2rem; } }

.alert {
    padding: 1rem 1.5rem; margin-bottom: 1.5rem;
    font-size: var(--text-lg); border: 1px solid;
}
.alert-success { background-color: var(--brand-bg); color: var(--brand-primary-hover); border-color: var(--brand-border); }
.alert-danger { background-color: var(--status-danger-bg); color: var(--status-danger); border-color: var(--status-danger-border); }

/* ---------- Footer ---------- */
footer#site-footer {
    background-color: var(--brand-dark);
    color: var(--brand-muted-inverse);
    border-top: 1px solid rgba(226, 223, 250, 0.15);
    padding: 4rem 0 3rem;
    margin-top: 4rem;
}
.foot-grid {
    display: grid; grid-template-columns: 1fr; gap: 3rem; margin-bottom: 4rem;
}
@media (min-width: 768px) { .foot-grid { grid-template-columns: repeat(2, 1fr); } }
/* auto-fit rather than a fixed 4: the link columns come from the footer menu
   now, so there can be one or three of them, not always exactly two. With the
   default two columns this resolves to the same four tracks as before. */
@media (min-width: 1024px) { .foot-grid { grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); } }

.foot-brand {
    font-family: var(--font-serif); font-size: var(--text-5xl); font-weight: 900;
    color: var(--brand-white); display: block; margin-bottom: 1.25rem;
}
.foot-brand span { color: var(--brand-primary); }
.foot-brand:hover { color: var(--brand-primary); }
.foot-desc { font-size: var(--text-base); line-height: 1.7; color: var(--brand-muted-inverse); }

.foot-socials { display: flex; gap: 1rem; margin-top: 1.5rem; }
.foot-socials a {
    padding: 0.5rem; background-color: var(--brand-divider-dark); color: var(--brand-muted-inverse); display: inline-flex;
    transition: all 0.2s ease;
}
.foot-socials a:hover { background-color: var(--brand-primary); color: var(--brand-white); }

.foot-title {
    font-family: var(--font-serif); color: var(--brand-white); font-size: var(--text-2xl);
    font-weight: 600; margin-bottom: 1.5rem; letter-spacing: 0.01em;
}
.foot-links { list-style: none; display: flex; flex-direction: column; gap: 0.9rem; font-size: var(--text-base); }
.foot-links a:hover { color: var(--brand-primary); }

.foot-sub-btn {
    display: block; width: 100%; padding: 0.65rem 1rem; margin-top: 1.25rem;
    background-color: var(--brand-primary); color: var(--brand-white); font-weight: 600;
    font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.1em;
    text-align: center; cursor: pointer; border: none; transition: background-color 0.2s ease;
}
.foot-sub-btn:hover { background-color: var(--brand-primary-hover); }

.foot-bottom {
    border-top: 1px solid var(--brand-divider-dark); padding-top: 2rem;
    display: flex; flex-direction: column; align-items: center; justify-content: space-between;
    gap: 1rem; font-size: var(--text-xs); color: var(--brand-muted);
}
@media (min-width: 768px) { .foot-bottom { flex-direction: row; } }
.foot-bottom .links { display: flex; gap: 1.5rem; }
.foot-bottom .links a:hover { color: var(--brand-primary); }

@keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
.animated-fade { animation: fadeIn 0.4s ease forwards; }

/* Shared homepage-widget helpers */
.homepage-section-title {
    font-family: var(--font-serif);
    font-size: var(--text-5xl);
    font-weight: 700;
    color: var(--brand-dark);
    letter-spacing: -0.01em;
    padding-bottom: 1rem;
    margin-bottom: 2rem;
    border-bottom: 1px solid var(--brand-border);
}
.homepage-empty {
    text-align: center; padding: 4rem 1.5rem;
    border: 1px dashed var(--brand-border);
    background-color: var(--brand-white);
}
.homepage-empty p { color: var(--brand-muted); font-size: var(--text-xl); }

/* ---------- Static pages (about / contact / advertise / policies) ---------- */
.sp-narrow { max-width: 56rem; margin: 0 auto; }
.sp-head { border-bottom: 1px solid var(--brand-border); padding-bottom: 1.5rem; margin-bottom: 2.5rem; }
.sp-kicker {
    display: inline-block; font-size: var(--text-xs); font-weight: 700; text-transform: uppercase;
    letter-spacing: 0.14em; color: var(--brand-primary);
    background-color: rgba(122, 90, 191, 0.06);
    border: 1px solid rgba(122, 90, 191, 0.18);
    padding: 0.3rem 0.65rem; margin-bottom: 1rem;
}
.sp-title {
    font-family: var(--font-serif); font-weight: 800; color: var(--brand-dark);
    font-size: var(--text-7xl); line-height: 1.08; letter-spacing: -0.02em; margin-bottom: 1rem;
}
@media (min-width: 640px) { .sp-title { font-size: var(--text-9xl); } }
.sp-lead { font-size: var(--text-2xl); line-height: 1.7; color: var(--brand-text); max-width: 48rem; }

.sp-prose { color: var(--brand-dark); font-size: var(--text-xl); line-height: 1.75; }
.sp-prose p { margin-bottom: 1.25rem; }
.sp-prose strong { color: var(--brand-dark); }

.sp-h2 {
    font-family: var(--font-serif); font-size: var(--text-5xl); font-weight: 700; color: var(--brand-dark);
    border-bottom: 1px solid var(--brand-border); padding-bottom: 0.6rem; margin-bottom: 1.5rem;
}

.sp-grid-2 { display: grid; grid-template-columns: 1fr; gap: 1.5rem; }
.sp-grid-3 { display: grid; grid-template-columns: 1fr; gap: 1.5rem; }
@media (min-width: 640px) { .sp-grid-2 { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 768px) { .sp-grid-3 { grid-template-columns: repeat(3, 1fr); } }

.sp-card { border: 1px solid var(--brand-border); background-color: var(--brand-white); padding: 1.5rem; }
.sp-card .sp-icon { color: var(--brand-primary); margin-bottom: 0.75rem; display: block; }
.sp-card h2, .sp-card h3, .sp-card h4 {
    font-family: var(--font-serif); font-weight: 700; color: var(--brand-dark);
    font-size: var(--text-xl); margin-bottom: 0.5rem;
}
.sp-card p { font-size: var(--text-base); line-height: 1.6; color: var(--brand-text); }
.sp-role { font-size: var(--text-xs); font-weight: 600; text-transform: uppercase; color: var(--brand-muted); letter-spacing: 0.04em; }

.sp-cta {
    background-color: rgba(122, 90, 191, 0.06); border: 1px solid rgba(122, 90, 191, 0.18);
    padding: 2rem; display: flex; flex-direction: column; gap: 1.5rem; align-items: flex-start;
}
@media (min-width: 640px) { .sp-cta { flex-direction: row; align-items: center; justify-content: space-between; } }
.sp-cta h2, .sp-cta h3 { font-family: var(--font-serif); font-size: var(--text-2xl); font-weight: 700; color: var(--brand-dark); margin-bottom: 0.35rem; }
.sp-cta p { font-size: var(--text-md); color: var(--brand-text); max-width: 36rem; }
.sp-cta-actions { display: flex; flex-wrap: wrap; gap: 0.75rem; }
.sp-btn-dark {
    display: inline-flex; align-items: center; gap: 0.4rem; padding: 0.65rem 1.25rem;
    background-color: var(--brand-dark); color: var(--brand-white); font-size: var(--text-xs); font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.08em; transition: background-color 0.2s ease;
}
.sp-btn-dark:hover { background-color: var(--brand-primary); }

/* Static-page grid layout (sidebar + content) */
.sp-cols { display: grid; grid-template-columns: 1fr; gap: 2.5rem; align-items: start; }
@media (min-width: 900px) { .sp-cols { grid-template-columns: 4fr 8fr; } }

/* Forms */
.sp-form { background-color: var(--brand-white); border: 1px solid var(--brand-border); padding: 1.75rem; }
.sp-form-grid { display: grid; grid-template-columns: 1fr; gap: 1.25rem; }
@media (min-width: 640px) { .sp-form-grid { grid-template-columns: repeat(2, 1fr); } .sp-col-2 { grid-column: 1 / -1; } }
.sp-field { display: flex; flex-direction: column; gap: 0.4rem; }
.sp-label { font-size: var(--text-xs); font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--brand-dark); }
.sp-input, .sp-select, .sp-textarea {
    width: 100%; font-family: var(--font-sans); font-size: var(--text-md); padding: 0.7rem 0.9rem;
    border: 1px solid var(--brand-border); background-color: var(--brand-white); color: var(--brand-dark); outline: none;
    transition: border-color 0.2s ease;
}
.sp-input:focus, .sp-select:focus, .sp-textarea:focus { border-color: var(--brand-primary); }
.sp-textarea { resize: vertical; min-height: 130px; }
.sp-check-row { display: flex; align-items: flex-start; gap: 0.6rem; }
.sp-check-row input { margin-top: 0.2rem; accent-color: var(--brand-primary); }
.sp-check-row label { font-size: var(--text-xs); color: var(--brand-text); line-height: 1.4; }
.sp-check-grid { display: grid; grid-template-columns: 1fr; gap: 0.6rem; }
@media (min-width: 640px) { .sp-check-grid { grid-template-columns: repeat(2, 1fr); } }
.sp-btn-submit {
    display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.8rem 1.5rem;
    background-color: var(--brand-primary); color: var(--brand-white); border: none; cursor: pointer;
    font-size: var(--text-xs); font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em;
    transition: background-color 0.2s ease;
}
.sp-btn-submit:hover { background-color: var(--brand-primary-hover); }
.sp-success { border: 1px solid var(--brand-border); background-color: var(--brand-white); padding: 2.5rem; text-align: center; }
.sp-success .sp-check-badge { display: inline-flex; padding: 0.75rem; background-color: rgba(122,90,191,0.1); color: var(--brand-primary); border-radius: 50%; margin-bottom: 1rem; }
.sp-success h2, .sp-success h3 { font-family: var(--font-serif); font-size: var(--text-5xl); font-weight: 700; color: var(--brand-dark); margin-bottom: 0.75rem; }
.sp-success p { font-size: var(--text-md); color: var(--brand-text); max-width: 30rem; margin: 0 auto 1rem; }

/* Policies tabs */
.sp-tab {
    display: block; width: 100%; text-align: left; padding: 0.8rem 1.25rem; margin-bottom: 0.6rem;
    font-size: var(--text-base); font-weight: 600; cursor: pointer; border: 1px solid var(--brand-border);
    background-color: var(--brand-white); color: var(--brand-text); transition: all 0.15s ease;
}
.sp-tab:hover { border-color: var(--brand-border); }
.sp-tab.is-active { background-color: var(--brand-dark); color: var(--brand-white); border-color: var(--brand-dark); }
.sp-panel { display: none; }
.sp-panel.is-active { display: block; animation: fadeIn 0.3s ease forwards; }
.sp-panel .sp-panel-lead { font-family: var(--font-serif); font-size: var(--text-2xl); font-weight: 700; color: var(--brand-dark); margin-bottom: 0.75rem; }
.sp-panel p { color: var(--brand-dark); font-size: var(--text-md); line-height: 1.7; margin-bottom: 1rem; }
.sp-panel ul { list-style: disc; padding-left: 1.25rem; margin-bottom: 1rem; }
.sp-panel li { font-size: var(--text-md); line-height: 1.6; margin-bottom: 0.5rem; color: var(--brand-dark); }

/* ---------------------------------------------------------------
   Form utilities added when the public forms were wired to the backend.
   --------------------------------------------------------------- */

/* Honeypot: off-screen rather than display:none, because some bots skip
   hidden fields but not positioned ones. */
.sp-hp {
    position: absolute;
    left: -9999px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

/* Visually hidden but announced by screen readers. */
.sp-sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.sp-form-errors {
    border: 1px solid var(--status-danger);
    background: var(--status-danger-bg);
    color: var(--status-danger);
    padding: 0.85rem 1rem;
    margin-bottom: 1.25rem;
    font-size: var(--text-base);
}

.sp-form-errors p { font-weight: 700; margin-bottom: 0.35rem; }
.sp-form-errors ul { list-style: disc; padding-left: 1.15rem; }

.hp-nl-error,
.mag-news-error { color: var(--status-danger-border); font-size: var(--text-sm); margin-top: 0.5rem; }

.hp-nl-done,
.mag-news-done { font-weight: 600; margin-top: 0.75rem; }

/* ---------------------------------------------------------------
   Accessibility baseline.
   --------------------------------------------------------------- */

/* Every input in this stylesheet sets outline:none and signals focus with a
   1px border colour shift, which keyboard users cannot see. This restores a
   real focus ring everywhere without touching each rule. */
:focus-visible {
    outline: 2px solid var(--brand-primary);
    outline-offset: 2px;
}

/* The marquee and pulse animations run continuously; on a low-end Android that
   is measurable battery drain. */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.001ms !important;
        scroll-behavior: auto !important;
    }
}

/* Footer legal text was var(--brand-muted) on var(--brand-dark) — 3.86:1, below the 4.5:1 AA
   threshold at this size. var(--brand-muted-inverse) is already used elsewhere in the footer. */
.foot-bottom { color: var(--brand-muted-inverse); }

/* Tap targets: these link rows were ~22px tall against a 44px guideline. */
.foot-links a,
.foot-bottom .links a { display: inline-block; padding: 0.5rem 0; }

/* ---------------------------------------------------------------
   Search panel — one entry point at every width, opened by the header icon.
   --------------------------------------------------------------- */
.hdr-search-panel {
    border-top: 1px solid var(--brand-border);
    background: var(--brand-white);
}
.hdr-search-panel[hidden] { display: none; }

.hdr-search-form {
    position: relative;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.85rem 0;
}

.hdr-search-icon { flex-shrink: 0; color: var(--brand-muted); }

.hdr-search-form input {
    flex: 1;
    min-width: 0;
    font-family: var(--font-sans);
    font-size: var(--text-lg);
    padding: 0.55rem 0;
    border: 0;
    background: transparent;
    color: var(--brand-dark);
    outline: none;
}
/* Chrome adds its own clear affordance that overlaps the Esc button. */
.hdr-search-form input::-webkit-search-cancel-button { display: none; }

.hdr-search-close {
    flex-shrink: 0;
    padding: 0.35rem 0.6rem;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    color: var(--brand-muted);
    background: transparent;
    border: 1px solid var(--brand-border);
    cursor: pointer;
}
.hdr-search-close:hover { color: var(--brand-dark); border-color: var(--brand-dark); }

.hdr-search-results { padding-bottom: 0.5rem; }
.hdr-search-results:empty { display: none; }

.hdr-result {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    padding: 0.6rem 0.5rem;
    border-top: 1px solid var(--brand-border);
    color: var(--brand-dark);
}
.hdr-result:hover,
.hdr-result.is-active {
    background: var(--brand-bg);
    color: var(--brand-primary);
}

.hdr-result img {
    width: 56px;
    height: 42px;
    object-fit: cover;
    flex-shrink: 0;
    background: var(--brand-bg);
}
/* Placeholder keeps the row height identical when a post has no image, so the
   list does not jump as results stream in. */
.hdr-result .hdr-result-noimg { width: 56px; height: 42px; flex-shrink: 0; background: var(--brand-bg); }

.hdr-result-body { min-width: 0; }
.hdr-result-title {
    font-family: var(--font-serif);
    font-size: var(--text-base);
    font-weight: 700;
    line-height: 1.3;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.hdr-result-meta {
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    color: var(--brand-muted);
    margin-top: 0.15rem;
}

.hdr-search-status {
    padding: 0.9rem 0.5rem;
    font-family: var(--font-sans);
    font-size: var(--text-sm);
    color: var(--brand-muted);
    border-top: 1px solid var(--brand-border);
}

.hdr-search-all {
    display: block;
    padding: 0.75rem 0.5rem;
    font-family: var(--font-sans);
    font-size: var(--text-sm);
    font-weight: 700;
    color: var(--brand-primary);
    border-top: 1px solid var(--brand-border);
}
.hdr-search-all:hover { background: var(--brand-bg); }

/* ---------------------------------------------------------------
   Back to top
   --------------------------------------------------------------- */

/* Sentinel sits at the very top; the button appears once it scrolls away. */
#topSentinel {
    position: absolute;
    top: 0;
    height: 1px;
    width: 1px;
}

.back-to-top {
    position: fixed;
    right: 1rem;
    bottom: 1rem;
    z-index: 60;
    /* 44px minimum touch target. */
    width: 44px;
    height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--brand-dark);
    color: var(--brand-white);
    border: 1px solid var(--brand-border);
    opacity: 0;
    visibility: hidden;
    transform: translateY(0.5rem);
    transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s;
}

.back-to-top.is-visible {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
}

.back-to-top:hover { background: var(--brand-primary); }

@media (min-width: 640px) {
    .back-to-top { right: 1.5rem; bottom: 1.5rem; }
}

/* ---------------------------------------------------------------
   Press state. The design reference uses one system-wide active
   micro-interaction; there was none anywhere on the site before this.
   --------------------------------------------------------------- */
button:active,
.btn:active,
.btn-outline:active,
.btn-subscribe:active,
.sp-btn-submit:active,
.sp-btn-dark:active,
.back-to-top:active,
.page-link:active {
    transform: scale(0.96);
}

/* ---------------------------------------------------------------
   Long-form prose media.

   Imported WordPress bodies carry images up to 1920px wide, tables,
   and YouTube iframes. `.article-body` styled only p / h2 / h3 /
   blockquote, so a 1920px image sat in a 760px column and pushed the
   whole document 904px sideways on desktop and 1554px on a phone —
   every article scrolled horizontally.

   Scoped to both body classes so the next long-form template cannot
   miss it the way the article one did.
   --------------------------------------------------------------- */
.article-body,
.page-body {
    /* An unbroken URL in imported copy is its own overflow source. */
    overflow-wrap: break-word;
}

.article-body img,
.article-body video,
.article-body iframe,
.article-body embed,
.article-body object,
.page-body img,
.page-body video,
.page-body iframe {
    max-width: 100%;
}

/* height:auto overrides the width/height attributes WordPress writes
   into the tag — without it a constrained image keeps its original
   height and stretches. */
.article-body img,
.article-body video,
.page-body img,
.page-body video {
    height: auto;
    display: block;
}

.article-body figure,
.page-body figure {
    margin: 2rem 0;
    max-width: 100%;
}

.article-body figcaption,
.page-body figcaption {
    margin-top: 0.6rem;
    font-family: var(--font-sans);
    font-size: var(--text-base);
    line-height: 1.5;
    color: var(--brand-muted);
    text-align: center;
}

/* Embeds arrive with fixed pixel width/height attributes; aspect-ratio
   keeps them 16:9 at whatever width is actually available. */
.article-body iframe,
.page-body iframe {
    width: 100%;
    aspect-ratio: 16 / 9;
    height: auto;
    border: 0;
    display: block;
    margin: 2rem 0;
}

/* Players restored from the imported WordPress embed blocks. The figure
   carries the spacing so the iframe rule above can keep owning the shape. */
.article-body .post-embed,
.page-body .post-embed {
    margin: 2rem 0;
}

/* A Spotify player is a fixed-height bar, not a 16:9 video. Left on the
   video aspect it renders as a short widget stranded in a tall empty box. */
.article-body .post-embed--audio iframe,
.page-body .post-embed--audio iframe {
    aspect-ratio: auto;
    height: 152px;
    margin: 0;
}

/* A provider we do not embed — TikTok is the only one in the archive.
   Better a visible link than a naked URL sitting in the prose. */
.article-body .post-embed-link,
.page-body .post-embed-link {
    margin: 2rem 0;
    word-break: break-all;
}

/* A table cannot scroll inside itself. display:block turns it into its
   own scroll container so a wide price table stays on the page instead
   of widening the document. */
.article-body table,
.page-body table {
    display: block;
    width: 100%;
    max-width: 100%;
    overflow-x: auto;
    border-collapse: collapse;
    margin: 2rem 0;
    font-size: var(--text-lg);
}

.article-body th,
.article-body td,
.page-body th,
.page-body td {
    padding: 0.65rem 0.9rem;
    border: 1px solid var(--brand-border);
    text-align: left;
    vertical-align: top;
}

.article-body th,
.page-body th {
    background: var(--brand-bg);
    font-family: var(--font-sans);
    font-weight: 700;
    white-space: nowrap;
}

.article-body ul,
.article-body ol {
    margin: 0 0 1.6rem 1.4rem;
}

.article-body li {
    margin-bottom: 0.5rem;
}

.article-body hr {
    border: 0;
    border-top: 1px solid var(--brand-border);
    margin: 2.5rem 0;
}

.article-body a {
    color: var(--brand-primary);
    text-decoration: underline;
    text-underline-offset: 0.15em;
}
.article-body a:hover { color: var(--brand-primary-hover); }

/* WordPress alignment classes. Without these, alignleft/alignright do
   nothing and the caption block loses its width constraint. */
.article-body .aligncenter,
.page-body .aligncenter {
    margin-left: auto;
    margin-right: auto;
}

.article-body .alignleft,
.article-body .alignright {
    float: none;
    margin-left: auto;
    margin-right: auto;
}

@media (min-width: 768px) {
    .article-body .alignleft { float: left; margin: 0.4rem 1.6rem 1.2rem 0; max-width: 50%; }
    .article-body .alignright { float: right; margin: 0.4rem 0 1.2rem 1.6rem; max-width: 50%; }
}

.article-body .wp-caption,
.article-body .wp-block-image {
    max-width: 100%;
}

/* Headings must be able to break.

   An imported title joined entirely by non-breaking spaces laid out as one
   966px word inside a 350px column and scrolled the whole document 600px
   sideways on a phone. The data is normalised on import now, but a long URL or
   hashtag in a headline would do the same, so the wrap is enforced here too. */
h1, h2, h3, h4, h5, h6 {
    overflow-wrap: break-word;
}

/* ── Article cards ──────────────────────────────────────────────────────
   The shared <x-public.article-card> component. These rules lived inside
   the style block of four separate page views, so the component only
   rendered correctly on pages that happened to carry a copy — and the
   copies had already drifted apart. Using the card anywhere new produced
   an unstyled block. One definition here, one component there. */
.article-card {
    background-color: var(--brand-white);
    border: 1px solid var(--brand-border);
    border-radius: var(--radius-lg);
    overflow: hidden;
    box-shadow: var(--shadow-sm);
    display: flex;
    flex-direction: column;
    transition: transform 0.6s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.6s ease;
}

.article-card:hover {
    transform: translateY(-4px);
    box-shadow: var(--shadow-md);
}

.card-img-container {
    position: relative;
    height: 220px;
    overflow: hidden;
    background: var(--brand-img-placeholder);
}

.card-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
}

.article-card:hover .card-img {
    transform: scale(1.04);
}

.card-content {
    padding: 1.8rem;
    display: flex;
    flex-direction: column;
    flex-grow: 1;
}

.card-title {
    font-size: var(--text-4xl);
    margin-bottom: 0.8rem;
}

.card-title a {
    position: relative;
    transition: color 0.3s ease;
}

.card-title a::after {
    content: '';
    position: absolute;
    width: 100%;
    transform: scaleX(0);
    height: 1.5px;
    bottom: -2px;
    left: 0;
    background-color: var(--brand-primary);
    transform-origin: bottom right;
    transition: transform 0.25s ease-out;
}

.article-card:hover .card-title a::after {
    transform: scaleX(1);
    transform-origin: bottom left;
}

.article-card:hover .card-title a {
    color: var(--brand-primary);
}

.card-excerpt {
    color: var(--brand-muted);
    font-size: var(--text-lg);
    margin-bottom: 1.5rem;
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.meta-row {
    display: flex;
    align-items: center;
    gap: 1rem;
    font-size: var(--text-md);
    color: var(--brand-muted);
    border-top: 1px solid var(--brand-border);
    padding-top: 1.5rem;
    margin-top: auto;
}

.author-img {
    width: 36px;
    height: 36px;
    border-radius: 50%;
    object-fit: cover;
    background-color: var(--brand-border);
}

/* ── Image gallery ──────────────────────────────────────────────────────
   A run of adjacent images in a body becomes one slider. Scroll-snap, so the
   swipe is the browser's own and the buttons only nudge scrollLeft — there is
   no carousel library here any more than anywhere else on this site.

   The in-body table-of-contents styles used to live here. The list is now
   rendered once, in the rail (.rail-toc, in the article view), because the
   body copy and the rail copy were the same twenty links on one page. */
.gallery {
    position: relative;
    margin: 2rem 0;
}

.gallery-track {
    display: flex;
    gap: 0.5rem;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scroll-behavior: smooth;
    /* The scrollbar is redundant beside the buttons and the counter. */
    scrollbar-width: none;
}
.gallery-track::-webkit-scrollbar { display: none; }

.gallery-slide {
    flex: 0 0 100%;
    scroll-snap-align: center;
    min-width: 0;
}

/* The figure inside keeps its own caption styling; only the box changes. */
.gallery-slide figure,
.gallery-slide p { margin: 0; }
.gallery-slide img {
    width: 100%;
    height: auto;
    display: block;
    background-color: var(--brand-img-placeholder);
}

/* Hidden until the script claims the gallery. Without JavaScript the track is
   still a swipeable row of the same images; a visible arrow that does nothing
   is worse than no arrow. */
.gallery-nav, .gallery-count { display: none; }
.gallery.is-live .gallery-nav { display: flex; }
.gallery.is-live .gallery-count { display: block; }

.gallery-nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    background: var(--brand-white);
    color: var(--brand-dark);
    border: 1px solid var(--brand-border);
    border-radius: 999px;
    box-shadow: var(--shadow-sm);
    cursor: pointer;
    opacity: 0.9;
}
.gallery-nav:hover { opacity: 1; color: var(--brand-primary); }
.gallery-nav--prev { left: 0.5rem; }
.gallery-nav--next { right: 0.5rem; }

/* Hidden when there is nowhere to go, set by the script. Wins over the
   .is-live rule above because [hidden] is the more specific selector. */
.gallery.is-live .gallery-nav[hidden] { display: none; }

.gallery-count {
    margin-top: 0.5rem;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    color: var(--brand-muted);
    text-align: center;
}

/* Anchored headings should not land under the sticky header. */
.article-body h2[id],
.article-body h3[id] { scroll-margin-top: 5.5rem; }

/* ── Ad slots ───────────────────────────────────────────────────────────
   Direct-sold placements. Labelled, because an ad that is not obviously an
   ad is the kind readers stop trusting a publication over. */
.ad-slot {
    display: block;
    margin: 2.5rem 0;
    text-align: center;
}

.ad-slot__label {
    display: block;
    margin-bottom: 0.4rem;
    font-family: var(--font-mono);
    font-size: var(--text-sm, 0.75rem);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--brand-muted);
}

.ad-slot img {
    display: block;
    width: 100%;
    height: auto;
    margin: 0 auto;
    border: 1px solid var(--brand-border);
}

/* In the reading column the ad sits between paragraphs, so it needs the
   separation a paragraph gap does not give it. */
.article-body .ad-slot {
    margin: 2.5rem auto;
    padding: 1rem 0;
    border-top: 1px solid var(--brand-border);
    border-bottom: 1px solid var(--brand-border);
    max-width: 640px;
}

.ad-slot--sidebar { margin: 0; }

/* Affiliate disclosure.
   Above the body, because "clear and conspicuous" means before the reader
   meets the link rather than in a footer under the comments. Quiet on
   purpose — it is a statement of fact, not a warning, and a loud banner over
   every travel guide would read as one. */
.affiliate-disclosure {
    display: block;
    margin: 0 0 2rem;
    padding: 0.85rem 1.1rem;
    border-left: 3px solid var(--brand-border);
    background: var(--brand-bg);
    font-family: var(--font-sans);
    font-size: var(--text-md);
    line-height: 1.6;
    color: var(--brand-muted);
}

/* GetYourGuide activities widget.
   The height is reserved because the widget arrives in an iframe after the
   page has painted — unreserved, it shoves the rest of the article down as
   it loads, which is the same Core Web Vitals penalty an unsized ad causes.
   A minimum, not a fixed height: three cards that come back taller grow. */
.gyg-widget {
    margin: 2.5rem 0;
    min-height: 420px;
}

.gyg-widget__label {
    display: block;
    margin-bottom: 0.6rem;
    font-family: var(--font-mono);
    font-size: var(--text-sm, 0.75rem);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--brand-muted);
}

/* One column on a phone, so the cards stack rather than sit in a squeezed
   row — the widget is responsive, this only stops the reserved box being
   taller than what lands in it. */
@media (max-width: 640px) {
    .gyg-widget { min-height: 640px; }
}

/* The Airalo offer block.
   Reads as an aside rather than an ad — it is a discount the reader gets,
   and dressing it as advertising would make them scroll past the one thing
   on the page that saves them money. */
.airalo-offer {
    margin: 2.5rem 0;
    padding: 1.4rem 1.5rem;
    border: 1px solid var(--brand-border);
    border-left: 3px solid var(--brand-primary);
    border-radius: var(--radius-md);
    background: var(--brand-bg);
    font-family: var(--font-sans);
    font-size: var(--text-lg);
    line-height: 1.6;
}

.airalo-offer p { margin: 0 0 0.75rem; }
.airalo-offer p:last-child { margin-bottom: 0; }

.airalo-offer__code-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.6rem;
}

.airalo-offer__label {
    font-size: var(--text-md);
    color: var(--brand-muted);
}

/* A button because it copies. Styled as a code chip because that is what it
   is — the reader has to get these characters into a checkout field. */
.airalo-offer__code {
    font-family: var(--font-mono);
    font-size: var(--text-xl);
    font-weight: 700;
    letter-spacing: 0.12em;
    /* 44px minimum touch target, same as every other tap target here. */
    min-height: 44px;
    padding: 0 1rem;
    border: 1px dashed var(--brand-primary);
    border-radius: var(--radius-md);
    background: var(--brand-white);
    color: var(--brand-primary);
    cursor: pointer;
}

.airalo-offer__code:hover,
.airalo-offer__code:focus-visible {
    background: var(--brand-primary);
    color: var(--brand-white);
}

.airalo-offer__action { font-size: var(--text-md); color: var(--brand-muted); }

/* The body sets a drop cap on its first paragraph. This sits above that
   paragraph but is not one, so the cap is unaffected — noted because it is
   the kind of thing a later refactor breaks silently. */

/* AdSense backfill.
   The unit arrives after the page has painted, so its height has to be
   reserved or every ad shifts the article down as it loads. Layout shift is a
   Core Web Vitals penalty, which costs ranking, which costs traffic — more
   than the ad earns back. These are minimums, not fixed heights: a responsive
   unit that comes back taller simply grows. */
.ad-slot--adsense .adsbygoogle {
    display: block;
    min-height: 280px;
}

/* Narrower column, so the unit that fits is shorter. */
.ad-slot--sidebar.ad-slot--adsense .adsbygoogle { min-height: 250px; }

/* A phone gets the full width and taller inventory. */
@media (max-width: 640px) {
    .ad-slot--adsense .adsbygoogle { min-height: 250px; }
}

/* ── Sign in and register ───────────────────────────────────────────────
   Two panels: the form, and the reason to sign up. These four screens
   shipped as raw unstyled HTML, which reads as a broken site at the exact
   moment somebody is deciding whether to trust it with a password. */
.auth-body {
    min-height: 100vh;
    margin: 0;
    background: var(--brand-white);
    font-family: var(--font-sans);
    color: var(--brand-text);
}

.auth-split { min-height: 100vh; }

/* One column until there is genuinely room for two. Below this the showcase
   is hidden rather than stacked — a phone should open onto the form, not
   scroll past three articles to reach it. */
@media (min-width: 900px) {
    .auth-split {
        display: grid;
        grid-template-columns: minmax(0, 30rem) minmax(0, 1fr);
    }
}

.auth-panel {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 2.5rem 1.25rem;
}

.auth-shell { width: 100%; max-width: 24rem; }

.auth-brand { display: inline-block; margin-bottom: 2.5rem; }
.auth-brand img { display: block; height: 2rem; width: auto; }

.auth-title {
    margin: 0 0 0.5rem;
    font-family: var(--font-serif);
    font-size: var(--text-7xl);
    font-weight: 900;
    line-height: 1.15;
    letter-spacing: -0.01em;
    color: var(--brand-dark);
}

.auth-intro {
    margin: 0 0 1.75rem;
    font-size: var(--text-lg);
    line-height: 1.55;
    color: var(--brand-muted);
}

.auth-notice,
.auth-errors {
    margin-bottom: 1.25rem;
    padding: 0.75rem 0.9rem;
    border-radius: var(--radius-md);
    font-size: var(--text-md);
    line-height: 1.5;
}

.auth-notice {
    border: 1px solid var(--status-success);
    background: var(--status-success-bg);
    color: var(--brand-text);
}

.auth-errors {
    border: 1px solid var(--status-danger-border);
    background: var(--status-danger-bg);
    color: var(--status-danger);
}

.auth-errors p { margin: 0; }
.auth-errors p + p { margin-top: 0.3rem; }

.auth-form { display: flex; flex-direction: column; gap: 1.1rem; }

.auth-field { display: flex; flex-direction: column; gap: 0.4rem; }

.auth-field label,
.auth-label-row label {
    font-size: var(--text-md);
    font-weight: 600;
    color: var(--brand-text);
}

.auth-label-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
}

.auth-label-row a { font-size: var(--text-base); color: var(--brand-primary); font-weight: 600; }

.auth-field input {
    width: 100%;
    /* 46px, at or above the 44px minimum tap target used site-wide. */
    min-height: 46px;
    padding: 0 0.9rem;
    border: 1px solid var(--brand-border);
    border-radius: var(--radius-md);
    background: var(--brand-white);
    font-family: var(--font-sans);
    font-size: var(--text-lg);
    color: var(--brand-text);
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.auth-field input::placeholder { color: var(--brand-muted); }

.auth-field input:focus-visible {
    outline: none;
    border-color: var(--brand-primary);
    box-shadow: 0 0 0 3px var(--brand-bg);
}

/* Set by the views on a field the server rejected, so the message in the
   block above has something to point at. */
.auth-field input[aria-invalid="true"] { border-color: var(--status-danger); }

.auth-hint { font-size: var(--text-base); color: var(--brand-muted); }

.auth-check {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: var(--text-md);
    color: var(--brand-muted);
    cursor: pointer;
}

.auth-check input { width: 16px; height: 16px; accent-color: var(--brand-primary); }

.auth-submit {
    min-height: 48px;
    margin-top: 0.35rem;
    border: none;
    border-radius: var(--radius-md);
    background: var(--brand-primary);
    color: var(--brand-white);
    font-family: var(--font-sans);
    font-size: var(--text-lg);
    font-weight: 700;
    letter-spacing: 0.01em;
    cursor: pointer;
    transition: background 0.15s ease;
}

.auth-submit:hover,
.auth-submit:focus-visible { background: var(--brand-primary-hover); }

/* Providers below the email form, not above it.
   Most readers here will not have a linked provider yet, so the primary path
   stays first — the buttons are the alternative, which is what the "or"
   divider is announcing. */
.auth-social { display: flex; flex-direction: column; gap: 0.6rem; }

.auth-social__button {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.65rem;
    min-height: 48px;
    padding: 0 1rem;
    border: 1px solid var(--brand-border);
    border-radius: var(--radius-md);
    background: var(--brand-white);
    font-size: var(--text-md);
    font-weight: 600;
    color: var(--brand-text);
    text-decoration: none;
    transition: border-color 0.15s ease, background 0.15s ease;
}

.auth-social__button svg { flex-shrink: 0; }

.auth-social__button:hover,
.auth-social__button:focus-visible {
    border-color: var(--brand-primary);
    background: var(--brand-bg);
}

.auth-divider {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    margin: 1.4rem 0;
    font-size: var(--text-xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--brand-muted);
}

.auth-divider::before,
.auth-divider::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--brand-border);
}

.auth-footer {
    margin: 1.5rem 0 0;
    font-size: var(--text-md);
    color: var(--brand-muted);
}

.auth-footer a { color: var(--brand-primary); font-weight: 700; }

.auth-legal {
    margin: 1.75rem 0 0;
    font-size: var(--text-base);
    line-height: 1.55;
    color: var(--brand-muted);
}

.auth-legal a { color: var(--brand-muted); text-decoration: underline; }

.auth-back {
    display: inline-block;
    margin-top: 1.5rem;
    font-size: var(--text-md);
    color: var(--brand-muted);
    text-decoration: none;
}

.auth-back:hover { color: var(--brand-primary); }

/* The showcase.
   Real published articles rather than a stock illustration — on a
   publication the honest version of a marketing panel is the work itself. */
.auth-showcase { display: none; }

@media (min-width: 900px) {
    .auth-showcase {
        display: flex;
        align-items: center;
        padding: 3rem;
        background: var(--brand-dark);
        color: var(--brand-white);
    }
}

.auth-showcase__inner { max-width: 30rem; }

.auth-showcase__kicker {
    margin: 0 0 1rem;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--brand-muted-inverse);
}

.auth-showcase__title {
    margin: 0 0 2.5rem;
    font-family: var(--font-serif);
    font-size: var(--text-8xl);
    font-weight: 900;
    line-height: 1.1;
    letter-spacing: -0.01em;
    color: var(--brand-white);
}

.auth-showcase__list { list-style: none; margin: 0; padding: 0; }

.auth-showcase__list li + li {
    margin-top: 1.1rem;
    padding-top: 1.1rem;
    border-top: 1px solid var(--brand-divider-dark);
}

/* Showcase rows: thumbnail beside the headline.
   Flex rather than a two-column grid because the thumbnail is optional — a
   post with no featured image gives its whole row to the text instead of
   leaving a dead column where the picture would have been. */
.auth-showcase__list a {
    display: flex;
    align-items: center;
    gap: 0.9rem;
    text-decoration: none;
}

.auth-showcase__thumb {
    flex: 0 0 4.5rem;
    /* Reserved before the image loads, so the panel does not reflow. */
    height: 4.5rem;
    overflow: hidden;
    border-radius: var(--radius-md);
    background: var(--brand-img-placeholder);
}

.auth-showcase__thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    transition: transform 0.25s ease;
}

.auth-showcase__list a:hover .auth-showcase__thumb img { transform: scale(1.05); }

.auth-showcase__text {
    display: grid;
    gap: 0.25rem;
    min-width: 0;
}

.auth-showcase__cat {
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--brand-accent);
}

.auth-showcase__headline {
    font-family: var(--font-serif);
    font-size: var(--text-4xl);
    font-weight: 700;
    line-height: 1.3;
    color: var(--brand-white);
}

.auth-showcase__list a:hover .auth-showcase__headline { color: var(--brand-accent); }

.auth-showcase__date {
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    color: var(--brand-muted-inverse);
}

/* ── Reader avatars and profile ─────────────────────────────────────────
   One component for the picture, because four hand-rolled versions is how
   three of them end up square when one is round. */
.avatar {
    display: inline-block;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
    background: var(--brand-border);
}

/* No picture: the initial, in brand colour. A letter reads as a person
   without pretending to be a photograph, which a generic silhouette does. */
.avatar--initial {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--brand-primary);
    color: var(--brand-white);
    font-family: var(--font-sans);
    font-weight: 700;
    line-height: 1;
}

.profile-identity {
    display: flex;
    align-items: center;
    gap: 1rem;
    margin-bottom: 2rem;
    padding-bottom: 1.5rem;
    border-bottom: 1px solid var(--brand-border);
}

.profile-identity__name {
    font-family: var(--font-serif);
    font-size: var(--text-4xl);
    font-weight: 700;
    color: var(--brand-text);
}

.profile-identity__meta {
    margin-top: 0.2rem;
    font-size: var(--text-md);
    color: var(--brand-muted);
}

/* What this reader has said. A comment scrolls away and there is no other
   route back to it, which makes this the one thing an account here produces
   that is not visible anywhere else. */
.profile-comments { list-style: none; margin: 0; padding: 0; }

.profile-comments li + li {
    margin-top: 1rem;
    padding-top: 1rem;
    border-top: 1px solid var(--brand-border);
}

.profile-comments__on {
    display: block;
    font-size: var(--text-base);
    color: var(--brand-muted);
    margin-bottom: 0.25rem;
}

.profile-comments__on a { color: var(--brand-primary); font-weight: 600; }

.profile-comments__body {
    font-size: var(--text-md);
    line-height: 1.55;
    color: var(--brand-text);
    /* Two lines: this is a way back to the thread, not the thread itself. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.profile-comments__date {
    display: block;
    margin-top: 0.3rem;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    color: var(--brand-muted);
}

@media (max-width: 640px) {
    .profile-identity { gap: 0.75rem; }
    .profile-identity__name { font-size: var(--text-3xl); }
}

/* =========================================================================
   Reader dashboard (/account/*)

   Its own shell — topbar + sidebar — rather than the site header, because
   once you are signed in these are the pages of an app, not articles. Same
   token vocabulary as the rest of the file; nothing new is defined here.
   ========================================================================= */

.dash-body { background: var(--brand-bg); }

.dash-top {
    position: sticky;
    top: 0;
    z-index: 40;
    display: flex;
    align-items: center;
    gap: 0.75rem;
    height: 60px;
    padding: 0 1rem;
    background: var(--brand-white);
    border-bottom: 1px solid var(--brand-border);
}

.dash-burger {
    display: inline-flex;
    align-items: center;
    background: none;
    border: 0;
    color: var(--brand-text);
    cursor: pointer;
    padding: 0.25rem;
}
@media (min-width: 900px) { .dash-burger { display: none; } }

.dash-brand img { height: 30px; width: auto; display: block; }

.dash-top-actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-left: auto;
}

.dash-icon-btn {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    color: var(--brand-muted);
    border: 1px solid var(--brand-border);
    border-radius: var(--radius-md);
}
.dash-icon-btn:hover { color: var(--brand-primary); border-color: var(--brand-primary); }

.dash-dot {
    position: absolute;
    top: 6px;
    right: 6px;
    width: 7px;
    height: 7px;
    border-radius: 999px;
    background: var(--brand-accent);
}

.dash-me { display: flex; align-items: center; gap: 0.5rem; }
.dash-me-name { display: none; font-size: var(--text-sm); font-weight: 600; }
@media (min-width: 640px) { .dash-me-name { display: inline; } }

.dash-signout {
    font-family: var(--font-sans);
    font-size: var(--text-sm);
    font-weight: 600;
    padding: 0.4rem 0.75rem;
    background: none;
    color: var(--brand-muted);
    border: 1px solid var(--brand-border);
    border-radius: var(--radius-md);
    cursor: pointer;
}
.dash-signout:hover { color: var(--status-danger); border-color: var(--status-danger); }

.dash-shell { display: flex; align-items: flex-start; }

.dash-sidebar {
    display: none;
    flex-direction: column;
    justify-content: space-between;
    width: 232px;
    flex: 0 0 232px;
    min-height: calc(100vh - 60px);
    padding: 1.25rem 0.75rem;
    background: var(--brand-white);
    border-right: 1px solid var(--brand-border);
    position: sticky;
    top: 60px;
}
.dash-sidebar.is-open {
    display: flex;
    position: fixed;
    top: 60px;
    left: 0;
    bottom: 0;
    z-index: 39;
    width: 82%;
    max-width: 300px;
    overflow-y: auto;
}
@media (min-width: 900px) { .dash-sidebar { display: flex; position: sticky; } }

.dash-sidebar nav { display: flex; flex-direction: column; gap: 0.15rem; }

.dash-link {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    padding: 0.6rem 0.75rem;
    font-size: var(--text-md);
    font-weight: 500;
    color: var(--brand-text);
    border-radius: var(--radius-md);
}
.dash-link:hover { background: var(--brand-bg); }
.dash-link.is-active { background: var(--brand-primary); color: var(--brand-white); font-weight: 600; }

.dash-badge {
    margin-left: auto;
    font-size: var(--text-xs);
    font-weight: 700;
    padding: 0.05rem 0.4rem;
    background: var(--brand-accent);
    color: var(--brand-white);
    border-radius: 999px;
}

.dash-sidebar-foot { border-top: 1px solid var(--brand-border); padding-top: 0.75rem; }

.dash-main {
    flex: 1;
    min-width: 0;
    padding: 1.5rem 1rem 4rem;
}
@media (min-width: 900px) { .dash-main { padding: 2rem 2rem 4rem; } }

.dash-head { margin-bottom: 1.5rem; }
.dash-head h1 { font-size: var(--text-7xl); }
.dash-head p { font-size: var(--text-lg); color: var(--brand-muted); margin-top: 0.25rem; }
.dash-head--split { display: flex; align-items: flex-end; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }

.dash-stats {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 0.75rem;
    margin-bottom: 1.5rem;
}

.dash-stat {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    padding: 1rem;
    background: var(--brand-white);
    border: 1px solid var(--brand-border);
    border-radius: var(--radius-lg);
}
.dash-stat:hover { border-color: var(--brand-primary); }
.dash-stat-n { font-family: var(--font-serif); font-size: var(--text-6xl); color: var(--brand-dark); line-height: 1; }
.dash-stat-l { font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.05em; color: var(--brand-muted); }

/* Two columns from the tablet breakpoint up. The grid is what puts the
   picture-and-name block beside the password block instead of below it. */
.dash-grid { display: grid; grid-template-columns: 1fr; gap: 1.25rem; align-items: start; }
@media (min-width: 900px) { .dash-grid { grid-template-columns: 1fr 1fr; } }

.dash-card {
    padding: 1.5rem;
    background: var(--brand-white);
    border: 1px solid var(--brand-border);
    border-radius: var(--radius-lg);
}
.dash-card h2 { font-size: var(--text-4xl); margin-bottom: 1rem; }

.dash-subhead { font-size: var(--text-3xl); margin: 1.5rem 0 0.75rem; }

.dash-identity {
    display: flex;
    align-items: center;
    gap: 0.9rem;
    padding-bottom: 1.25rem;
    margin-bottom: 1.25rem;
    border-bottom: 1px solid var(--brand-border);
}
.dash-identity span { display: block; }

.dash-row {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    padding: 0.75rem 0;
    border-top: 1px solid var(--brand-border);
}
.dash-row:hover .dash-row-title { color: var(--brand-primary); }
.dash-row-title { font-size: var(--text-lg); font-weight: 600; color: var(--brand-dark); }
.dash-row-meta { font-size: var(--text-xs); color: var(--brand-muted); }

.dash-list { list-style: none; display: flex; flex-direction: column; gap: 0.75rem; margin-bottom: 1.5rem; }

.dash-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 1rem 1.25rem;
    background: var(--brand-white);
    border: 1px solid var(--brand-border);
    border-radius: var(--radius-lg);
}
.dash-item--stack { flex-direction: column; align-items: flex-start; gap: 0.35rem; }
.dash-item.is-unread { border-left: 3px solid var(--brand-accent); }
.dash-item-body { min-width: 0; }
.dash-item-title { font-family: var(--font-serif); font-size: var(--text-4xl); font-weight: 700; color: var(--brand-dark); }
.dash-item-title a:hover { color: var(--brand-primary); }
.dash-comment { font-size: var(--text-lg); color: var(--brand-text); }
.dash-comment a { text-decoration: underline; }

.dash-pill {
    font-size: var(--text-xs);
    font-weight: 700;
    padding: 0.05rem 0.4rem;
    background: var(--brand-bg);
    color: var(--brand-muted);
    border-radius: 999px;
}

.dash-remove {
    font-family: var(--font-sans);
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--status-warning);
    background: none;
    border: 0;
    cursor: pointer;
    white-space: nowrap;
}

.dash-empty {
    padding: 2.5rem 1.5rem;
    text-align: center;
    font-size: var(--text-lg);
    color: var(--brand-muted);
    background: var(--brand-white);
    border: 1px dashed var(--brand-border);
    border-radius: var(--radius-lg);
}

.dash-field { margin-bottom: 1rem; display: flex; flex-direction: column; gap: 0.3rem; }
.dash-field label { font-size: var(--text-base); font-weight: 600; color: var(--brand-muted); }
.dash-field input, .dash-field textarea {
    font-family: var(--font-sans);
    font-size: var(--text-md);
    color: var(--brand-text);
    padding: 0.6rem;
    background: var(--brand-white);
    border: 1px solid var(--brand-border);
    border-radius: var(--radius-md);
    width: 100%;
}
.dash-field input:focus, .dash-field textarea:focus { outline: 2px solid var(--brand-primary); outline-offset: 1px; }
.dash-field textarea { resize: vertical; }

.dash-hint { font-size: var(--text-xs); color: var(--brand-muted); }
.dash-error { font-size: var(--text-xs); color: var(--status-danger); }

.dash-check { display: flex; align-items: center; gap: 0.5rem; font-size: var(--text-md); margin-bottom: 1.25rem; }

.dash-btn {
    font-family: var(--font-sans);
    font-size: var(--text-md);
    font-weight: 600;
    padding: 0.6rem 1.1rem;
    background: var(--brand-white);
    color: var(--brand-text);
    border: 1px solid var(--brand-border);
    border-radius: var(--radius-md);
    cursor: pointer;
}
.dash-btn--primary { width: 100%; background: var(--brand-primary); color: var(--brand-white); border-color: var(--brand-primary); }
.dash-btn--primary:hover { background: var(--brand-primary-hover); }

.dash-alert {
    padding: 0.85rem 1rem;
    margin-bottom: 1.25rem;
    font-size: var(--text-md);
    border-radius: var(--radius-md);
}
.dash-alert--ok { background: var(--status-success-bg); color: var(--status-success); }
.dash-alert--bad { background: var(--status-danger-bg); color: var(--status-danger); border: 1px solid var(--status-danger-border); }

/* A form button that has to read as a link — "sign in as someone else" is a
   POST because it clears session state, but it is not a primary action. */
.auth-linkish {
    font-family: var(--font-sans);
    font-size: var(--text-base);
    color: var(--brand-primary);
    background: none;
    border: 0;
    padding: 0;
    cursor: pointer;
    text-decoration: underline;
}
