/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Root
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

:root {
    /* Type-size scale */
    --font-size-5xl: 3.4rem;
    --font-size-4xl: 3rem;
    --font-size-3xl: 2.6rem;
    --font-size-2xl: 2.4rem;
    --font-size-xl: 2.2rem;
    --font-size-l: 1.8rem;
    --font-size: 1.5rem; /* 10px */
    --font-size-s: 1.2rem;
    --font-size-xs: 1rem;
    --font-size-2xs: 0.8rem;

    /* Layout — the canonical content width used by .container, and the
    narrower width every modal panel uses so dialogs read as focused
    overlays rather than full-page surfaces. */
    --container-max-width: 1200px;
    --modal-max-width: 800px;

    /* Spaces */
    --space-4xl: 4.5rem;
    --space-3xl: 3rem;
    --space-2xl: 2.5rem;
    --space-xl: 2rem;
    --space-l: 1.75rem;
    --space: 1.5rem;
    --space-s: 1rem;
    --space-xs: 0.75rem;
    --space-2xs: 0.5rem;
    --space-3xs: 0.25rem;
    --space-4xs: 0.125rem;

    /* Colour, shadow, type-family and corner-radius tokens — everything a
    theme restyles — live in theme.css alongside the [data-theme] blocks. */
}

/* NOTE
html is set to 62.5% so that all the REM measurements throughout Skeleton
are based on 10px sizing. So basically 1.5rem = 15px :) */
html {
    font-size: 62.5%;
}

body {
    font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */
    line-height: 1.6;
    font-weight: 400;
    font-family: var(--font-sans);
    color: var(--text);
    background: var(--page);
    margin: 0;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Fixed Grid sectioning
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

.container {
    position: relative;
    width: 100%;
    max-width: var(--container-max-width);
    margin: 0 auto;
    padding: 0 20px;
    box-sizing: border-box;
}
.column,
.columns {
    width: 100%;
    float: left;
    box-sizing: border-box;
}

/* For devices larger than 400px */
@media (min-width: 400px) {
    .container {
        width: 85%;
        padding: 0;
    }
}

/* For devices larger than 550px */
@media (min-width: 550px) {
    .container {
        width: 80%;
    }
    .column,
    .columns {
        margin-left: 4%;
    }
    .column:first-child,
    .columns:first-child {
        margin-left: 0;
    }

    .one.column,
    .one.columns {
        width: 4.66666666667%;
    }
    .two.columns {
        width: 13.3333333333%;
    }
    .three.columns {
        width: 22%;
    }
    .four.columns {
        width: 30.6666666667%;
    }
    .five.columns {
        width: 39.3333333333%;
    }
    .six.columns {
        width: 48%;
    }
    .seven.columns {
        width: 56.6666666667%;
    }
    .eight.columns {
        width: 65.3333333333%;
    }
    .nine.columns {
        width: 74%;
    }
    .ten.columns {
        width: 82.6666666667%;
    }
    .eleven.columns {
        width: 91.3333333333%;
    }
    .twelve.columns {
        width: 100%;
        margin-left: 0;
    }

    .one-third.column {
        width: 30.6666666667%;
    }
    .two-thirds.column {
        width: 65.3333333333%;
    }

    .one-half.column {
        width: 48%;
    }

    /* Offsets */
    .offset-by-one.column,
    .offset-by-one.columns {
        margin-left: 8.66666666667%;
    }
    .offset-by-two.column,
    .offset-by-two.columns {
        margin-left: 17.3333333333%;
    }
    .offset-by-three.column,
    .offset-by-three.columns {
        margin-left: 26%;
    }
    .offset-by-four.column,
    .offset-by-four.columns {
        margin-left: 34.6666666667%;
    }
    .offset-by-five.column,
    .offset-by-five.columns {
        margin-left: 43.3333333333%;
    }
    .offset-by-six.column,
    .offset-by-six.columns {
        margin-left: 52%;
    }
    .offset-by-seven.column,
    .offset-by-seven.columns {
        margin-left: 60.6666666667%;
    }
    .offset-by-eight.column,
    .offset-by-eight.columns {
        margin-left: 69.3333333333%;
    }
    .offset-by-nine.column,
    .offset-by-nine.columns {
        margin-left: 78%;
    }
    .offset-by-ten.column,
    .offset-by-ten.columns {
        margin-left: 86.6666666667%;
    }
    .offset-by-eleven.column,
    .offset-by-eleven.columns {
        margin-left: 95.3333333333%;
    }

    .offset-by-one-third.column,
    .offset-by-one-third.columns {
        margin-left: 34.6666666667%;
    }
    .offset-by-two-thirds.column,
    .offset-by-two-thirds.columns {
        margin-left: 69.3333333333%;
    }

    .offset-by-one-half.column,
    .offset-by-one-half.columns {
        margin-left: 52%;
    }
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Auto Grid sectioning
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
.grid {
    display: grid;
    grid-gap: 1.5rem;
}
.grid.fill {
    grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
    grid-auto-flow: dense;
}
.grid.fill.medium {
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
    row-gap: var(--space-2xs);
}
.grid.fill.small {
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    row-gap: var(--space-2xs);
}
@media screen and (max-width: 900px) {
    .grid.fill {
        grid-template-columns: repeat(auto-fill, minmax(245px, 1fr));
    }
}
.grid.fit {
    grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Section Styling
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
.control-row {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    gap: var(--space-l);
    background: var(--surface-sunken);
    border-radius: var(--radius-pill);
    padding: 0 var(--space);
}

/* Shared surface base — radius, contour border, padding, elevation. */
.surface,
.surface-accent,
.surface-tint,
.surface-alt,
.surface-scene,
.surface-recess,
.surface-destructive,
.surface-good,
.surface-warn,
.surface-passive,
.surface-active {
    border-radius: var(--radius);
    border: 1px solid var(--border);
    padding: 1rem;
    box-shadow: var(--shadow);
}

/* Surface variants. The base .surface rule (border/radius/padding/shadow)
is defined by the shared selector group above. */
.surface-accent {
    background: var(--accent);
}
.surface-tint {
    background: var(--tint);
}
.surface-scene {
    background: var(--surface);
}
.surface-recess {
    background: var(--surface-sunken);
    box-shadow: none;
    border: none;
}
/* Destructive matches the warn / good / active "status surface" pattern:
   a softened tint background paired with a saturated border so the variant
   reads as a coloured card rather than a solid block. The previous bg
   used the full --destructive hue with the default neutral border which
   visually collapsed the variant into a generic surface. */
.surface-destructive {
    border: 1px solid var(--destructive);
    background: var(--destructive-soft);
}
.surface-active {
    border: 1px solid var(--active);
    background: var(--active-soft);
}
.surface-passive {
    border: 1px solid var(--passive);
    background: var(--passive-soft);
}
.surface-warn {
    border: 1px solid var(--warn);
    background: var(--warn-soft);
}
.surface-good {
    border: 1px solid var(--good);
    background: var(--good-soft);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Content sectioning
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

h1,
h2,
h3,
h4,
h5,
h6 {
    margin-top: 0;
    margin-bottom: 2rem;
    font-weight: 300;
}
h1 {
    font-size: 4rem;
    line-height: 1.2;
    letter-spacing: -0.1rem;
}
h2 {
    font-size: 3.6rem;
    line-height: 1.25;
    letter-spacing: -0.1rem;
}
h3 {
    font-size: 3rem;
    line-height: 1.3;
    letter-spacing: -0.1rem;
}
h4 {
    font-size: 2.4rem;
    line-height: 1.35;
    letter-spacing: -0.08rem;
}
h5 {
    font-size: 1.8rem;
    line-height: 1.5;
    letter-spacing: -0.05rem;
}
h6 {
    font-size: 1.5rem;
    line-height: 1.6;
    letter-spacing: 0;
}

/* Larger than phablet */
@media (min-width: 550px) {
    h1 {
        font-size: 5rem;
    }
    h2 {
        font-size: 4.2rem;
    }
    h3 {
        font-size: 3.6rem;
    }
    h4 {
        font-size: 3rem;
    }
    h5 {
        font-size: 2.4rem;
    }
    h6 {
        font-size: 1.5rem;
    }
}

/* The header element's skin — the floating brand-bar look (width, height,
background, border, centring margin, corner radius and shadow) — now lives in
theme.css so each theme can restyle the bar that gives the site its character.
Only the bar's internal flex layout stays in site.css, on .site-header below. */

footer {
    padding: var(--space) calc(env(safe-area-inset-left, 0) + var(--space))
        calc(env(safe-area-inset-bottom, 0) + var(--space-4xl))
        calc(env(safe-area-inset-right, 0) + var(--space));
    background-color: var(--surface);
    margin-top: var(--space-4xl);
    z-index: 99;
    position: relative;
    text-align: center;
}

/* Site-wide footer (shared.SiteFooter), shown on every admin and public shell.
It overrides the generic footer card above: no surface fill and no centring — a
single full-width rule separates it from the page, then a row holds the
copyright on the left and the links area on the right. */
.site-footer {
    margin-top: calc(2 * var(--space-4xl));
    padding: 0;
    background-color: var(--surface-sunken);
    text-align: left;
    height: 210px;
}

/* The separator spans edge to edge: the footer itself carries no horizontal
padding, so the rule runs the full page width while the inner row pads itself. */
.site-footer__rule {
    margin: 0;
    border: 0;
    border-top: 1px solid var(--border);
}

/* Spaced row: copyright pinned left, links pushed right. The padding-top is the
"some space" below the rule; the bottom padding folds in the mobile safe-area
inset so the row clears a phone's home indicator. */
.site-footer__inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space);
    padding: var(--space-l) var(--space)
        calc(env(safe-area-inset-bottom, 0) + var(--space-2xl));
}

/* Copyright line: dimmed and a step smaller than body text, with the default
paragraph margin removed so it sits centred against the links opposite it. */
.site-footer__copyright {
    margin: 0;
    color: var(--text-dim);
    font-size: var(--font-size-s);
}

/* Right-hand links area — a flex row today holding only the home link, laid out
so additional links can join it later without markup changes. */
.site-footer__links {
    display: flex;
    gap: var(--space);
}

/* Footer links read as quiet chrome until hovered. */
.site-footer__link {
    color: var(--text-dim);
}
.site-footer__link:hover {
    color: var(--text);
}

.site-logo {
    display: block;
    line-height: 0;
    padding: var(--space-2xs) var(--space);
    text-align: center;
}
.site-logo img {
    height: 2.4rem;
    width: auto;
}

/* The bare structural elements (nav, main, article, section, aside, search,
address) intentionally carry no base styling — their appearance comes from the
component classes applied to them (.site-nav, .site-main, .page-content, …). */

/* Flex layout helper. Modifiers are standalone single-hyphen classes so
they no longer collide with the .left / .right text-align utilities. */
.flex-container {
    display: flex;
    gap: var(--space-xs);
    align-items: center;
}
.flex-container-compact {
    gap: var(--space-4xs);
}
.flex-container-vertical {
    justify-content: center;
    flex-direction: column;
}
.flex-container-spaced {
    justify-content: space-between;
}
.flex-container-wrapped {
    flex-flow: row wrap;
}
.flex-container-start {
    justify-content: flex-start;
    gap: 0;
}
.flex-container-end {
    justify-content: flex-end;
    gap: 0;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Content spacing
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
.spacer-4xl {
    margin: 0 auto;
    padding-top: var(--space-4xl);
    padding-bottom: var(--space-4xs);
}
.spacer-3xl {
    margin: 0 auto;
    padding-top: var(--space-3xl);
    padding-bottom: var(--space-3xl);
}
.spacer-2xl {
    margin: 0 auto;
    padding-top: var(--space-2xl);
    padding-bottom: var(--space-2xl);
}
.spacer-xl {
    margin: 0 auto;
    padding-top: var(--space-xl);
    padding-bottom: var(--space-xl);
}
.spacer-l {
    margin: 0 auto;
    padding-top: var(--space-l);
    padding-bottom: var(--space-l);
}
.spacer {
    margin: 0 auto;
    padding-top: var(--space);
    padding-bottom: var(--space);
}
.spacer-s {
    margin: 0 auto;
    padding-top: var(--space-s);
    padding-bottom: var(--space-s);
}
.spacer-xs {
    margin: 0 auto;
    padding-top: var(--space-xs);
    padding-bottom: var(--space-xs);
}
.spacer-2xs {
    margin: 0 auto;
    padding-top: var(--space-2xs);
    padding-bottom: var(--space-2xs);
}
.spacer-3xs {
    margin: 0 auto;
    padding-top: var(--space-3xs);
    padding-bottom: var(--space-3xs);
}
.spacer-4xs {
    margin: 0 auto;
    padding-top: var(--space-4xs);
    padding-bottom: var(--space-4xs);
}

button,
.button {
    margin-bottom: 1rem;
}

input,
textarea,
select,
fieldset {
    margin-bottom: 1.5rem;
}

pre,
blockquote,
dl,
figure,
table,
p,
ul,
ol,
form {
    margin-bottom: 2.5rem;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Text Styling
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

.label {
    font-size: var(--font-size-s);
}
.footnote {
    font-size: var(--font-size-xs);
}
.caption {
    font-size: var(--font-size-2xs);
}
.no-margin {
    margin: 0;
}
.no-wrap {
    white-space: nowrap;
}
.indent {
    margin-left: var(--space-s);
}
.no-padding {
    padding: 0;
}
.underline {
    text-decoration: underline;
}
.underline.tint {
    text-decoration-color: var(--tint);
}
.underline.accent {
    text-decoration-color: var(--accent);
}
.accent {
    color: var(--accent);
}
.tint {
    color: var(--tint);
}
.destructive {
    color: var(--destructive);
}
.dim {
    color: var(--text-dim);
}
.left {
    text-align: left;
}
.center {
    text-align: center;
}
.right {
    text-align: right;
}
.justify {
    text-align: justify;
}
.uppercase {
    text-transform: uppercase;
}
.disabled {
    pointer-events: none;
    cursor: none;
    opacity: 50%;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Text block content
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

p {
    margin-top: 0;
}

ul {
    list-style: circle inside;
}
ol {
    list-style: decimal inside;
}
ol,
ul {
    padding-left: 0;
    margin-top: 0;
}
ul ul,
ul ol,
ol ol,
ol ul {
    margin: 1.5rem 0 1.5rem 3rem;
    font-size: 90%;
}
li {
    margin-bottom: 1rem;
}

pre code {
    display: block;
    padding: 1rem 1.5rem;
    white-space: pre;
}

hr {
    margin-top: 2.4rem;
    margin-bottom: 2rem;
    border-width: 0;
    border-top: 1px solid var(--border);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Inline text semantics
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

a {
    color: var(--accent);
    text-decoration: none;
}
a:hover {
    filter: saturate(200%);
    cursor: pointer;
}
a.no-underline:hover {
    text-decoration: none;
}
a.no-decoration {
    color: inherit;
    text-decoration: none;
}
a.no-decoration:hover {
    text-decoration: none;
}

/* Inline semantic elements (strong, b, em, i, q, cite, s, mark, small, sub,
sup) use the browser's default rendering. */

code {
    padding: 0.2rem 0.5rem;
    margin: 0 0.2rem;
    font-size: 90%;
    white-space: nowrap;
    background: var(--surface-sunken);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

/* dfn, abbr, var, kbd, samp, u, data and time use default rendering. */

/* del / ins use default rendering. */

/* Embedded media (img, svg, video, canvas, audio, object, noscript) inherit UA
defaults; block-level sizing comes from the .block-image / .block-video
component classes rather than bare element rules. */

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Table content
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* Base table — fills the bounding container and is framed as a card
(rounded contour, 1px border, drop shadow) matching the .surface
family minus padding (cells own their own padding). border-collapse
must be 'separate' for the table's border-radius to take effect at
the corners; border-spacing: 0 keeps cells flush so row layout is
unchanged. overflow: hidden clips the header row's background fill
to the rounded top corners. Per-section table classes
(.assets__table, .pages__table, etc.) inherit this look and add
their own padding/density. */
table {
    width: 100%;
    border-collapse: separate;
    border-spacing: 0;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    overflow: hidden;
}

/* Suppress the last body row's bottom border so the table closes off
against its own rounded contour border without painting a redundant
horizontal rule one pixel above the curve. */
table tbody > tr:last-child > td {
    border-bottom: 0;
}
/* caption, col, thead, tbody, tfoot and tr carry no base rules; cell padding
and the row border are set on th/td below. */
/* Header cells — sit on the raised --surface tone so the heading row
reads as a separate band from the data rows below. */
th {
    background: var(--surface);
}

/* Shared cell box — tight even padding on all four sides (per-section
table classes can still override). Only a bottom border is drawn so
the row separation is purely horizontal and no vertical rule appears
between cells. */
th,
td {
    padding: var(--space-xs);
    text-align: left;
    border-bottom: 1px solid var(--border);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Buttons
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
.button,
button,
input[type="submit"],
input[type="reset"],
input[type="button"] {
    display: inline-block;
    height: 38px;
    padding: 0 16px;
    text-align: center;
    font-size: 11px;
    font-weight: 600;
    line-height: 38px;
    letter-spacing: 0.1rem;
    text-transform: uppercase;
    text-decoration: none;
    white-space: nowrap;
    border-radius: var(--radius);
    border: 1px solid var(--border);
    cursor: pointer;
    box-sizing: border-box;
}
.button:hover,
button:hover,
input[type="submit"]:hover,
input[type="reset"]:hover,
input[type="button"]:hover,
.button:focus,
button:focus,
input[type="submit"]:focus,
input[type="reset"]:focus,
input[type="button"]:focus {
    color: var(--text);
    filter: saturate(200%);
    outline: 0;
}
.button.button-primary,
button.button-primary,
input[type="submit"].button-primary,
input[type="reset"].button-primary,
input[type="button"].button-primary {
    color: var(--accent);
    background-color: var(--surface-sunken);
    border-color: var(--border);
}
.button.button-primary:hover,
button.button-primary:hover,
input[type="submit"].button-primary:hover,
input[type="reset"].button-primary:hover,
input[type="button"].button-primary:hover,
.button.button-primary:focus,
button.button-primary:focus,
input[type="submit"].button-primary:focus,
input[type="reset"].button-primary:focus,
input[type="button"].button-primary:focus {
    color: var(--text);
    filter: saturate(200%);
    background-color: var(--surface-sunken);
    border-color: var(--border);
}

/* Button Types — colour variants over the shared button base. The solid-fill
variants take --button-text so the label stays legible on the accent/tint/
destructive fill; the hover/focus states are listed too so the generic
`.button:hover` rule above cannot revert them to the low-contrast --text. The
borderless .button-alt keeps the body --text tone. */
.button-accent,
.button-tint,
.button-destructive,
.button-accent:hover,
.button-tint:hover,
.button-destructive:hover,
.button-accent:focus,
.button-tint:focus,
.button-destructive:focus {
    color: var(--button-text);
}
.button-alt {
    color: var(--text);
}
.button-accent {
    border: none;
    background: var(--accent);
}
.button-tint {
    border: none;
    background: var(--tint);
}
.button-destructive {
    border: none;
    background: var(--destructive);
}
.button-recess {
    border: none;
    background: var(--surface-sunken);
    box-shadow: none;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Forms
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* Placeholder text — dimmed role token so the hint stays legible against
the sunken input background in both themes. */
::placeholder {
    color: var(--text-dim);
    opacity: 1;
}

/* `textarea` and `input` elements that share a similar look and similar styling
capabilities */
/* - common styling */
textarea,
input[type="text"],
input[type="password"],
input[type="email"],
input[type="tel"],
input[type="number"],
input[type="search"],
input[type="url"],
input[type="date"],
input[type="datetime-local"],
input[type="month"],
input[type="week"],
input[type="time"] {
    width: 100%;
    max-width: 1180px;
    height: 38px;
    padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */
    color: inherit;
    background-color: var(--surface-sunken);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: none;
    box-sizing: border-box;
}

/* Removes awkward default styles on some inputs for iOS */
input[type="email"],
input[type="number"],
input[type="search"],
input[type="text"],
input[type="tel"],
input[type="url"],
input[type="password"],
textarea {
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
}

/* - individual styling */
textarea {
    min-height: 65px;
    padding-top: 6px;
    padding-bottom: 6px;
}

input[type="text"]:focus,
input[type="password"]:focus,
input[type="email"]:focus,
input[type="tel"]:focus,
input[type="number"]:focus,
input[type="search"]:focus,
input[type="url"]:focus,
input[type="date"]:focus,
input[type="datetime-local"]:focus,
input[type="month"]:focus,
input[type="week"]:focus,
input[type="time"]:focus,
textarea:focus,
select:focus {
    border: 1px solid var(--focus-ring);
    outline: 0;
}

label,
legend {
    display: block;
    margin-bottom: 0.5rem;
    font-weight: 600;
}

/* Stacked form labels — the title text sits on its own line above the
field, both flush-left. Labels that wrap a tick-box (checkbox / radio)
are excluded so the tick stays inline with its caption. */
label:not(:has(input[type="checkbox"])):not(:has(input[type="radio"])) {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-2xs);
}

label > .label-body {
    display: inline-block;
    margin-left: 0.5rem;
    font-weight: normal;
}

fieldset {
    padding: 0;
    border-width: 0;
}

/* The individual text-like input types share the common rule above and need no
per-type overrides. */

/* Tick boxes */
input[type="checkbox"],
input[type="radio"] {
    display: inline;
}

/* Select control — inherits text color from the page so it stays legible
under both themes (the UA default would otherwise be black). */
select {
    height: 38px;
    padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */
    color: inherit;
    background-color: var(--surface-sunken);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: none;
    box-sizing: border-box;
}
/* Drop-down option — same surface as the select, theme-aware text. Honored
by browsers that render options in-page (e.g. Firefox); native OS pickers
on macOS Safari ignore it. */
option {
    color: inherit;
    background-color: var(--surface-sunken);
}

button:disabled,
input[type="submit"]:disabled,
.button.disabled {
    pointer-events: none;
    cursor: none;
    opacity: 50%;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Interactive elements
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* Native <details>/<summary> accordion — no JS, CSP-clean. The disclosure
triangle is the browser-supplied list-item marker; we just tighten the
heading layout and add a hover hint. */
details {
    margin-bottom: var(--space);
}
summary {
    cursor: pointer;
    padding: var(--space-2xs) 0;
}
summary:hover {
    color: var(--accent);
}
/* Inline any heading inside a summary so the marker triangle and the
title sit on one line; reset the heading's own bottom margin so it
does not double up with the details' margin. Covers every heading
level because /admin/profile uses h4 sections and other accordions
use h2 (§16). */
summary > h2,
summary > h3,
summary > h4,
summary > h5,
summary > h6 {
    display: inline;
    margin: 0;
}
details[open] summary {
    margin-bottom: var(--space-s);
}

/* dialog / ::backdrop and the :focus / :focus-visible / [inert] hooks are left
to UA defaults; focus styling is applied per component (inputs, links,
buttons, summary). */

/* Self Clearing Goodness */
.container:after,
.row:after,
.u-cf {
    content: "";
    display: table;
    clear: both;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Modals
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* Overlay is visible whenever the server has rendered it; closing = HTMX
removes the node. No JS display toggle exists under the strict CSP. */
.modal {
    text-align: left;
    display: block;
    position: fixed;
    z-index: 100;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    overflow: auto;
    background-color: var(--page);
}

/* Modal panel. */
.modal-content {
    margin: 10% auto;
}

/* Modal close affordance. */
.modal-close {
    color: var(--text-dim);
    float: right;
    font-size: 28px;
    font-weight: bold;
}

.modal-close:hover,
.modal-close:focus {
    color: var(--text);
    text-decoration: none;
    cursor: pointer;
}

/* Responsive @media rules live alongside the components they adjust (the grid
at 400/550px, headings at 550px, .block-row at 550px, the nav at 750px, and the
editor/admin shells at 750px) rather than in a central breakpoint block. */

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Element Library (Blocks)
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* row — column count / gap / alignment are predefined classes because the
strict CSP forbids inline style; the Go renderer picks the class. */
.block-row {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space);
}
.block-row-cols-1 {
    grid-template-columns: 1fr;
}
.block-row-cols-2 {
    grid-template-columns: repeat(2, 1fr);
}
.block-row-cols-3 {
    grid-template-columns: repeat(3, 1fr);
}
.block-row-cols-4 {
    grid-template-columns: repeat(4, 1fr);
}
.block-row-gap-s {
    gap: var(--space-2xs);
}
.block-row-gap-m {
    gap: var(--space);
}
.block-row-gap-l {
    gap: var(--space-2xl);
}
.block-row-align-start {
    align-items: start;
}
.block-row-align-center {
    align-items: center;
}
.block-row-align-end {
    align-items: end;
}
@media (max-width: 550px) {
    .block-row-cols-2,
    .block-row-cols-3,
    .block-row-cols-4 {
        grid-template-columns: 1fr;
    }
}

/* text — prose wrapper; constrains measure for readability. */
.block-text {
    max-width: 70ch;
}

/* image — semantic size classes only; intrinsic width/height come from
server-emitted HTML attributes to prevent layout shift. */
.block-image {
    display: block;
    height: auto;
    border-radius: var(--radius);
}
.block-image-small {
    max-width: 25rem;
}
.block-image-medium {
    max-width: 50rem;
}
.block-image-full {
    max-width: 100%;
    width: 100%;
}

/* video_embed — responsive 16:9 frame for the allowlisted iframe. */
.block-video {
    position: relative;
    aspect-ratio: 16 / 9;
    width: 100%;
    border-radius: var(--radius);
    overflow: hidden;
}
.block-video iframe {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: 0;
}
.block-video-caption {
    margin-top: var(--space-2xs);
    color: var(--text-dim);
    font-size: var(--font-size-xs);
}

/* callout — variant tints reuse the status hue roles. */
.block-callout {
    border-left: 0.4rem solid var(--border);
    border-radius: var(--radius);
    padding: var(--space-s) var(--space);
    background: var(--surface-sunken);
}
.block-callout-info {
    border-left-color: var(--active);
    background: var(--active-soft);
}
.block-callout-warning {
    border-left-color: var(--warn);
    background: var(--warn-soft);
}
.block-callout-danger {
    border-left-color: var(--destructive);
    background: var(--destructive-soft);
}
.block-callout-success {
    border-left-color: var(--good);
    background: var(--good-soft);
}
.block-callout-title {
    margin: 0 0 var(--space-2xs) 0;
    font-weight: 600;
}
.block-callout-body {
    margin: 0;
}

/* button — content-block link styled as a button, aligned with the form
.button control. */
.block-button {
    display: inline-block;
    padding: 0 16px;
    height: 38px;
    line-height: 38px;
    border-radius: var(--radius);
    border: 1px solid var(--border);
    text-decoration: none;
    text-transform: uppercase;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.1rem;
    color: var(--text);
}
.block-button-primary {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--button-text);
}
.block-button-secondary {
    background: var(--tint);
    border-color: var(--tint);
    color: var(--button-text);
}
.block-button-ghost {
    background: transparent;
    color: var(--accent);
}
.block-button:hover {
    filter: saturate(200%);
}

/* Module-gating placeholder — shown in place of a gated element. */
.block-placeholder {
    border: 1px dashed var(--border);
    border-radius: var(--radius);
    padding: var(--space);
    background: var(--surface-sunken);
    color: var(--text-dim);
    text-align: center;
}

/* Public page shell — ordered block tree inside the existing .container. */
.page-content > * + * {
    margin-top: var(--space-l);
}
.page-title {
    margin-bottom: var(--space-2xs);
}
.page-meta {
    color: var(--text-dim);
    font-size: var(--font-size-s);
    margin-bottom: var(--space-xl);
}

/* Theme-switch control — issues the HTMX /theme request; styled like a
compact button. Behaviour is the plan-side handler. */
.theme-switch {
    display: inline-flex;
    align-items: center;
    gap: var(--space-3xs);
    height: 3rem;
    padding: 0 var(--space-s);
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    background: var(--surface);
    color: var(--text);
    font-size: var(--font-size-xs);
    cursor: pointer;
}
.theme-switch:hover {
    filter: saturate(150%);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Navigation Tree
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* Nested nav tree of arbitrary depth (follows the category hierarchy). */
.nav-tree,
.nav-tree ul {
    list-style: none;
    margin: 0;
    padding: 0;
}
.nav-tree ul {
    margin-left: var(--space);
    border-left: 1px solid var(--border);
    padding-left: var(--space-s);
}
.nav-tree-item {
    margin: 0;
}
.nav-tree-link {
    display: block;
    padding: var(--space-3xs) var(--space-2xs);
    color: var(--text);
    text-decoration: none;
    border-radius: var(--radius-s);
}
.nav-tree-link:hover {
    background: var(--surface-sunken);
}
.nav-tree-link-current {
    color: var(--accent);
    font-weight: 600;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Notifications
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* Dismissable notification — the close affordance is a <label> bound to a
sibling checkbox; :has(:checked) on the wrapper hides the whole element.
No JS, CSP-clean. A server-side re-render brings the notice back (the
checkbox is fresh in the new DOM), which matches the flash-message model.
Base rule is layout-only: tone comes from one of the .surface-good /
.surface-warn / .surface-destructive variants applied alongside. */
.notification {
    display: flex;
    align-items: center;
    gap: var(--space-s);
    margin-bottom: var(--space);
}
.notification:has(.notification__dismiss-input:checked) {
    display: none;
}
/* The dismiss input is a presentational checkbox driving :has(:checked);
the scoped selector beats the global input[type="checkbox"] { display:
inline } rule on specificity. */
.notification .notification__dismiss-input {
    display: none;
}
.notification__message {
    flex: 1;
    margin: 0;
}
.notification__dismiss {
    font-size: var(--font-size-l);
    line-height: 1;
    padding: 0 var(--space-2xs);
    cursor: pointer;
    user-select: none;
}
.notification__dismiss:hover {
    color: var(--text);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Site Header
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* Single horizontal bar: brand on the left, public site nav directly after
it (left-aligned), and the authenticated account nav pushed to the far right
by margin-left:auto. justify-content:flex-start keeps the brand+nav pair
welded together at the left edge regardless of how many account links the
session adds on the right. */
.site-header {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: var(--space);
    padding: 0 var(--space);
}
/* Brand link doubles as the home affordance — sized up and recoloured so it
reads as a logo rather than a regular nav link. */
.site-header__brand {
    font-size: var(--font-size-l);
    font-weight: 700;
    color: var(--text);
    text-decoration: none;
}
.site-header__brand:hover {
    color: var(--accent);
}
/* When a site logo is configured the brand anchor wraps this image instead of
the site-name text (shared.BrandLink, §17). A fixed height keeps the header bar
rhythm stable across logos of differing intrinsic sizes while width:auto
preserves each logo's aspect ratio. The one class serves the public header, the
admin header, and the full-screen mobile-nav bar so the logo sizing is uniform
everywhere the brand appears. */
.brand-logo {
    display: block;
    height: 3.2rem;
    width: auto;
}
/* Right-side group keeps nav links and the logout form on one row.
margin-left:auto pushes the whole account group to the right edge of the
header so the public .site-nav can sit left-aligned next to the brand
without being centred by space-between. */
.site-header__nav {
    display: flex;
    align-items: center;
    gap: var(--space-l);
    margin-left: auto;
}
/* The logout form is wrapped to participate in the flex row without adding
its own block-level margins. */
.site-header__logout {
    margin: 0;
}
/* Header link styling — also applied to the logout submit button so it
reads as a link rather than a button (overrides the global button base).
Text colour + bold weight match the brand so the bar reads as a single
typographic group rather than mixing accent-coloured nav links with the
neutral logo. */
.site-header__link {
    display: inline-flex;
    align-items: center;
    height: auto;
    margin: 0;
    padding: 0;
    color: var(--text);
    background: transparent;
    border: none;
    font-size: inherit;
    font-weight: 700;
    line-height: 1;
    letter-spacing: 0;
    text-transform: none;
    text-decoration: none;
    cursor: pointer;
}
.site-header__link:hover,
.site-header__link:focus {
    background: transparent;
    color: var(--accent);
    text-decoration: underline;
    filter: none;
    outline: 0;
}
/* Pushes the logout glyph away from the other nav links so it reads as
its own affordance rather than another navigation target. The arrow glyph
is bumped up a touch so it does not look undersized against bold link
text alongside it. */
.site-header__link--logout {
    margin-left: var(--space-xl);
    font-size: var(--font-size-l);
    line-height: 1.5;
}

/* Header search glyph (⌕, U+2315). margin-left:auto makes it the header's
free-space absorber, so it sits at the far right when logged out and is the
left-most of the right-hand group when logged in. The sibling rule then zeroes
the account nav's own auto margin so the two do not split the free space
between them — the glyph absorbs it and the account links sit immediately after.
Typed like the other header links; hidden ≤750px (the hamburger overlay carries
a Search entry there instead). */
.site-header__search {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    color: var(--text);
    font-size: var(--font-size-xl);
    line-height: 1;
    text-decoration: none;
}
.site-header__search:hover,
.site-header__search:focus {
    color: var(--accent);
    outline: 0;
}
.site-header__search ~ .site-header__nav {
    margin-left: 0;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Public Site Nav (header, hover-dropdown)
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* The public navigation forest renders inside the site header next to the
brand. Top-level items are inline links typed like the admin nav links;
nested children appear in a small panel positioned under the parent when
the item is hovered or any descendant link is focused. The dropdowns are
CSS-only — no JavaScript, no inline style — so the strict CSP (§21) holds
and keyboard navigation works via :focus-within on the parent <li>. */
.site-nav {
    display: flex;
    align-items: center;
    margin-left: var(--space-xl);
}

/* Top-level list — horizontal, gap-spaced, no bullets/padding so it reads
as a row of links rather than a menu list. */
.site-nav__list {
    display: flex;
    align-items: center;
    gap: var(--space-l);
    margin: 0;
    padding: 0;
    list-style: none;
}

/* Each top-level item is the positioning context for its dropdown panel
so absolutely-positioned child <ul> aligns under the parent link. The
margin reset overrides the global li { margin-bottom: 1rem } that would
otherwise inflate each item's margin box and push the visible link above
the header's centerline — the brand and account links carry no such
bottom margin, so without this zero-out the public nav row reads visibly
higher than every other item on the header's cross-axis. */
.site-nav__item {
    position: relative;
    margin: 0;
}

/* Top-level link styling matches the admin .site-header__link contract
(bold text-coloured link, accent on hover/focus, no underline by default)
so the brand+nav row reads as a single typographic group. The block-level
display gives the hover-target a real box that is easy to reach with a
mouse — a plain inline anchor would only react when the pointer hit the
glyph itself, which makes the dropdown feel flaky. */
.site-nav__item > a {
    display: inline-block;
    padding: var(--space-xs) 0;
    color: var(--text);
    font-weight: 700;
    text-decoration: none;
    line-height: 1;
}
.site-nav__item > a:hover,
.site-nav__item > a:focus {
    color: var(--accent);
    text-decoration: underline;
    outline: 0;
}
/* Non-clickable parent label — rendered by navList when a category has
shows_index = off. Same box geometry as the anchor sibling so the row
stays vertically aligned, but in --text-dim with cursor:default so it
reads as a heading for the dropdown beneath rather than as a link. */
.site-nav__item > .site-nav__label {
    display: inline-block;
    padding: var(--space-xs) 0;
    color: var(--text-dim);
    font-weight: 700;
    line-height: 1;
    cursor: default;
}

/* Nested list (dropdown panel) — hidden by default, revealed only when
the parent <li> is hovered or contains the focused element (keyboard).
position:absolute lets the panel float under the parent without pushing
the header's flex row down. The panel is centred under its parent
(left:50% + translateX(-50%)) and dropped a touch lower (margin-top) so
it reads as a distinct panel sitting beneath the trigger rather than
butting straight against it. The min-width keeps short labels from
collapsing into an unreadable narrow column; align-items/text-align
centre the child links so the stack mirrors the centred panel. */
.site-nav__list .site-nav__list {
    position: absolute;
    top: 100%;
    left: 50%;
    transform: translateX(-50%);
    margin-top: var(--space-2xs);
    display: none;
    flex-direction: column;
    text-align: center;
    gap: var(--space-xs);
    min-width: 12rem;
    padding: var(--space-xs);
    background: var(--surface);
    border: 1px solid var(--border);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
    z-index: 10;
    border-radius: var(--radius);
}
/* Transparent hover bridge spanning the margin-top gap. Because the panel
is a DOM descendant of the parent <li>, hovering it keeps :hover true —
but the bare margin gap is a dead zone the pointer would cross on its way
down, dropping :hover and snapping the panel shut before it is reached.
This pseudo-element fills that gap so the pointer never leaves a
hoverable surface. */
.site-nav__list .site-nav__list::before {
    content: "";
    position: absolute;
    bottom: 100%;
    left: 0;
    right: 0;
    height: var(--space-2xs);
}
.site-nav__item:hover > .site-nav__list,
.site-nav__item:focus-within > .site-nav__list {
    display: flex;
}

/* Nested items render as vertical block links inside the dropdown panel,
distinct from the inline top-level row. Padding gives a comfortable
click target without inflating the panel height per-row. Children of a
nested list (i.e. third-level entries) keep stacking inside the same
panel — full-depth nav as agreed — by inheriting the column flex. */
.site-nav__list .site-nav__list .site-nav__item {
    position: relative;
}
.site-nav__list .site-nav__list .site-nav__item > a {
    display: block;
    padding: var(--space-xs) 0;
    font-weight: 600;
    white-space: nowrap;
}

/* Third-and-deeper dropdowns (a subcategory's own children) drop straight
down, anchored to their parent row's left edge (top:3.5rem clears the row,
left:0 aligns under it). This overrides the second-level panel's centring
transform and lowering margin, which only make sense for the first-child
panel hanging off the header. (The immediate first-child panel keeps its
original centred layout.) */
.site-nav__list .site-nav__list .site-nav__list {
    top: 3.5rem;
    left: 0;
    transform: none;
    margin-top: 0;
}
/* Re-anchor the inherited hover bridge to span the gap the 3.5rem drop
   opens between the parent row and this panel, so the pointer never crosses
   a dead zone that would drop :hover and snap the panel shut. */
.site-nav__list .site-nav__list .site-nav__list::before {
    content: "";
    height: var(--space);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Mobile Site Nav (hamburger + full-screen overlay)
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* The hamburger toggle and the overlay are markup that is always present
but only relevant on narrow viewports. By default (desktop) the toggle is
hidden — the inline .site-nav forest and .site-header__nav links carry the
navigation — and the overlay never shows because site.js leaves its
[hidden] attribute in place. The ≤750px media query below flips this:
the inline groups are hidden and the toggle takes over. */
.site-nav-toggle {
    display: none;
    align-items: center;
    justify-content: center;
    width: var(--font-size-2xl);
    height: var(--font-size-2xl);
    margin-left: auto;
    margin-bottom: 0;
    padding: 0;
    color: var(--text);
    background: transparent;
    border: none;
    cursor: pointer;
}
.site-nav-toggle:hover,
.site-nav-toggle:focus {
    color: var(--accent);
    background: transparent;
    outline: 0;
}
/* Three-bar hamburger drawn in CSS rather than printed as the ☰ glyph,
whose ink sits high in its line box in most fonts and so reads as
vertically offset in the header. The span is the middle bar; its ::before
and ::after are the top and bottom bars, offset symmetrically. Because the
button flex-centres the span (the middle bar) and the other two bars are
mirrored around it, the icon is always perfectly centred. currentColor
ties all three bars to the button's text colour so the hover state
carries through. */
.site-nav-toggle__bars,
.site-nav-toggle__bars::before,
.site-nav-toggle__bars::after {
    display: block;
    width: 2.2rem;
    height: 0.3rem;
    background: currentColor;
    border-radius: 1px;
}
.site-nav-toggle__bars {
    position: relative;
}
.site-nav-toggle__bars::before,
.site-nav-toggle__bars::after {
    content: "";
    position: absolute;
    left: 0;
}
.site-nav-toggle__bars::before {
    top: -0.7rem;
}
.site-nav-toggle__bars::after {
    top: 0.7rem;
}

/* The overlay is a fixed full-viewport panel. [hidden] keeps it out of the
layout (and the accessibility tree) until site.js reveals it; the explicit
display:none guards against the global resets that would otherwise let a
styled element ignore the attribute. */
.mobile-nav[hidden] {
    display: none;
}
.mobile-nav {
    position: fixed;
    inset: 0;
    z-index: 200;
    display: flex;
    flex-direction: column;
    padding: var(--space) var(--space) var(--space-3xl);
    overflow-y: auto;
    background: var(--page);
}

/* Top bar inside the overlay — the brand/logo on the left, the close glyph
on the right, separated by a hairline so the bar reads as a distinct
header for the panel. */
.mobile-nav__bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space);
    padding-bottom: var(--space);
    margin-bottom: var(--space);
    border-bottom: 1px solid var(--border);
}
.mobile-nav__brand {
    font-size: var(--font-size-l);
    font-weight: 700;
    color: var(--text);
    text-decoration: none;
}
.mobile-nav__brand:hover {
    color: var(--accent);
}
.mobile-nav__close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    color: var(--text);
    background: transparent;
    border: none;
    font-size: var(--font-size-xl);
    line-height: 1;
    cursor: pointer;
}
.mobile-nav__close:hover,
.mobile-nav__close:focus {
    color: var(--accent);
    background: transparent;
    outline: 0;
}

/* Body holds the forest then the static links, stacked. */
.mobile-nav__body {
    display: flex;
    flex-direction: column;
    gap: var(--space-s);
}

/* Forest lists stack vertically with no bullets; nested branches indent a
step so the tree depth stays legible inside the accordion. */
.mobile-nav__list {
    display: flex;
    flex-direction: column;
    gap: var(--space-2xs);
    margin: 0;
    padding: 0;
    list-style: none;
}
.mobile-nav__list .mobile-nav__list {
    margin-left: var(--space);
    padding-left: var(--space-s);
    border-left: 1px dashed var(--border);
}
.mobile-nav__item {
    margin: 0;
}

/* Links and the dimmed non-clickable labels share one block geometry so
every row in the tree is the same comfortable tap target. */
.mobile-nav__link,
.mobile-nav__label {
    display: block;
    padding: var(--space-2xs) 0;
    font-size: var(--font-size-l);
    font-weight: 600;
    line-height: 1.2;
    text-decoration: none;
}
.mobile-nav__link {
    color: var(--text);
}
.mobile-nav__link:hover,
.mobile-nav__link:focus {
    color: var(--accent);
    text-decoration: underline;
    outline: 0;
}
.mobile-nav__label {
    color: var(--text-dim);
}

/* A parent's own index link, surfaced as the first child of its expanded
branch. It is a sibling of (not inside) the nested child list, so it
repeats that list's indent and guide border here — the two then line up
as one continuous indented branch beneath the summary. Lighter weight
distinguishes it from the bold summary heading above it. */
.mobile-nav__link--index {
    margin-left: var(--space);
    padding-left: var(--space-s);
    border-left: 1px solid var(--border);
    font-weight: 500;
}

/* Accordion summary — the tap target that expands a branch. The default
disclosure triangle is removed in favour of the explicit chevron span so
the marker styling is consistent across browsers. */
.mobile-nav__group {
    margin: 0;
}
.mobile-nav__summary {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: var(--space-2xs);
    padding: var(--space-2xs) 0;
    font-size: var(--font-size-l);
    font-weight: 700;
    color: var(--text);
    cursor: pointer;
    list-style: none;
}
.mobile-nav__summary::-webkit-details-marker {
    display: none;
}
.mobile-nav__summary:hover,
.mobile-nav__summary:focus {
    color: var(--accent);
    outline: 0;
}

/* Chevron rotates from pointing-right (collapsed) to pointing-down when
the branch is open, giving the accordion its open/closed affordance with
no extra glyph swap. */
.mobile-nav__chevron {
    display: inline-block;
    font-size: var(--font-size);
    line-height: 1;
    transition: transform 0.15s ease;
}
.mobile-nav__group[open] > .mobile-nav__summary .mobile-nav__chevron {
    transform: rotate(90deg);
}

/* Static account links — separated from the forest by a hairline so the
two groups read as distinct. The logout submit button is restyled to read
as a link, matching the desktop header's logout affordance. */
.mobile-nav__static {
    display: flex;
    flex-direction: column;
    gap: var(--space-2xs);
    padding-top: var(--space-s);
    margin-top: var(--space-s);
    border-top: 1px solid var(--border);
}
.mobile-nav__logout {
    margin: 0;
}
.mobile-nav__logout-button {
    width: 100%;
    text-align: left;
    background: transparent;
    border: none;
    cursor: pointer;
}

/* Below the breakpoint the inline desktop nav groups give way to the
hamburger. The toggle becomes visible and the .site-nav forest is removed.
The account links are hidden only via the sibling combinator — i.e. only
the .site-header__nav that follows a .site-nav-toggle (the public shell,
which has an overlay to recover those links). The admin shell reuses
.site-header__nav but emits no toggle, so this selector deliberately
leaves it visible there rather than hiding links with no way back. */
@media (max-width: 750px) {
    .site-nav,
    .site-header__search,
    .site-nav-toggle ~ .site-header__nav {
        display: none;
    }
    .site-nav-toggle {
        display: inline-flex;
    }
}

/* Body scroll-lock while the overlay is open (toggled by site.js) so the
page underneath cannot scroll behind the full-screen menu. */
body.mobile-nav-open {
    overflow: hidden;
}

/* Mobile overlay search entry — the first item in the hamburger menu, where
the desktop header glyph is hidden. A bottom rule separates this action from
the navigation forest below it. */
.mobile-nav__search {
    margin-bottom: var(--space-s);
    padding-bottom: var(--space-s);
    border-bottom: 1px solid var(--border);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Public Search
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* The live-search field. The input keeps the global text-input styling; only
its width is capped so the bar does not stretch the full content column. */
.search__form {
    margin-bottom: var(--space-l);
}
.search__form input {
    max-width: 32rem;
}

/* Active tag-mode banner. */
.search__mode {
    color: var(--text-dim);
}

/* First-load prompt and the genuine zero-results message — both secondary
text so the field stays the focus until results arrive. */
.search__prompt,
.search__empty {
    color: var(--text-dim);
}

/* Result list — a plain stacked list of links, no bullets. */
.search__results {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-s);
}

/* Prev/Next row beneath the results. */
.search__pagination {
    display: flex;
    gap: var(--space-l);
    margin-top: var(--space-l);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Page Editor (admin)
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* Title row — h1 has no bottom margin so it aligns cleanly inside the
.flex-container-spaced wrapper that holds the title + publish/discard. */
.page-edit__title {
    margin: 0;
}

/* Title row wrapper — pins the "View" button to the far right of the row
while the h1 keeps its natural baseline on the left. Items align on the
text baseline so the button sits visually centred against the heading. */
.page-edit__title-row {
    align-items: baseline;
}

/* Multi-field settings row — each label takes an equal share of the
row's width so selects and inputs line up in a clean grid. The basis
keeps fields from collapsing into unusable widths on narrow viewports;
flex-wrap lets the row break to a second line below that threshold. */
.page-edit__form-row {
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--space);
}
.page-edit__form-row > label {
    flex: 1 1 12rem;
}
.page-edit__form-row > label > input,
.page-edit__form-row > label > select {
    width: 100%;
}

/* Single-field variant — same vertical rhythm as the multi-field row but
no flex layout, so the wrapped label spans the full form width. Used by
the element details modal to lift the FieldAsset picker out of the
shared inline row and onto its own line above the short fields (alt /
size etc.). */
.page-edit__form-row--single {
    margin-top: var(--space);
}
.page-edit__form-row--single > label {
    display: block;
}
.page-edit__actions {
    display: flex;
    align-items: baseline;
    gap: var(--space-s);
}

/* Permalink line under the Slug input — uses .footnote + .dim from the
text utilities; this rule only tucks the top margin so the line sits
close to the input above. */
.page-edit__permalink {
    margin-top: 0;
}

/* Save-settings action row — right-aligned so the primary action sits
on the form's far edge, with no competing "Back to pages" link. */
.page-edit__form-actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--space);
}

/* Left-anchors the destructive Delete action in the right-justified
actions row: an auto right margin pushes the trailing Cancel/Save pair to
the far edge while Delete stays pinned to the left. */
.page-edit__form-actions-delete {
    margin-right: auto;
}

/* Helper text sitting underneath a locked field on the home-page edit
form (slug always "/", status always Public). Dimmed and small-print so
the read-only state reads as "by design" rather than as a validation
error. */
.page-edit__lock-hint {
    margin: var(--space-xs) 0 0 0;
    color: var(--text-dim);
    font-size: var(--font-size-s);
}

/* "Content" section title — bold body text replacing the prior h2 so it
reads as a subtle divider rather than a primary heading. */
.page-edit__section-title {
    margin-top: var(--space-l);
    margin-bottom: var(--space-s);
}

/* Page-settings Version Control section — sits at the foot of the form
   under the field rows, separated by a quiet divider so it reads as a
   distinct group rather than another row of fields. The horizontal row
   pairs the "Current Version" inline summary on the left with the
   Revert button on the right, mirroring the Save-settings action row's
   right-anchored emphasis. The disabled Revert variant keeps the
   destructive colouring but visually flattens via opacity so an
   un-publishable page reads as "no action available yet". */
.page-edit__divider {
    border: 0;
    border-top: 1px solid var(--border);
    margin: var(--space) 0 var(--space-s) 0;
}
.page-edit__version-title {
    margin: 0 0 var(--space-s) 0;
    font-size: var(--font-size);
}
.page-edit__version-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space);
    flex-wrap: wrap;
}
.page-edit__version-meta {
    flex: 1;
    min-width: 0;
    color: var(--text);
}
.page-edit__version-row .button-destructive.disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* Version-history modal — shares the asset-picker__list row scaffold so
   the modal reads visually consistent with the asset picker, with a few
   row-shape overrides: no thumbnail column, two-line metadata stack
   (index / when / title-status), and a per-row Preview link that sits
   outside the radio-wrapping <label> so clicking Preview does not toggle
   selection. The radio is visually hidden but kept focusable via :focus
   styles; row highlight derives from :has(:checked) to keep the JS
   surface zero. */
.version-history__list {
    max-height: 24rem;
    overflow-y: auto;
}
.version-history__item {
    align-items: stretch;
}
.version-history__row {
    flex: 1;
    display: flex;
    align-items: center;
    gap: var(--space-s);
    cursor: pointer;
    min-width: 0;
}
.version-history__radio {
    flex-shrink: 0;
    margin: 0;
}
.version-history__meta {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-3xs);
}
.version-history__index {
    font-weight: 600;
}
.version-history__current-tag {
    display: inline-block;
    margin-left: var(--space-2xs);
    padding: 0 var(--space-2xs);
    background: var(--good-soft);
    color: var(--good);
    border-radius: var(--radius-s);
    font-size: var(--font-size-xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05rem;
}
.version-history__when,
.version-history__title {
    color: var(--text-dim);
    font-size: var(--font-size-s);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.version-history__preview {
    flex-shrink: 0;
    align-self: center;
    color: var(--accent);
    text-decoration: none;
}
.version-history__preview:hover {
    text-decoration: underline;
}
.version-history__item:has(.version-history__radio:checked) {
    border-color: var(--accent);
    background: var(--surface-sunken);
}
.version-history__actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--space-s);
    margin-top: var(--space);
}

/* Snapshot preview banner — sits above the article on the admin preview
   route. Distinct from the global notification surface so an operator
   can tell at a glance they are not looking at a live page. The "Back
   to editor" link sits on the right, matching the editor's right-
   anchored action conventions. */
.snapshot-preview-banner {
    display: flex;
    align-items: center;
    gap: var(--space);
    margin: 0 0 var(--space) 0;
    padding: var(--space-s) var(--space);
    background: var(--warn-soft);
    border: 1px solid var(--warn);
    border-radius: var(--radius-s);
}
.snapshot-preview-banner__hint {
    flex: 1;
    color: var(--text-dim);
    font-size: var(--font-size-s);
}
.snapshot-preview-banner__back {
    color: var(--accent);
    text-decoration: none;
    white-space: nowrap;
}
.snapshot-preview-banner__back:hover {
    text-decoration: underline;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Content Editor
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* Two-pane editor shell — palette sidebar on the left, the element list
on the right. Banner rows (error / dirty / confirm-discard) span both
columns so they sit above both panes. */
.editor {
    display: grid;
    grid-template-columns: 20rem 1fr;
    gap: var(--space);
    align-items: start;
}
.editor > .editor__banner,
.editor > .notification {
    grid-column: 1 / -1;
}
@media (max-width: 750px) {
    .editor {
        grid-template-columns: 1fr;
    }
    /* Single-column layout: the palette stacks above the list, so sticky
       positioning would just pin it over the content — revert to static. */
    .editor__sidebar {
        position: static;
        max-height: none;
        overflow-y: visible;
    }
}

/* Palette sidebar — surface tone separates it from the page background;
the add-element links stack vertically and stretch to the column width.
The sidebar is sticky so the element picker travels alongside the canvas
while scrolling a long page; align-self:start (the grid already sets
align-items:start) lets it settle at the top rather than stretching to the
row height, and the max-height + overflow keep a long palette scrollable
within the viewport. */
.editor__sidebar {
    position: sticky;
    top: var(--space);
    align-self: start;
    max-height: calc(100vh - var(--space) * 2);
    overflow-y: auto;
}
.editor__sidebar-title {
    margin: 0 0 var(--space-s) 0;
}
.editor__add {
    display: block;
    padding: var(--space-2xs) var(--space-s);
    color: var(--accent);
    text-decoration: none;
    border-radius: var(--radius-s);
}
.editor__add:hover {
    background: var(--surface-sunken);
    cursor: pointer;
}
/* Glyph column in the palette — fixed width so the labels align in a tidy
column regardless of which unicode character a given element declares.
Colour is inherited from the parent link so the glyph reads as part of the
same affordance (accent in the default state, hover state on hover). */
.editor__add-icon {
    display: inline-block;
    width: 1.25em;
    margin-right: var(--space-2xs);
    text-align: center;
}

/* Right pane — element list. min-width:0 lets the column shrink when the
viewport narrows so a wide row label can't push the grid wider. */
.editor__main {
    min-width: 0;
}

.editor-library {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: var(--space-s);
    background: var(--surface);
}
.editor-library-item {
    display: block;
    width: 100%;
    margin-bottom: var(--space-2xs);
    text-align: left;
}
.editor-canvas {
    display: flex;
    flex-direction: column;
    gap: var(--space-s);
    min-height: 20rem;
}

/* Public render of a Columns element — CSS grid driven entirely by the
   --cols-N modifier on the wrapper. Slots inherit the body background
   (no surface of their own), matching the spec that Columns renders
   without a backdrop. The grid collapses to a single column on narrow
   viewports so column-rich layouts stay readable on mobile. */
.element-columns {
    display: grid;
    gap: var(--space);
    margin: var(--space) 0;
}
.element-columns--cols-1 {
    grid-template-columns: minmax(0, 1fr);
}
.element-columns--cols-2 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
}
.element-columns--cols-3 {
    grid-template-columns: repeat(3, minmax(0, 1fr));
}
.element-columns--cols-4 {
    grid-template-columns: repeat(4, minmax(0, 1fr));
}
.element-columns__slot {
    min-width: 0;
}
@media (max-width: 640px) {
    .element-columns--cols-1,
    .element-columns--cols-2,
    .element-columns--cols-3,
    .element-columns--cols-4 {
        grid-template-columns: 1fr;
    }
}

/* Public render of a Group element — a transparent wrapper whose only
   purpose is to keep its children together in one Columns slot or as
   one organisational unit at the top level. No visual treatment by
   default; the styled variants below opt back into a real box. */
.element-group {
    display: contents;
}

/* Any styled group variant needs a real box — display:contents would
   discard the background, accordion frame or full-width sizing. */
.element-group--accordion,
.element-group--has-bg,
.element-group--full,
.element-group[class*="surface-"] {
    display: block;
}

/* Normal group carrying a surface background reuses the shared .surface-*
   colour / border / radius / padding; this only adds the inter-element
   vertical rhythm so a styled group reads as a discrete card. */
.element-group[class*="surface-"] {
    margin: var(--space) 0;
}

/* Full-width group breaks its contents out of the article column to span
   the viewport, with side padding so content is not flush to the edges. */
.element-group--full {
    width: 100vw;
    max-width: 100vw;
    margin-left: calc(50% - 50vw);
    margin-right: calc(50% - 50vw);
    padding-left: var(--space);
    padding-right: var(--space);
}

/* Background-image group: a decorative <img> fills the box behind a
   relatively-positioned body. No CSS background-image is used, so the
   strict CSP holds (§21). */
.element-group--has-bg {
    position: relative;
    overflow: hidden;
    border-radius: var(--radius-s);
    /* Only the block margins are set here. The shorthand `margin: … 0` would
       reset margin-left/right to 0 and, because this rule follows
       .element-group--full, wipe out that rule's negative breakout margins —
       leaving a full-width background group spanning only to the right. */
    margin-top: var(--space);
    margin-bottom: var(--space);
}
/* An edge-to-edge background group has no visible corners, so drop the
   radius (mirrors .element-image--full-width). */
.element-group--has-bg.element-group--full {
    border-radius: 0;
}
.element-group__bg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    z-index: 0;
}
.element-group--has-bg .element-group__body {
    position: relative;
    z-index: 1;
    padding: var(--space);
}

/* Accordion group: a collapsible <details>. A fallback border makes the
   control visible when no surface background is chosen; a surface variant
   supplies its own background, so the fallback border and surface padding
   are dropped and the summary/body provide the internal spacing. */
.element-group--accordion {
    margin: var(--space) 0;
    border: 1px solid var(--border);
    border-radius: var(--radius-s);
    overflow: hidden;
}
.element-group--accordion[class*="surface-"] {
    border: 0;
    padding: 0;
}
.element-group__summary {
    cursor: pointer;
    padding: var(--space-s);
    font-weight: 600;
}
.element-group--accordion .element-group__body {
    padding: 0 var(--space-s) var(--space-s);
}

/* Public render of an Image element. The image element registry exposes
three size choices — small / medium / full — and the renderer emits the
size as a modifier class (.element-image--{size}). The base rule pins
max-width:100% so the image never breaks out of its container regardless
of the underlying file dimensions; height:auto preserves the aspect
ratio when max-width clamps the rendered size. block display + margin
auto centres the image inside the article column for the small and
medium variants where the image is narrower than the container. */
.element-image {
    display: block;
    max-width: 100%;
    height: auto;
    margin: var(--space) auto;
    border-radius: var(--radius-s);
}
.element-image--small {
    max-width: min(100%, 24rem);
}
.element-image--medium {
    max-width: min(100%, 48rem);
}
.element-image--full {
    max-width: 100%;
    width: 100%;
}
/* full-width breaks the image out of the article column to span the full
   viewport width (the banner treatment), using the standard 50%/50vw
   centring trick so it stays centred at any container width. The corner
   radius is dropped because an edge-to-edge banner has no visible corners. */
.element-image--full-width {
    width: 100vw;
    max-width: 100vw;
    margin-left: calc(50% - 50vw);
    margin-right: calc(50% - 50vw);
    border-radius: 0;
}
/* Max-height presets crop the image to a fixed height band with
   object-fit:cover so a tall source fills its size box and the overflow is
   trimmed rather than letterboxed. width:100% makes the image fill that box
   (small / medium / full / full-width) before the vertical crop applies. */
.element-image--maxh-short,
.element-image--maxh-medium,
.element-image--maxh-tall {
    object-fit: cover;
}
.element-image--maxh-short {
    height: 14rem;
}
.element-image--maxh-medium {
    height: 22rem;
}
.element-image--maxh-tall {
    height: 34rem;
}
.element-image--missing {
    display: inline-block;
    padding: var(--space-s);
    color: var(--text-dim);
    font-style: italic;
    border: 1px dashed var(--border);
    border-radius: var(--radius-s);
}

/* Images embedded inline via markdown (`![alt](path)`) render as bare
   <img> tags inside .element-text (public, non-editable) or
   .user-editable .preview (admin editor canvas + public inline-editable).
   The dedicated image content element has its own size modifiers; this
   rule only guards the markdown path so a tall/wide source file cannot
   overflow the surrounding prose column. height:auto preserves aspect
   ratio when max-width clamps width. Display is left default so an image
   placed mid-paragraph stays inline with surrounding text and one placed
   alone in a paragraph keeps its natural block-like layout from the
   wrapping <p>. */
.element-text img,
.user-editable .preview img {
    max-width: 100%;
    height: auto;
}

/* Text element horizontal alignment. The renderer always emits one
   .element-text--align-X class (defaulting to left) on both the public
   .element-text render and the .user-editable inline-edit container, so the
   chosen alignment reads identically to a visitor and to an editor. */
.element-text--align-left {
    text-align: left;
}
.element-text--align-center {
    text-align: center;
}
.element-text--align-right {
    text-align: right;
}
.element-text--align-justify {
    text-align: justify;
}

/* Public render of a Video element. Mirrors .element-image: the figure
   acts as the size-bounded container (small / medium / full modifier
   classes pin its max-width), while the inner iframe expands to fill
   that width with a fixed 16:9 aspect ratio so the embed is always
   responsive regardless of provider. block display + margin auto
   centres the figure inside the article column for the small and
   medium variants where it is narrower than the container. The figure
   override of the browser default 40px inline margin is intentional —
   matches the image rule so videos and images line up visually. */
.element-video {
    display: block;
    margin: var(--space) auto;
}
.element-video__frame {
    display: block;
    width: 100%;
    aspect-ratio: 16 / 9;
    border: 0;
    border-radius: var(--radius-s);
}
.element-video--small {
    max-width: min(100%, 24rem);
}
.element-video--medium {
    max-width: min(100%, 48rem);
}
.element-video--full {
    max-width: 100%;
    width: 100%;
}
.element-video__caption {
    margin-top: var(--space-2xs);
    color: var(--text-dim);
    font-size: var(--font-size-xs);
}

/* Public render of a Button element. The element wraps a single inline-block
   anchor styled with the shared .button base + .button-{variant} colour
   class. The wrapper uses text-align so left / center / right alignment
   modifiers don't require the anchor to know its own placement, keeping
   the variant classes decoupled from layout. The nested anchor's bare
   .button margin-bottom is reset to zero so adjacent content elements
   don't compound vertical space — the wrapper's own var(--space) margin
   handles separation in the article flow. */
.element-button {
    margin: var(--space) 0;
}
.element-button .button {
    margin: 0;
}
.element-button--align-left {
    text-align: left;
}
.element-button--align-center {
    text-align: center;
}
.element-button--align-right {
    text-align: right;
}

/* Separator element — a standalone horizontal rule. Uses the theme border
   token for the line and the standard inter-element vertical rhythm so it
   reads as a deliberate section break rather than a hairline divider. */
.element-separator {
    border: 0;
    border-top: 1px solid var(--border);
    margin: var(--space-l) 0;
}

/* Text element surface treatment. The shared .surface-* classes already
   provide the colour / border / radius / shadow / 1rem padding — these
   rules only add the inter-element vertical rhythm (var(--space)) so a
   surfaced paragraph reads as a discrete card, and zero the first/last
   markdown child's outer margin so the natural <p>/<h*>/<ul> margins
   don't compound with the surface's own padding. Both selectors cover
   the non-editable public render (.element-text) and the inline-edit
   wrapper (.user-editable) since either may carry the surface class. */
.element-text[class*="surface-"],
.user-editable[class*="surface-"] {
    margin: var(--space) 0;
}
.element-text[class*="surface-"] > :first-child,
.user-editable[class*="surface-"] .preview > :first-child {
    margin-top: 0;
}
.element-text[class*="surface-"] > :last-child,
.user-editable[class*="surface-"] .preview > :last-child {
    margin-bottom: 0;
}

/* The editor row/child ordered lists drive ordering server-side via the
↑↓ controls; the default decimal markers add no information and clash with
the surface-styled rows, so suppress them on both nesting levels. The
child list also gets a left rail so a nested Group or Column slot reads
visibly as "inside" its parent. */
.editor__rows,
.editor__children {
    list-style: none;
    padding-left: 0;
}
.editor__children {
    margin: var(--space-3xs) 0 0 var(--space);
    padding-left: var(--space-s);
    border-left: 1px dashed var(--border);
}
/* Inline placeholder shown inside an empty Columns or Group slot. Muted
   so it does not visually compete with real content while still being
   readable as a hint. */
.editor__empty--child {
    padding: var(--space-2xs) var(--space-s);
    color: var(--text-dim);
    font-style: italic;
}

/* Editor element header — label on the left, link-style controls pinned
to the right. The body-level hx-headers carries the CSRF token so each
link can act without its own form wrapper. The head wears .surface-scene
in markup so it picks up the shared radius/elevation tokens; this rule
keeps the header-tight padding. The accordion config form that used to
expand under this head has been retired — every element type now opens
its details modal via the ••• control instead. */
.editor__element-head {
    display: flex;
    align-items: baseline;
    gap: var(--space-s);
    padding: var(--space-2xs) var(--space-s);
}
.editor__element-label {
    font-weight: 600;
    flex: 1;
}

/* Inline control cluster — right-aligned flex row whose direct children
are .editor__controls-group spans (reorder / indent / copy-paste /
delete / details). The outer gap separates one logical group from the
next; the inner gap inside each group keeps related buttons tight. */
.editor__controls {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    gap: var(--space-l);
    background: var(--surface-sunken);
    border-radius: var(--radius-pill);
    padding: 0 var(--space);
}
/* Inner button cluster — buttons in the same logical group sit close
together so the eye reads them as one control set, while the outer
.editor__controls gap puts breathing room between groups. The inner
gap is large enough that adjacent glyphs don't visually fuse but
still well below the outer gap so the group remains a unit. */
.editor__controls-group {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2xs);
}
/* Glyph buttons read at a touch above body text so the row sits
visually balanced with the heading label next to it — slight bump
only, the icons are still subordinate to the element label. */
.editor__controls-group button {
    font-size: 1.4rem;
    line-height: 1;
}
/* Confirm variant — the cluster contents are swapped for a prompt + the
Yes/No pair so the destructive action cannot fire alongside the
non-destructive ones. The dimmed text sits on the left, the buttons
stay clustered on the right. */
.editor__controls--confirm {
    gap: var(--space-s);
}
.editor__controls-confirm-text {
    color: var(--text-dim);
}
.editor__control {
    color: var(--accent);
}
.editor__control.destructive {
    color: var(--destructive);
}
.editor__control:hover {
    filter: saturate(200%);
    text-decoration: underline;
    cursor: pointer;
}
/* Disabled control — visible but inert. Used by ← / → indent/outdent when
the operation is not applicable in the current tree shape. */
.editor__control.disabled {
    color: var(--text-dim);
    pointer-events: none;
    cursor: default;
}

/* Unpublished-changes banner — attention (warn) state. */
.unpublished-banner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-s);
    padding: var(--space-s) var(--space);
    border: 1px solid var(--warn);
    border-radius: var(--radius);
    background: var(--warn-soft);
}

/* Confirmation dialog inside the modal overlay. Width is the shared
--modal-max-width so every dialog has the same horizontal footprint. */
.confirm-dialog {
    max-width: var(--modal-max-width);
    margin: 10% auto;
    padding: var(--space-xl);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
}

/* Form dialog — modal panel sized for a multi-field form (used by the
New Page / New Category dialogs on /admin/pages and the text-element
details modal in the editor). Shares --modal-max-width with
.confirm-dialog so dialogs line up regardless of their inner layout. */
.form-dialog {
    max-width: var(--modal-max-width);
    margin: 6% auto;
    padding: var(--space-xl);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
}
.form-dialog__title {
    margin: 0 0 var(--space) 0;
}
/* Inline error banner inside the dialog. */
.form-dialog__error {
    margin: 0 0 var(--space) 0;
    padding: var(--space-2xs) var(--space-s);
    color: var(--destructive);
    background: var(--destructive-soft);
    border: 1px solid var(--destructive);
    border-radius: var(--radius-s);
}
.form-dialog__form {
    display: flex;
    flex-direction: column;
    gap: var(--space);
    margin: 0;
}
/* Field column — label sits above the control, both span the full width
of their parent slot (so a single field fills the row, while two fields
share via .form-dialog__row's flex layout below). */
.form-dialog__field {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-width: 0;
    gap: var(--space-3xs);
    margin: 0;
}
.form-dialog__field > input,
.form-dialog__field > select {
    width: 100%;
    margin: 0;
}
/* Two-column row inside a dialog (Slug + Meta description on the Create
Page form, Name + Slug on Create Category). The fields split the row's
width 50/50 with a single gap between them. */
.form-dialog__row {
    display: flex;
    gap: var(--space);
}
/* Action row — Cancel link on the left, primary submit on the far right
so the destructive-feeling close affordance sits visually distinct from
the create action. */
.form-dialog__actions {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: var(--space);
    margin-top: var(--space-s);
}
.form-dialog__cancel {
    margin-right: auto;
}
/* Inline italic error rendered inside the universal element-details
modal's action row, just to the left of Cancel / Save. Used when a
modal submission is rejected by a domain rule the modal's fields cannot
surface (Columns shrink-guard). Italic + slightly smaller than the
button text so it reads as commentary on the save attempt rather than
another control. Colour borrows the shared destructive token so the
visual weight is consistent with the rest of the editor's error
language. */
.editor__details-error {
    font-style: italic;
    font-size: 0.875em;
    color: var(--destructive);
}
/* Destructive entry point on the left edge of the action row (Delete
inside the Rename Category modal). margin-right: auto pushes the
Cancel/Save pair to the far right while keeping this anchor flush-left;
the destructive tint and button chrome come from the .button-
destructive class that's also on the element, so this rule only owns
the layout. */
.form-dialog__danger {
    margin-right: auto;
}

/* Inline action row — used inside the multi-form Edit User modal where
each form has its own Save button that should sit flush to the right
of its own field rather than at the bottom of the dialog. */
.form-dialog__actions--inline {
    margin-top: 0;
}

/* Split action row — Generate-password on the left, Save on the right.
Overrides the default flex-end alignment so the two controls anchor to
opposite edges and read as opposite intents (regenerate vs. commit). */
.form-dialog__actions--split {
    justify-content: space-between;
}

/* Multi-select listbox inside a form dialog. The global `select { height:
38px }` rule forces every select to one row of height which collides
with size="8" on a multi-select — without this override the box renders
cramped to a single line. Releasing the height to auto lets the browser
size the box to the requested row count (same fix as the editor's
.editor__access-modules-list select). */
.form-dialog__select-multi {
    height: auto;
}

/* One-time notification banner inside a form dialog — used to surface
the freshly-generated plaintext password on the Edit User modal.
Background and border use the --good tone to read as a positive
result; the embedded .form-dialog__reveal input is the copy-friendly
display of the secret itself. */
.form-dialog__notice {
    margin: 0 0 var(--space) 0;
    padding: var(--space-2xs) var(--space-s);
    color: var(--good);
    background: var(--good-soft);
    border: 1px solid var(--good);
    border-radius: var(--radius-s);
}
.form-dialog__notice-title {
    margin: 0 0 var(--space-3xs) 0;
    font-weight: 600;
}
.form-dialog__reveal {
    width: 100%;
    margin: 0;
    font-family: var(--font-mono);
    font-size: 1.1em;
}

/* Inline hint paragraph inside a form dialog — used to explain auto-
generated values (e.g. the absent password field in New User). Muted
tone so it reads as a side note, not an error or success banner. */
.form-dialog__hint {
    margin: 0 0 var(--space-s) 0;
    color: var(--text-dim);
    font-size: 0.9em;
}

/* Inline hint under a profile field (e.g. the optional Moniker) — muted
side-note tone, mirroring .form-dialog__hint. */
.profile__hint {
    margin: 0 0 var(--space-s) 0;
    color: var(--text-dim);
    font-size: 0.9em;
}

/* Live password-policy checklist under the New-password field on
/admin/profile. Each rule sits muted with a ✗ marker until satisfied, then
turns green with a ✓ as site.js re-evaluates the field on every keystroke
(mirroring internal/auth/password.go). It is only a hint — the server remains
the authoritative gate. */
.password-policy {
    list-style: none;
    margin: 0 0 var(--space-s) 0;
    padding: 0;
    font-size: 0.9em;
}
.password-policy__rule {
    display: flex;
    align-items: baseline;
    gap: var(--space-2xs);
    color: var(--text-dim);
}
.password-policy__rule::before {
    content: "\2717";
    color: var(--text-dim);
}
.password-policy__rule--met {
    color: var(--good);
}
.password-policy__rule--met::before {
    content: "\2713";
    color: var(--good);
}

/* Row-action cell on /admin/users — Edit User (accent) + Delete User
(destructive) plain text links, right-anchored to match the other
admin tables (/admin/pages, /admin/recovery, /admin/modules). */
.users__actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--space-s);
}

/* Moniker cell placeholder — the dimmed em-dash shown when a user has no
display name set, so the column reads as "unset" rather than blank. */
.users__moniker-empty {
    color: var(--text-dim);
}

/* Role glyph chips in front of the role label on /admin/users — a
filled-star for superusers in an amber tone, a filled-circle for
plain users in the project's --good green. The amber is drawn from
--accent (the warm orange used elsewhere); the green from --good.
The glyph itself is rendered with display: inline-block so the line
height matches the role label next to it. */
.role-glyph {
    display: inline-block;
    margin-right: var(--space-3xs);
    line-height: 1;
}
.role-glyph--super {
    color: var(--accent);
}
.role-glyph--user {
    color: var(--good);
}

/* Inline user-editable contract (server emits these literally). The hover
affordance needs no extra markup. The min-height keeps an empty preview tall
enough to click into editing mode — without it a node with no markdown
collapses to zero height and the user has nothing to target. */
.user-editable .preview {
    border-radius: var(--radius-s);
    min-height: var(--space-3xl);
}
.user-editable:hover .preview {
    outline: 2px dashed var(--accent);
    outline-offset: 0.3rem;
    cursor: text;
}

/* Strip the global form margin-bottom (the generic `form` rule sets 2.5rem)
inside the inline editor. The form is a near-empty child of the bordered
.user-editable wrapper, so that inherited margin pads the wrapper out and makes
an editable block stand taller than an identical non-editable one. */
.user-editable__form {
    margin-bottom: 0;
}

/* §15 inline-edit save affordance — the Save button is hidden until editor.js
flips the container's data-dirty bit to "1" on the first CodeMirror change.
The button-and-row layout is right-anchored so the user-editable action row
matches the project-wide convention used on the sign-in and profile forms.
The action row carries an obvious gap below the EasyMDE editor so Save does
not crowd the editor chrome. */
.user-editable__actions {
    margin-top: var(--space-s);
}
.user-editable[data-dirty="0"] .user-editable__save {
    display: none;
}

/* EasyMDE skin — overrides the vendor light chrome to follow the active
theme. Vendor class names are targeted verbatim (required to bind). The
preview pane is disabled (plan section 15) and intentionally untouched. */
.EasyMDEContainer .CodeMirror {
    color: var(--text);
    background: var(--surface-sunken);
    border: 1px solid var(--border);
    height: 270px;
}

/* Flatten the vendor's WYSIWYG-ish heading scale inside the source view.
EasyMDE's bundled CSS sizes .cm-header-1 through .cm-header-6 with
calc-based responsive font sizes so headings render visibly larger than
body text — that turns the editor into a typographic preview and
defeats the "edit markdown source" model. Resetting font-size and
line-height to inherit keeps every line at the base editor size while
leaving the heading color and font-weight from the vendor highlighter
intact so markers remain colour-coded. The dedicated server preview
(see editor.templ) is unaffected because it renders outside any
.EasyMDEContainer / .CodeMirror scope. */
.EasyMDEContainer .CodeMirror .cm-header,
.EasyMDEContainer .CodeMirror .cm-header-1,
.EasyMDEContainer .CodeMirror .cm-header-2,
.EasyMDEContainer .CodeMirror .cm-header-3,
.EasyMDEContainer .CodeMirror .cm-header-4,
.EasyMDEContainer .CodeMirror .cm-header-5,
.EasyMDEContainer .CodeMirror .cm-header-6 {
    font-size: inherit;
    line-height: inherit;
}

/* Markdown syntax colours inside the source view. The vendor's
`.cm-s-easymde` theme only paints a handful of tokens (links, urls,
quotes, html tags); every other markdown token — the formatting
markers (#, *, `, [, ]), inline code, strong, em — would otherwise
inherit the editor's body colour, which the dark theme sets to
white. The mappings below colour those tokens using the existing
semantic palette so the same rules read in both themes. Heading
TEXT inherits body colour intentionally (no .cm-header rule) — only
the leading marker glyphs are coloured. List markers get a more
specific rule so the general "markers → warn" doesn't repaint them
amber alongside heading and emphasis markers. */
.EasyMDEContainer .CodeMirror .cm-formatting {
    color: var(--warn);
}
.EasyMDEContainer .CodeMirror .cm-formatting-list {
    color: var(--accent);
}
.EasyMDEContainer .CodeMirror .cm-link {
    color: var(--tint);
}
.EasyMDEContainer .CodeMirror .cm-url {
    color: var(--text-dim);
}
.EasyMDEContainer .CodeMirror .cm-comment {
    color: var(--good);
}
.EasyMDEContainer .CodeMirror .cm-quote {
    color: var(--text-dim);
}
.EasyMDEContainer .CodeMirror .cm-strong {
    color: var(--tint);
}
.EasyMDEContainer .CodeMirror .cm-em {
    color: var(--tint);
}
.EasyMDEContainer .CodeMirror .cm-hr {
    color: var(--text-dim);
}
.EasyMDEContainer .CodeMirror-cursor {
    border-left-color: var(--text);
}
/* The toolbar is split into two layers (editor.js's wrapToolbarPill
injects the inner wrapper): the outer .editor-toolbar is a full-width
header bar matching the .surface-active recipe (deep --active-soft
navy + --active hairline) so it reads as the "active" surface type
used elsewhere in the admin, and the inner .editor-toolbar__pill
carries the control-row pill that holds the buttons and separators.
The pill is anchored left so it sits flush with the editor's edge,
and the vertical padding is bumped a notch so the pill breathes
inside the header instead of touching its top/bottom borders. */
.editor-toolbar {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    background: var(--active-soft);
    border: 1px solid var(--active);
    padding: var(--space-xs) var(--space-s);
}
.editor-toolbar__pill {
    display: inline-flex;
    align-items: center;
    gap: var(--space-l);
    background: var(--surface-sunken);
    border-radius: var(--radius-pill);
    padding: 0 var(--space);
    /* .control-row carries margin-left: auto for right-anchoring inside
    its usual flex parents; override that so the pill stays flush with
    the editor's left edge. */
    margin-left: 0;
}
/* Buttons inside the toolbar take the button-recess / no-padding /
no-margin / accent visual recipe — borderless, no shadow, accent-coloured
glyphs, zero outer spacing (the toolbar's flex gap handles separation).
Vendor's forced 30px height and 30px min-width are released so each
button sizes to its glyph instead of carrying empty padding. */
.editor-toolbar button {
    border: none;
    background: var(--surface-sunken);
    box-shadow: none;
    padding: 0;
    margin: 0;
    height: auto;
    min-width: 0;
    color: var(--accent) !important;
}
/* Hover / active state — fill with accent so the inverted glyph (forced
to --text below) stays legible against the bright surface. */
.editor-toolbar button:hover,
.editor-toolbar button.active {
    background: var(--surface-sunken);
    color: var(--text);
}
.editor-toolbar button:hover i,
.editor-toolbar button.active i {
    color: var(--text);
}
/* Separators between button groups render as a dim bullet instead of
the vendor's vertical rule. The vendor markup is <i class="separator">|
</i> with width:0, text-indent:-10px and color:transparent hiding the
pipe behind 1px borders — we strip the borders, restore width and
text-indent, and inject a bullet via ::before with an explicit colour
so the parent's color:transparent (vendor rule) keeps the pipe
character hidden. */
.editor-toolbar i.separator {
    border-left: none;
    border-right: none;
    width: auto;
    text-indent: 0;
    margin: 0;
}
.editor-toolbar i.separator::before {
    content: "\2022";
    color: var(--text-dim);
}
.editor-statusbar {
    color: var(--text-dim);
}
/* The EasyMDE container is generated from a textarea and inherits no width
of its own — without this it collapses to its content. Let it fill the
inline-editor body that wraps it. */
.EasyMDEContainer,
.EasyMDEContainer .CodeMirror {
    width: 100%;
}
/* EasyMDE ships toolbar buttons that expect FontAwesome to paint their
glyphs (vendor markup is <i class="fa fa-bold"> etc.), but FontAwesome is
not bundled (autoDownloadFontAwesome:false in editor.js — CSP forbids the
hot-link). Provide visible text content for each icon class used by our
toolbar (see editor.js) so the controls render legibly in both themes,
themed in --text so dark mode stays readable. The font-style override is
needed because <i> is italicised by the user agent by default. */
.editor-toolbar button i {
    color: var(--accent);
    font-style: normal;
    font-weight: 700;
    font-family: var(--font-sans);
}
.editor-toolbar i.fa-bold::before {
    content: "B";
}
.editor-toolbar i.fa-italic::before {
    content: "I";
    font-style: italic;
}
.editor-toolbar i.fa-strikethrough::before {
    content: "S";
    text-decoration: line-through;
}
.editor-toolbar i.fa-header::before {
    content: "H";
}
.editor-toolbar i.fa-code::before {
    content: "</>";
    font-family: var(--font-mono);
}
.editor-toolbar i.fa-quote-left::before {
    content: "\201C";
}
.editor-toolbar i.fa-list-ul::before {
    content: "\2261";
}
.editor-toolbar i.fa-list-ol::before {
    content: "\2116";
}
.editor-toolbar i.fa-link::before {
    content: "\2197";
}
.editor-toolbar i.fa-image::before {
    content: "\1F5BC";
}
.editor-toolbar i.fa-table::before {
    content: "\229E";
}
.editor-toolbar i.fa-minus::before {
    content: "\2014";
}
.editor-toolbar i.fa-eraser::before {
    content: "\2715";
}
.editor-toolbar i.fa-check-square-o::before {
    content: "\2611";
}

/* Markdown text block layout (§15) — head + hr + body live inside a
single surface card so the click-the-preview-to-edit flow reads as one
visual unit. The card overrides .surface-scene's 1rem padding to 0 so
the divider hr runs edge-to-edge; padding moves to the inner head and
body wrappers. */
.editor__text-card {
    padding: 0;
    overflow: hidden;
}
.editor__text-head {
    display: flex;
    align-items: baseline;
    gap: var(--space-s);
    padding: var(--space-2xs) var(--space-s);
}
.editor__text-divider {
    border: 0;
    border-top: 1px solid var(--border);
    margin: 0;
}
.editor__text-body {
    padding: var(--space-s);
}

/* Section heading inside the text-element details modal — a quiet
visual break between the description input and the multi-control access
row below. The modal panel itself uses the shared .form-dialog width. */
.editor__details-section {
    margin: var(--space-s) 0 0 0;
}

/* Access Restrictions accordion's multi-select listbox — the global
`select { height: 38px }` rule forces every select to a single-row
height which collides with `size="8"` on a multi-select, so the
listbox would otherwise render as one cramped line. Releasing the
height to auto lets the browser size the box to the requested rows;
width is handled by the surrounding .page-edit__form-row rule
(`select { width: 100% }`) so the listbox fills its flex slot. */
.editor__access-modules-list select {
    height: auto;
}
/* This row pairs the tall listbox with the short User Editable
select; flip the form-row's default flex-end so the short select
aligns with the TOP of the listbox rather than dropping to its
bottom. */
.editor__access-modules-row {
    align-items: flex-start;
}

/* Asset UI — one shared grid reused on /admin/assets and inside the
picker modal. */
.asset-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
    gap: var(--space-s);
}
.asset-card {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: var(--space-2xs);
    background: var(--surface);
    font-size: var(--font-size-xs);
}
.asset-thumb {
    display: block;
    width: 100%;
    aspect-ratio: 4 / 3;
    object-fit: cover;
    border-radius: var(--radius-s);
    background: var(--surface-sunken);
}
.asset-picker {
    margin: 8% auto;
    max-width: 80rem;
    padding: var(--space-l);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
}
.asset-picker-filter {
    margin-bottom: var(--space-s);
}

/* Embedded asset picker — shared visual between two host contexts:
   1) .asset-field__picker inside the element-details modal (image
      element's FieldAsset);
   2) .form-dialog inside the markdown-mode picker modal opened by the
      EasyMDE "img" toolbar button.
   Both render the picker as a bordered, scrollable list panel rather than
   the default floating modal styling, so the picker reads as a deliberate
   embedded panel inside the host dialog. Items in markdown mode are
   directly clickable (no per-row "Use" button), so the form-dialog scope
   adds cursor:pointer to advertise that affordance. */
.asset-field__picker .asset-picker,
.form-dialog .asset-picker {
    margin: 0;
    padding: var(--space-s);
    max-width: 100%;
    max-height: 28rem;
    overflow-y: auto;
}
.asset-field__picker .asset-picker__filter,
.form-dialog .asset-picker__filter {
    display: flex;
    gap: var(--space-xs);
    align-items: center;
    margin-bottom: var(--space-xs);
}
.asset-field__picker .asset-picker__filter input[type="text"],
.form-dialog .asset-picker__filter input[type="text"] {
    flex: 1;
}
.asset-field__picker .asset-picker__list,
.form-dialog .asset-picker__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-2xs);
}
.asset-field__picker .asset-picker__item,
.form-dialog .asset-picker__item {
    display: flex;
    align-items: center;
    gap: var(--space-s);
    padding: var(--space-2xs);
    border: 1px solid transparent;
    border-radius: var(--radius-s);
}
.asset-field__picker .asset-picker__item:hover,
.form-dialog .asset-picker__item:hover {
    border-color: var(--border);
    background: var(--surface-sunken);
}
.asset-field__picker .asset-picker__thumb,
.form-dialog .asset-picker__thumb {
    width: 4rem;
    height: 4rem;
    object-fit: cover;
    border-radius: var(--radius-s);
    background: var(--surface-sunken);
    flex-shrink: 0;
}
.asset-field__picker .asset-picker__item > span,
.form-dialog .asset-picker__item > span {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Both picker modes render rows as wholesale click targets (the "Use"
   button is gone; htmx attrs in inline mode and a JS delegate in markdown
   mode both fire on the row itself), so the cursor must advertise that
   affordance. The empty-state <li> doesn't carry .js-asset-pick, so the
   selector skips it. */
.asset-field__picker .asset-picker__item.js-asset-pick,
.form-dialog .asset-picker__item.js-asset-pick {
    cursor: pointer;
}

/* The asset field — the inline picker control rendered for every
FieldAsset registry entry on the element-details modal. The wrapper has
no border/padding of its own so it spans the full content width of the
modal (the modal's padding owns the inset). Layout is a vertical stack:
the current-state row (thumbnail + filename + a right-anchored action,
either Choose asset… when empty or Clear when bound), an optional
Replace action row underneath when an asset is bound, and the lazy
picker host below that. */
.asset-field {
    display: flex;
    flex-direction: column;
    gap: var(--space-s);
    width: 100%;
}
/* Current-state row: thumb + filename on the left, the inline action
button pushed to the right by margin-left:auto. The min-height keeps the
empty-state row at the same height as the bound row so the action
button does not jump vertically when an asset is selected for the first
time. */
.asset-field__current {
    display: flex;
    align-items: center;
    gap: var(--space-s);
    min-height: 3.2rem;
}
.asset-field__thumb {
    width: 3rem;
    height: 3rem;
    object-fit: cover;
    border-radius: var(--radius-s);
    background: var(--surface-sunken);
    flex-shrink: 0;
}
.asset-field__thumb--placeholder {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: var(--font-size-l);
    color: var(--text-dim);
}
.asset-field__name {
    flex: 1 1 0;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.asset-field__name--empty,
.asset-field__name--orphan {
    color: var(--text-dim);
    font-style: italic;
}
/* The Clear / Choose asset button sits at the row's right edge. The name
above takes flex:1 to soak up the remaining width, which naturally pushes
this action to the right; flex-shrink:0 prevents the button from
collapsing when the filename is long. */
.asset-field__inline-action {
    flex-shrink: 0;
}
/* Replace action row — only renders when an asset is bound. Right-
anchored as its own row beneath the current-state row, with the parent
gap providing the vertical breathing room. */
.asset-field__actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--space-s);
}
/* Picker host is empty by default and contributes no layout until the
operator opens the picker — :empty + display:none keeps the field
compact when the picker has been collapsed via Cancel. */
.asset-field__picker:empty {
    display: none;
}

/* Editor canvas row thumbnail — small confirmation preview shown next to
the type label for image elements whose config carries a resolvable
asset. Caps height so a portrait-oriented source does not stretch the
row beyond the standard editor head height. */
.editor__element-label--with-thumb {
    display: inline-flex;
    align-items: center;
    gap: var(--space-s);
    min-width: 0;
    /* The element head row is align-items:baseline (correct for the all-text
       rows), but a cluster led by a 3rem thumbnail has no usable text
       baseline, so baseline-aligning it sat the thumb and filename off the
       row. Centring this cluster keeps the thumb, label and filename on one
       tidy centreline within the row. */
    align-self: center;
}
.editor__element-thumb {
    width: 3rem;
    height: 3rem;
    object-fit: cover;
    border-radius: var(--radius-s);
    background: var(--surface-sunken);
    flex-shrink: 0;
}
.editor__element-thumb-name {
    color: var(--text-dim);
    font-size: var(--font-size-s);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 28rem;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Admin
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* Admin shell — section nav plus main content inside the .container. */
.admin-shell {
    display: grid;
    grid-template-columns: 18rem 1fr;
    gap: var(--space-l);
}
@media (max-width: 750px) {
    .admin-shell {
        grid-template-columns: 1fr;
    }
}
.admin-nav {
    border-right: 1px solid var(--border);
    padding-right: var(--space);
}
.admin-main {
    min-width: 0;
}

/* Dashboard — region wrapper holding the stat tiles and activity panel;
structural only (mirrors .admin-main grid-child safety). */
.admin-dashboard {
    min-width: 0;
}
.admin-dashboard-stats {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
    gap: var(--space-s);
    margin-bottom: var(--space-xl);
}
.admin-recent-activity {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    padding: var(--space-s);
}

/* Shared listing table (pages, users, modules, redirects, recovery,
static, activity, auth-log). */
.admin-table {
    width: 100%;
    border-collapse: collapse;
}
.admin-table th,
.admin-table td {
    padding: var(--space-xs);
    border-bottom: 1px solid var(--border);
    text-align: left;
}
.admin-table tbody tr:hover {
    background: var(--surface-sunken);
}
.admin-table-actions {
    display: flex;
    gap: var(--space-3xs);
}

/* Row-action cell on /admin/pages. Both controls are plain text links
(View Page in the default --accent, Delete in --destructive via the
shared .destructive utility), right-anchored in the cell with a small
gap so they read as a single action group at the far edge of the
row. justify-content: flex-end pushes the group to the cell's right
edge regardless of the natural label widths. */
.pages__actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--space-s);
}

/* Row-action cell on /admin/recovery — Restore (tint) + Delete permanently
(destructive) plain text links, right-anchored to match /admin/pages so the
admin tables read with a consistent action affordance. */
.recovery__actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--space-s);
}

/* Row-action cell on /admin/modules — Edit (accent) + Delete (destructive)
plain text links, right-anchored to match /admin/pages. */
.modules__actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--space-s);
}

/* Row-action cell on /admin/redirects — Delete (destructive) plain text
link, right-anchored to match /admin/pages so the admin tables read with
a consistent action affordance. Single action today; the flex shell keeps
the rule shape ready for any future row-level controls. */
.redirects__actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--space-s);
}

/* Shared page-title row used across the admin tables (assets, modules,
redirects, users) when a primary create action sits inline with the h1.
The h1 sits on the left at the row's natural baseline; the button is
pushed flush to the right edge via justify-content: space-between.
align-items: center keeps both elements vertically centred regardless of
the button's slightly taller chrome. */
.admin-page__header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--space);
}
/* Title h1 sheds its default margin inside the header row so the button
sits at the same vertical baseline as the heading text rather than the
heading block. */
.admin-page__header h1 {
    margin: 0;
}

/* /admin landing — grouped tile console (§17). The page is the post-login
hub, so it reads as labeled clusters of clickable cards rather than a flat
link list. Each group has a left-edge bar in a semantic colour token; each
tile is a self-contained surface with a glyph, title and one-line subtitle. */
.admin__intro {
    color: var(--text-dim);
    margin-bottom: var(--space-xl);
}

/* The outer column stacks groups with generous vertical breathing so the
clusters read as discrete sections rather than a single uninterrupted grid. */
.admin__groups {
    display: flex;
    flex-direction: column;
    gap: var(--space-2xl);
}

/* Group container — left-edge accent bar in the group's semantic colour.
The bar is the lightweight visual signal that ties tiles inside the same
cluster together without painting whole-tile backgrounds. */
.admin__group {
    border-left: 3px solid var(--border);
    padding-left: var(--space);
}
.admin__group--content {
    border-left-color: var(--accent);
}
.admin__group--access {
    border-left-color: var(--tint);
}
.admin__group--observability {
    border-left-color: var(--warn);
}

/* Group label — small-caps header that sits above the cluster's tile grid.
Sized down from h2 default so it reads as a section label, not another
page-level heading. */
.admin__group-label {
    font-size: var(--font-size-s);
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--text-dim);
    font-weight: 600;
    margin: 0 0 var(--space-s) 0;
}

/* Tile grid — responsive auto-fill so the cluster fills its column at any
viewport width without media queries. minmax(220px, 1fr) keeps cards from
collapsing to an unreadable width on narrow screens. */
.admin__tiles {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: var(--space-s);
}

/* Tile — the whole card is the link surface. Stack the glyph, title and
subtitle vertically so the tile reads top-to-bottom; the transition picks
up the per-group hover accent rules below. */
.admin__tile {
    display: flex;
    flex-direction: column;
    gap: var(--space-3xs);
    padding: var(--space);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    color: var(--text);
    text-decoration: none;
    box-shadow: var(--shadow);
    transition:
        border-color 120ms ease,
        transform 120ms ease,
        box-shadow 120ms ease;
}
.admin__tile:hover {
    text-decoration: none;
    transform: translateY(-1px);
}
/* Keyboard-focus ring — focus-visible only so the ring does not flash on
mouse clicks. Uses --focus-ring (--tint) for a clear accent visible across
both themes, sitting outside the tile so it does not displace the border. */
.admin__tile:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px;
}

/* Per-group hover treatment — tile picks up its group's accent colour on
the border, paired with a colour-matched inset ring so the hover state
reads as a deliberate emphasis rather than a one-pixel border swap. The
outer drop shadow stays as --shadow; only the inner ring changes hue. */
.admin__group--content .admin__tile:hover {
    border-color: var(--accent);
    box-shadow:
        var(--shadow),
        inset 0 0 0 1px var(--accent);
}
.admin__group--access .admin__tile:hover {
    border-color: var(--tint);
    box-shadow:
        var(--shadow),
        inset 0 0 0 1px var(--tint);
}
.admin__group--observability .admin__tile:hover {
    border-color: var(--warn);
    box-shadow:
        var(--shadow),
        inset 0 0 0 1px var(--warn);
}

/* Tile glyph — large, single-line typographic mark painted in the group's
accent token so each group reads visually distinct without per-tile colour
overrides. aria-hidden in the templ keeps the glyph invisible to assistive
tech (the title carries the semantic label). */
.admin__tile-glyph {
    font-size: var(--font-size-2xl);
    line-height: 1;
    margin-bottom: var(--space-2xs);
    color: var(--text-dim);
}
.admin__group--content .admin__tile-glyph {
    color: var(--accent);
}
.admin__group--access .admin__tile-glyph {
    color: var(--tint);
}
.admin__group--observability .admin__tile-glyph {
    color: var(--warn);
}

/* Tile title — primary label, larger than body copy and semibold so it
reads as the tile's heading even though the underlying element is a span
inside the anchor. */
.admin__tile-title {
    font-size: var(--font-size-l);
    font-weight: 600;
    line-height: 1.2;
    color: var(--text);
}

/* Tile subtitle — one-line description in dim text under the title. The
subtitle is intentionally short (4–6 words) so it fits a single line at
the narrowest tile width supported by the grid. */
.admin__tile-subtitle {
    font-size: var(--font-size-s);
    line-height: 1.4;
    color: var(--text-dim);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Category Index Tiles (public /c/<slug> when shows_index = on)
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* The category index renders the same auto-fill grid the admin landing does,
so the operator's public index reads as a deliberate console rather than a
text list. minmax(220px, 1fr) keeps cards from collapsing on narrow
viewports; gap matches the admin grid for visual consistency. */
.category-tiles {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: var(--space-s);
}

/* Each tile is the entire link surface — title stacks above the truncated
excerpt, the whole card is clickable, and the hover treatment matches the
admin tile so a visitor used to one surface recognises the other. */
.category-tiles__tile {
    display: flex;
    flex-direction: column;
    gap: var(--space-3xs);
    padding: var(--space);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    color: var(--text);
    text-decoration: none;
    box-shadow: var(--shadow);
    transition:
        border-color 120ms ease,
        transform 120ms ease,
        box-shadow 120ms ease;
}
.category-tiles__tile:hover {
    text-decoration: none;
    transform: translateY(-1px);
    border-color: var(--accent);
    box-shadow:
        var(--shadow),
        inset 0 0 0 1px var(--accent);
}
/* Keyboard-focus ring — focus-visible so the ring does not flash on mouse
clicks. Mirrors the admin tile contract so both surfaces feel the same to
keyboard users. */
.category-tiles__tile:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px;
}

/* Tile title — primary label, larger than body copy and semibold so it
reads as the tile's heading even though the underlying element is a span
inside the anchor. line-height tightened so two-line titles wrap without
inflating the tile height. */
.category-tiles__title {
    font-size: var(--font-size-l);
    font-weight: 600;
    line-height: 1.2;
    color: var(--text);
}

/* Tile excerpt — dim, smaller, line-clamped to two lines so a long first
paragraph does not push the card height past its neighbours. -webkit-line-clamp
is widely supported and is the standard CSS-only way to truncate by visible
lines; the white-space rules keep the fallback (no clamp support) tidy. */
.category-tiles__excerpt {
    font-size: var(--font-size-s);
    line-height: 1.4;
    color: var(--text-dim);
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    overflow: hidden;
}

/* /admin/assets filter row — text input fills the available space, the
Filter button sits flush right. justify-content: space-between pushes
the two children to opposite edges regardless of label widths; the
input grows via flex: 1 so the field's hit-target stretches as wide as
the row instead of staying its small default width. */
.assets__filter {
    display: flex;
    align-items: center;
    gap: var(--space-s);
    margin-bottom: var(--space);
}
.assets__filter input[type="text"] {
    flex: 1;
    margin: 0;
}

/* /admin/assets thumbnail cell. The image is capped by max-height so a tall
source image cannot stretch the row's intrinsic height; width: auto keeps
the natural aspect ratio so wide images stay readable in a narrow column.
display: block on the wrapping link strips the inline-baseline gap so the
cell's vertical centring works for both tall and wide images. */
.assets__thumb-link {
    display: inline-block;
    line-height: 0;
}
.assets__thumb {
    display: block;
    max-height: 56px;
    width: auto;
    height: auto;
    border-radius: var(--radius);
}

/* Row-action cell on /admin/assets — Delete (destructive) plain text link,
right-anchored to match /admin/pages, /admin/redirects, /admin/modules
and /admin/users so the admin tables read with a consistent action
affordance. */
.assets__actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--space-s);
}

/* /admin/assets upload-modal drop zone. The whole rule is a single label
wrapping a (visually-hidden) <input type="file">, so clicks anywhere on
the zone open the native file picker and drag-drop onto the zone hands
the file straight to the input (the native input accepts drops without
JS). Dashed border + generous padding gives the zone the universally-
recognised drop-target look; site.js swaps the prompt text for the
selected filename and toggles the assets__dropzone--has-file modifier
so the chosen state reads visually distinct. */
.assets__dropzone {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    gap: var(--space-2xs);
    padding: var(--space-l) var(--space);
    border: 2px dashed var(--border);
    border-radius: var(--radius);
    background: var(--surface-sunken);
    cursor: pointer;
    transition:
        border-color 120ms ease,
        background 120ms ease;
}
.assets__dropzone:hover,
.assets__dropzone--dragover {
    border-color: var(--accent);
    background: var(--surface);
}
.assets__dropzone--has-file {
    border-style: solid;
    border-color: var(--accent);
}
/* The filename slot is empty until site.js fills it in. Hiding empties
prevents an unwanted blank line when no file has been chosen yet. */
.assets__dropzone-filename:empty {
    display: none;
}
.assets__dropzone-filename {
    font-weight: 600;
    color: var(--accent);
    word-break: break-all;
}
/* The native input is visually hidden but still keyboard-focusable so
the file picker can be reached without a mouse. Pointer events stay on
because the label's implicit association passes clicks through to the
input itself; drag-drop is wired by site.js on the surrounding zone. */
.assets__dropzone-input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
}

/* /admin/pages toolbar — status filter on the left (inline Filter button
flush against the select), action buttons on the far right (New Page /
Create Category open the form modals). The row uses justify-content:
space-between so the filter and the action group anchor to opposite
edges regardless of label widths; align-items: center keeps the
controls baselined to the same vertical centre. */
.pages__toolbar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--space);
    margin-bottom: var(--space);
}
.pages__filter {
    display: flex;
    align-items: center;
    gap: var(--space-2xs);
    margin: 0;
}
/* Zero the default 1.5rem / 1rem bottom margins the global input/select
and button rules add — those margins shift the controls below the
flex-line centre and make the Filter button look misaligned next to
the select. The label's base margin-bottom is killed too so its text
sits on the same centreline as the input it labels. */
.pages__filter > * {
    margin: 0;
}
.pages__filter-label {
    font-weight: 600;
}
/* Right-anchored action group. Wider gap (var(--space-s)) so the two
primary actions read as distinct buttons rather than a packed pair. */
.pages__toolbar-actions {
    display: flex;
    gap: var(--space-s);
}

/* Folder selector breadcrumb — ⌂ Home → Articles → News. Each segment
except the last is a link; the last is plain text in --text-dim so the
current location reads differently from the clickable ancestors. The
root category is the home of the tree (no separate Home category),
hence the ⌂ glyph + "Home" label baked into the templ. → separators
are dim and wrap onto a second line on narrow screens. */
.pages__breadcrumb {
    display: flex;
    flex-flow: row wrap;
    align-items: center;
    gap: var(--space-2xs);
    margin-bottom: var(--space-2xs);
}
.pages__breadcrumb-sep {
    color: var(--text-dim);
}
.pages__breadcrumb-current {
    color: var(--text-dim);
}
/* House glyph in front of the Home segment. Slightly larger than the
adjacent text and given a single-space right margin so it reads as a
prefix icon rather than the first character of the word. */
.pages__breadcrumb-home {
    margin-right: var(--space-4xs);
    font-size: 1.1em;
    line-height: 1;
}

/* "Modify Category" trigger for the rename/delete modal of the
currently-selected category. Sits on the same row as the breadcrumb
chain but is pushed flush-right by margin-left:auto so the chain on
the left and the action on the right are visually separated without
needing a second row. */
.pages__breadcrumb-modify {
    margin-left: auto;
}

/* Sub-category dropdown sitting inline as the final element of the
breadcrumb chain. The form wrapper participates directly in the
breadcrumb's flex flow (inline-flex, no margin) so the <select>
aligns baseline-centred with the chain segments. */
.pages__breadcrumb-children {
    display: inline-flex;
    align-items: center;
    margin: 0;
}

/* Page status badge — the text label in the /admin/pages status column,
preceded by a coloured circle glyph keyed on publication maturity:
empty ring (Draft, grey), empty ring (Private, amber), solid disc
(Public, green). Draft and Private share the same ring shape and only
differ by colour so the operator reads "fillness" as a proxy for
public visibility (no fill = not yet on the open web). The glyph is
painted by ::before keyed on the BadgeClass so the templ markup stays
plain text. inline-flex with a small gap aligns the glyph baseline-
centred with the label. */
.status {
    display: inline-flex;
    align-items: center;
    gap: var(--space-3xs);
}
.status--draft::before {
    content: "○";
    color: var(--passive);
}
.status--private::before {
    content: "○";
    color: var(--warn);
}
.status--public::before {
    content: "●";
    color: var(--good);
}

/* Status filter control on /admin/pages. */
.status-filter {
    display: flex;
    gap: var(--space-2xs);
    align-items: center;
    margin-bottom: var(--space);
}

/* Offset pager (shared shared.Pager). Matches the activity-log pager: a plain
white caption followed by accent Previous/Next links, left-aligned, with no
bordered boxes. Previous/Next inherit the global accent anchor style. */
.pagination {
    display: flex;
    gap: var(--space-2xs);
    align-items: baseline;
    justify-content: flex-start;
    margin: var(--space-l) 0;
}
.pagination-current {
    color: var(--text);
}

/* Editable category tree on /admin/categories — reuses tree indentation. */
.category-tree,
.category-tree ul {
    list-style: none;
    margin: 0;
    padding: 0;
}
.category-tree ul {
    margin-left: var(--space);
    border-left: 1px solid var(--border);
    padding-left: var(--space-s);
}
.category-tree-item {
    display: flex;
    align-items: center;
    gap: var(--space-2xs);
    padding: var(--space-3xs) 0;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Status Badges
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* Intent-keyed badge. Go maps each extensible page status to one intent
(draft->passive, review->warn, public->good, private->active). */
.status-badge {
    display: inline-block;
    padding: 0 var(--space-2xs);
    height: 2rem;
    line-height: 2rem;
    border-radius: var(--radius-pill);
    font-size: var(--font-size-2xs);
    text-transform: uppercase;
    letter-spacing: 0.05rem;
    border: 1px solid var(--border);
    color: var(--text);
}
.status-badge-good {
    background: var(--good-soft);
    border-color: var(--good);
}
.status-badge-warn {
    background: var(--warn-soft);
    border-color: var(--warn);
}
.status-badge-passive {
    background: var(--passive-soft);
    border-color: var(--passive);
}
.status-badge-active {
    background: var(--active-soft);
    border-color: var(--active);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Utilities
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

.u-full-width {
    width: 100%;
    box-sizing: border-box;
}
.u-max-full-width {
    max-width: 100%;
    box-sizing: border-box;
}
.u-pull-right {
    float: right;
}
.u-pull-left {
    float: left;
}

/* Settings page (§17). Every setting is one row with a uniform shape so the
page reads as a list and future settings need no bespoke layout: a heading, the
current state / description in the body, and the action controls anchored right
by margin-left:auto, with an <hr> (.settings__sep) between rows. align-items
keeps the heading, preview, and action button on one baseline; wrap lets the
row reflow on narrow viewports. */
.settings__row {
    display: flex;
    align-items: center;
    gap: var(--space);
    flex-wrap: wrap;
}
.settings__row-heading {
    margin: 0;
    font-size: var(--font-size-l);
}
.settings__row-body {
    display: flex;
    align-items: center;
    color: var(--text-dim);
}
.settings__row-actions {
    margin-left: auto;
}
/* Inline preview of the active logo sitting in the row body, on a sunken card
so it reads as a value rather than page chrome. */
.settings__row-logo {
    max-height: 4rem;
    width: auto;
    padding: var(--space-xs);
    background: var(--surface-sunken);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}
.settings__none {
    color: var(--text-dim);
}
/* Active theme name in the theme row body — full-strength text so it reads as
a concrete value rather than the dimmed placeholder copy beside it. */
.settings__theme-name {
    color: var(--text);
}
/* Row separator between settings. */
.settings__sep {
    border: none;
    border-top: 1px solid var(--border);
    margin: var(--space) 0;
}
.settings__empty {
    color: var(--text-dim);
}
/* Right-hand confirm group inside the logo modal's split action row — keeps
Cancel and the primary Save together against the right edge while Remove logo
anchors left. */
.settings__modal-confirm {
    display: flex;
    align-items: center;
    gap: var(--space);
}
/* Selectable image grid — responsive auto-fill columns of fixed-width option
cards. The native radio stays in the flow as the selection control; the card's
border and background lift when its radio is checked (:has) so the chosen logo
reads at a glance without any scripting (§21). */
.settings__logo-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
    gap: var(--space);
    margin-bottom: var(--space-l);
}
.settings__logo-option {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-xs);
    padding: var(--space-s);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    cursor: pointer;
}
.settings__logo-option:has(.settings__logo-radio:checked) {
    border-color: var(--accent);
    background: var(--surface-sunken);
}
.settings__logo-thumb {
    max-height: 8rem;
    max-width: 100%;
    width: auto;
}
.settings__logo-name {
    color: var(--text-dim);
    font-size: 0.9em;
    word-break: break-all;
    text-align: center;
}

/* Standalone error page (404, 405, 403, 500, recovered panic). No site shell:
   a centred heading over a surface-warn detail card and a link back to home.
   The body fills the viewport so the panel sits in the optical centre. */
.error-page {
    min-height: 100vh;
    margin: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space);
}
/* The panel stacks the heading, the warn card and the home link, centred
   horizontally and capped to the shared modal footprint so the error card
   lines up with the site's dialogs. */
.error-page__panel {
    width: 100%;
    max-width: var(--modal-max-width);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space);
}
.error-page__heading {
    margin: 0;
}
/* The detail card carries the status, description and optional reference; it
   takes the full panel width and centres its own text. */
.error-page__detail {
    width: 100%;
    text-align: center;
}
/* The numeric HTTP status reads as the card's headline. */
.error-page__code {
    margin: 0;
    font-size: 2.5rem;
    font-weight: 700;
    line-height: 1;
}
.error-page__description {
    margin: var(--space-s) 0 0 0;
}
/* The reference identifier matters to maintainers grepping the logs, not to
   the visitor, so it sits quietly beneath the description. */
.error-page__reference {
    margin: var(--space-s) 0 0 0;
    color: var(--text-dim);
    font-size: 0.9em;
}

/*
This file evolved from Natural Selection:
https://github.com/frontaid/natural-selection
*/
