/* =========================================================================
   Biloou — dashboard & auth
   Same neo-brutalist system as the marketing site, tuned for long forms:
   calmer background, tighter rhythm, borders that still hit hard.
   ========================================================================= */

:root {
    --ink:     #141210;
    --paper:   #FFF8EC;
    --paper-2: #FFEFD6;
    --shade:   #F3EADA;
    --yellow:  #FFE04B;
    --cyan:    #6BE5E0;
    --pink:    #FF6FB5;
    --lime:    #C6FF5C;
    --orange:  #FF8A3D;
    --purple:  #B39BFF;
    --danger:  #D8362A;
    --ok:      #167A4B;

    --border: 3px solid var(--ink);
    --sh-sm: 3px 3px 0 var(--ink);
    --sh:    6px 6px 0 var(--ink);

    --font-display: "Arial Black", "Archivo Black", "Helvetica Neue", Impact, system-ui, sans-serif;
    --font-body: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    --font-mono: "JetBrains Mono", "Cascadia Mono", Consolas, "Courier New", monospace;

    --ease-pop: cubic-bezier(0.34, 1.56, 0.64, 1);
}

*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

html { overflow-x: clip; }
@supports not (overflow: clip) { html { overflow-x: hidden; } }

body {
    background: var(--shade);
    color: var(--ink);
    font-family: var(--font-body);
    font-size: 1rem;
    line-height: 1.55;
    -webkit-font-smoothing: antialiased;
}

a { color: inherit; }
ul, ol { list-style: none; padding: 0; }
button, input, select, textarea { font: inherit; color: inherit; }

/* The `hidden` attribute must win over any `display` a component sets, or
   things like the password meter stay on screen when they should not. */
[hidden] { display: none !important; }

h1, h2, h3 {
    font-family: var(--font-display);
    font-weight: 900;
    line-height: 1.02;
    letter-spacing: -0.015em;
    text-transform: uppercase;
}

:focus-visible { outline: 3px solid var(--ink); outline-offset: 2px; }

.sr-only {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

.skip-link {
    position: absolute;
    top: -100px; left: 1rem;
    z-index: 999;
    padding: 0.7rem 1.15rem;
    background: var(--yellow);
    border: var(--border);
    box-shadow: var(--sh);
    font-weight: 800;
    text-decoration: none;
}
.skip-link:focus { top: 1rem; }

/* -------------------------------------------------------------------------
   Buttons
   ------------------------------------------------------------------------- */
.btn-brut {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    padding: 0.7rem 1.1rem;
    background: var(--paper);
    border: var(--border);
    box-shadow: var(--sh-sm);
    font-family: var(--font-display);
    font-size: 0.78rem;
    text-transform: uppercase;
    text-decoration: none;
    cursor: pointer;
    transition: transform 0.13s var(--ease-pop), box-shadow 0.13s var(--ease-pop),
                background-color 0.13s ease;
}
.btn-brut:hover:not(:disabled) { transform: translate(-2px, -2px); box-shadow: 5px 5px 0 var(--ink); }
.btn-brut:active:not(:disabled) { transform: translate(2px, 2px); box-shadow: none; }
.btn-brut:disabled { opacity: 0.45; cursor: not-allowed; }

.btn-brut--primary { background: var(--yellow); }
.btn-brut--dark { background: var(--ink); color: var(--paper); }
.btn-brut--sm { padding: 0.45rem 0.7rem; font-size: 0.68rem; }
.btn-brut--lg { padding: 0.95rem 1.3rem; font-size: 0.9rem; box-shadow: var(--sh); }
.btn-brut--block { width: 100%; }

/* Continue with Google ----------------------------------------------------
   Google's brand guidelines ask for their mark on a white or light surface,
   untouched and with its own breathing room. That is what the tile on the left
   is for: the mark keeps its colours and its clear space, and the rest of the
   button is allowed to look like everything else here.

   Kept white rather than yellow on purpose — this is the one control on the
   page, and a sign-in button that looks like the site's own accent reads as a
   decoration rather than as the thing to press.
   ------------------------------------------------------------------------- */
.btn-google {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.75rem;
    padding: 0.85rem 1.15rem;
    background: #fff;
    color: var(--ink);
    border: var(--border);
    box-shadow: var(--sh);
    font-family: var(--font-display);
    font-size: 0.9rem;
    text-transform: uppercase;
    text-decoration: none;
    cursor: pointer;
    transition: transform 0.13s var(--ease-pop), box-shadow 0.13s var(--ease-pop),
                background-color 0.13s ease;
}
.btn-google:hover:not(:disabled) { transform: translate(-2px, -2px); box-shadow: 8px 8px 0 var(--ink); }
.btn-google:active:not(:disabled) { transform: translate(3px, 3px); box-shadow: none; }
.btn-google:disabled { opacity: 0.45; cursor: not-allowed; }
.btn-google--block { width: 100%; }

.btn-google__mark {
    display: grid;
    place-items: center;
    flex: 0 0 auto;
    width: 1.85rem; height: 1.85rem;
    background: #fff;
    border: 2px solid var(--ink);
}
.btn-google__mark svg { display: block; }

.icon-btn {
    display: grid;
    place-items: center;
    width: 2rem; height: 2rem;
    background: var(--paper);
    border: 2px solid var(--ink);
    cursor: pointer;
    line-height: 1;
    transition: background-color 0.12s ease, transform 0.12s var(--ease-pop);
}
.icon-btn:hover { background: var(--yellow); transform: translateY(-2px); }
.icon-btn--danger:hover { background: var(--danger); color: var(--paper); }

/* -------------------------------------------------------------------------
   Flash messages
   ------------------------------------------------------------------------- */
.flash {
    padding: 0.8rem 1rem;
    margin-bottom: 1.25rem;
    background: var(--lime);
    border: var(--border);
    border-left-width: 10px;
    box-shadow: var(--sh-sm);
    font-weight: 700;
    font-size: 0.9375rem;
}
.flash--warn  { background: var(--yellow); }
.flash--error { background: var(--pink); }

/* -------------------------------------------------------------------------
   Auth screens
   Split layout: brand panel on the left, form on the right. On narrow screens
   the panel becomes a slim strip so the form is what you land on.
   ------------------------------------------------------------------------- */
.auth-body { background: var(--paper); }

.auth-split {
    display: grid;
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    min-height: 100vh;
}

/* Brand panel ------------------------------------------------------------ */
.auth-panel {
    position: relative;
    display: flex;
    flex-direction: column;
    padding: clamp(1.75rem, 4vw, 3rem);
    background-color: var(--yellow);
    background-image:
        linear-gradient(rgba(20, 18, 16, 0.07) 1px, transparent 1px),
        linear-gradient(90deg, rgba(20, 18, 16, 0.07) 1px, transparent 1px);
    background-size: 26px 26px;
    border-right: var(--border);
    overflow: hidden;
}

.auth-panel__deco { position: absolute; inset: 0; pointer-events: none; }
.auth-panel__shape { position: absolute; border: var(--border); }
.auth-panel__shape--1 {
    top: 12%; right: -3rem;
    width: 11rem; height: 11rem;
    background: var(--cyan);
    border-radius: 50%;
}
.auth-panel__shape--2 {
    bottom: 16%; left: -2.5rem;
    width: 8rem; height: 8rem;
    background: var(--pink);
    transform: rotate(18deg);
}
.auth-panel__shape--3 {
    bottom: 40%; right: 18%;
    width: 3.5rem; height: 3.5rem;
    background: var(--lime);
    transform: rotate(-12deg);
}

.auth-panel__brand,
.auth-panel__title,
.auth-panel__points,
.auth-panel__foot { position: relative; z-index: 1; }

.auth-panel__brand {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    align-self: flex-start;
    font-family: var(--font-display);
    font-size: 1.1rem;
    text-decoration: none;
    text-transform: uppercase;
}
.auth-panel__mark {
    display: grid;
    place-items: center;
    width: 2.1rem; height: 2.1rem;
    background: var(--paper);
    border: var(--border);
    box-shadow: var(--sh-sm);
}
.auth-panel__brand:hover .auth-panel__mark { background: var(--pink); }

.auth-panel__title {
    margin-top: auto;
    padding-top: 2.5rem;
    font-family: var(--font-display);
    font-size: clamp(1.6rem, 3.2vw, 2.8rem);
    line-height: 1;
    letter-spacing: -0.02em;
    text-transform: uppercase;
}

.auth-panel__points {
    display: grid;
    gap: 0.6rem;
    margin-top: 1.75rem;
    font-size: 0.9375rem;
    font-weight: 600;
}
.auth-panel__points li { display: flex; align-items: flex-start; gap: 0.6rem; }
.auth-panel__points li::before {
    content: "";
    flex: 0 0 auto;
    width: 12px; height: 12px;
    margin-top: 0.45rem;
    background: var(--paper);
    border: 2px solid var(--ink);
}

.auth-panel__foot { margin-top: auto; padding-top: 2.5rem; font-size: 0.875rem; font-weight: 700; }
.auth-panel__foot a { text-decoration: none; border-bottom: 3px solid var(--ink); }
.auth-panel__foot a:hover { background: var(--paper); }

/* Form side -------------------------------------------------------------- */
.auth-main {
    display: grid;
    place-items: center;
    padding: clamp(1.75rem, 4vw, 3rem) clamp(1.25rem, 4vw, 3rem);
}

.auth-card { width: min(100%, 27rem); }

/* The card brand only shows when the panel is out of view. */
.auth-card__brand {
    display: none;
    align-items: center;
    gap: 0.55rem;
    margin-bottom: 1.5rem;
    font-family: var(--font-display);
    text-decoration: none;
    text-transform: uppercase;
}
.auth-card__mark {
    display: grid;
    place-items: center;
    width: 2rem; height: 2rem;
    background: var(--yellow);
    border: var(--border);
}

.auth-card__title { font-size: clamp(1.7rem, 5vw, 2.4rem); }
.auth-card__sub { margin-top: 0.5rem; font-size: 0.9375rem; }

.auth-form { margin-top: 1.75rem; display: grid; gap: 1.15rem; }

.auth-card__note {
    margin-top: 1.25rem;
    font-size: 0.8125rem;
    opacity: 0.75;
}

.auth-card__alt {
    margin-top: 1.5rem;
    padding-top: 1.25rem;
    border-top: 2px dashed var(--ink);
    font-size: 0.9rem;
}
.auth-card__alt a { font-weight: 800; border-bottom: 3px solid var(--pink); text-decoration: none; }
.auth-card__alt a:hover { background: var(--pink); }

/* The password field, its show/hide toggle, its strength meter and the signup
   honeypot all used to live here. They went with the passwords: sign-in is one
   button now, and the honeypot guarded a form a bot could fill in, which this
   is not. */

/* A lone card with no brand panel beside it — the 404 page. */
.auth-standalone {
    display: grid;
    place-items: center;
    min-height: 100vh;
    padding: 2rem 1.25rem;
    background-color: var(--yellow);
    background-image:
        linear-gradient(rgba(20, 18, 16, 0.07) 1px, transparent 1px),
        linear-gradient(90deg, rgba(20, 18, 16, 0.07) 1px, transparent 1px);
    background-size: 26px 26px;
}
.auth-standalone .auth-card {
    padding: clamp(1.5rem, 5vw, 2.25rem);
    background: var(--paper);
    border: var(--border);
    box-shadow: 10px 10px 0 var(--ink);
}
.auth-standalone .auth-card__brand { display: inline-flex; }

/* -------------------------------------------------------------------------
   Fields
   ------------------------------------------------------------------------- */
.field { display: grid; gap: 0.35rem; min-width: 0; }

.field > label,
.field label {
    font-family: var(--font-display);
    font-size: 0.7rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.field input[type="text"],
.field input[type="email"],
.field input[type="url"],
.field input[type="password"],
.field textarea,
.field select {
    width: 100%;
    padding: 0.65rem 0.75rem;
    background: var(--paper);
    border: var(--border);
    font-size: 0.9375rem;
}
.field label input,
.field label textarea,
.field label select { margin-top: 0.35rem; }

.field textarea { resize: vertical; min-height: 4.5rem; font-family: inherit; }
.field select { appearance: none; background-image: none; cursor: pointer; }

.field input:focus, .field textarea:focus, .field select:focus {
    outline: none;
    background: var(--yellow);
    box-shadow: var(--sh-sm);
}
.field [aria-invalid="true"] { background: var(--pink); }

.field__hint { font-size: 0.78rem; opacity: 0.75; }
.field__error { font-size: 0.8rem; font-weight: 800; color: var(--danger); }

/* A hint that has turned into a refusal — the file just chosen cannot be sent.
   Full opacity and the danger colour, because the muted hint styling is
   exactly wrong for the one message on the page that must be read. */
.field__hint--error {
    margin-top: 0.5rem;
    padding: 0.6rem 0.75rem;
    border: var(--border);
    background: var(--pink);
    box-shadow: var(--sh-sm);
    color: var(--ink);
    font-weight: 700;
    opacity: 1;
}

.field-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 230px), 1fr));
    gap: 1rem 1.15rem;
    align-items: start;
}
.field--full { grid-column: 1 / -1; }
.field--narrow { max-width: 12rem; }
.field--grow { grid-column: span 2; }
.field--action { align-self: end; padding-bottom: 0.15rem; }

.switch { display: inline-flex; align-items: center; gap: 0.6rem; cursor: pointer; }
.switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.switch__box {
    display: grid;
    place-items: center;
    width: 1.6rem; height: 1.6rem;
    background: var(--paper);
    border: var(--border);
    flex: 0 0 auto;
}
.switch input:checked + .switch__box { background: var(--lime); }
.switch input:checked + .switch__box::after { content: "\2713"; font-weight: 900; }
.switch input:focus-visible + .switch__box { outline: 3px solid var(--ink); outline-offset: 2px; }
.switch span:last-child { font-family: var(--font-body); font-size: 0.9375rem; text-transform: none; letter-spacing: 0; }

/* -------------------------------------------------------------------------
   Dashboard chrome
   ------------------------------------------------------------------------- */
.dash-top {
    position: sticky;
    top: 0;
    z-index: 80;
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 0.7rem clamp(1rem, 3vw, 1.75rem);
    background: var(--paper);
    border-bottom: var(--border);
}

/* The mark is the landing page's, from assets/css/brand.css. It shrinks a
   little here — the dashboard bar is not a hero — and the word goes at the
   narrowest widths, where the dots alone still say whose site this is. */
.dash-top .brand { --brand-dot: 5px; --brand-gap: 3px; flex: none; }
.dash-top .brand__word { font-size: 1rem; }

.dash-top__right { display: flex; align-items: center; gap: 0.6rem; margin-left: auto; }

.dash-top__preview { flex: none; }
.dash-top__previewshort { display: none; }

/* -------------------------------------------------------------------------
   The account menu

   A <details>, so it needs no JavaScript: it opens on a tap, closes on Escape,
   and is reachable from the keyboard. The page it sits on has to keep working
   when a script fails to load, and sign-out is not something to lose.
   ------------------------------------------------------------------------- */
.dash-acct { position: relative; flex: none; }

.dash-acct__button {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.2rem 0.35rem 0.2rem 0.2rem;
    background: var(--paper);
    border: var(--border);
    box-shadow: var(--sh-sm);
    cursor: pointer;
    list-style: none;          /* Safari and Firefox draw a marker otherwise */
    user-select: none;
}
.dash-acct__button::-webkit-details-marker { display: none; }
/* Preview already wears the yellow, so this one answers with the lime it uses
   for "live" elsewhere — two states, two colours, neither competing to be the
   thing you press. */
.dash-acct__button:hover { background: var(--lime); }
.dash-acct[open] .dash-acct__button { background: var(--lime); }

.dash-acct__initials {
    display: grid;
    place-items: center;
    width: 1.75rem; height: 1.75rem;
    background: var(--ink);
    color: var(--paper);
    font-family: var(--font-display);
    font-size: 0.75rem;
    letter-spacing: 0.02em;
}
.dash-acct__caret { font-size: 0.7rem; }

.dash-acct__panel {
    position: absolute;
    right: 0;
    top: calc(100% + 0.5rem);
    z-index: 90;
    display: grid;
    gap: 0.5rem;
    width: max-content;
    min-width: 13rem;
    max-width: min(18rem, calc(100vw - 2rem));
    padding: 0.85rem;
    background: var(--paper);
    border: var(--border);
    box-shadow: var(--sh);
}

.dash-acct__who { display: grid; gap: 0.1rem; padding-bottom: 0.6rem; border-bottom: 2px solid var(--ink); }
.dash-acct__name { font-weight: 800; font-size: 0.9rem; overflow-wrap: anywhere; }
.dash-acct__mail {
    font-family: var(--font-mono);
    font-size: 0.72rem;
    opacity: 0.7;
    overflow-wrap: anywhere;
}

.dash-shell {
    display: grid;
    grid-template-columns: 15rem minmax(0, 1fr);
    align-items: start;
    gap: clamp(1.25rem, 3vw, 2.25rem);
    padding: clamp(1.25rem, 3vw, 2.25rem);
    max-width: 1400px;
    margin-inline: auto;
}

.dash-nav {
    position: sticky;
    top: 5.5rem;
    padding: 0.9rem;
    background: var(--paper);
    border: var(--border);
    box-shadow: var(--sh-sm);
}
.dash-nav ul { display: grid; gap: 0.25rem; }

.dash-nav__link {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.55rem 0.7rem;
    border: 2px solid transparent;
    font-weight: 700;
    font-size: 0.9rem;
    text-decoration: none;
}
.dash-nav__link:hover { background: var(--paper-2); border-color: var(--ink); }
.dash-nav__link.is-current { background: var(--yellow); border-color: var(--ink); box-shadow: var(--sh-sm); }
.dash-nav__icon { font-size: 0.85rem; }

.dash-nav__state {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-top: 0.9rem;
    padding-top: 0.9rem;
    border-top: 2px dashed var(--ink);
    font-family: var(--font-mono);
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
}
.dot { width: 10px; height: 10px; border: 2px solid var(--ink); border-radius: 50%; }
.dot--live { background: var(--lime); }
.dot--draft { background: var(--paper-2); }

.dash-main { min-width: 0; }

.dash-head { margin-bottom: 1.5rem; }
.dash-head__title { font-size: clamp(1.7rem, 4vw, 2.4rem); }
.dash-head__lede { margin-top: 0.4rem; font-size: 0.95rem; }

/* -------------------------------------------------------------------------
   Panels
   ------------------------------------------------------------------------- */
.panel {
    padding: clamp(1.1rem, 2.5vw, 1.6rem);
    margin-bottom: 1.5rem;
    background: var(--paper);
    border: var(--border);
    box-shadow: var(--sh);
}
.panel--yellow { background: var(--yellow); }
.panel--lime { background: var(--lime); }
.panel--warn { background: var(--paper-2); border-color: var(--danger); }

.panel__title { font-size: 1.15rem; }
.panel__lede { margin-top: 0.4rem; margin-bottom: 1rem; font-size: 0.9rem; }
.panel__title + .field-grid,
.panel__title + .repeat { margin-top: 1.1rem; }

.panel__actions { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-top: 1.25rem; }

.dash-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr));
    gap: 1.5rem;
    align-items: start;
}
.dash-grid .panel { margin-bottom: 0; }
.panel--wide { grid-column: 1 / -1; }

/* Key/value list --------------------------------------------------------- */
.kv { display: grid; gap: 0; margin-top: 1rem; border-top: 2px solid var(--ink); }
.kv > div {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    padding-block: 0.55rem;
    border-bottom: 2px solid var(--ink);
}
.kv dt {
    font-family: var(--font-mono);
    font-size: 0.68rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
}
.kv dd { font-weight: 700; font-size: 0.9rem; text-align: right; word-break: break-word; }
.kv__mono { font-family: var(--font-mono); font-weight: 400 !important; }

/* Progress --------------------------------------------------------------- */
.meter {
    height: 1.1rem;
    margin-block: 0.9rem 1.1rem;
    background: var(--paper-2);
    border: var(--border);
    overflow: hidden;
}
.meter__fill { display: block; height: 100%; width: var(--pct, 0%); background: var(--lime); }

.checklist { display: grid; gap: 0.5rem; }
.checklist__item { display: flex; align-items: center; gap: 0.6rem; font-size: 0.9rem; }
.checklist__box {
    display: grid;
    place-items: center;
    width: 1.4rem; height: 1.4rem;
    flex: 0 0 auto;
    background: var(--paper);
    border: 2px solid var(--ink);
    font-weight: 900;
}
.checklist__item.is-done .checklist__box { background: var(--lime); }
.checklist__item.is-done a { text-decoration: line-through; opacity: 0.65; }
.checklist a { text-decoration: none; border-bottom: 2px solid var(--ink); }
.checklist a:hover { background: var(--yellow); }

/* Quick links ------------------------------------------------------------ */
.quick {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 180px), 1fr));
    gap: 0.9rem;
    margin-top: 1rem;
}
.quick__card {
    display: grid;
    gap: 0.2rem;
    padding: 1rem;
    background: var(--paper-2);
    border: var(--border);
    text-decoration: none;
    transition: transform 0.14s var(--ease-pop), box-shadow 0.14s var(--ease-pop);
}
.quick__card:hover { transform: translate(-3px, -3px); box-shadow: var(--sh); background: var(--cyan); }
.quick__icon { font-size: 1.2rem; }
.quick__card strong { font-family: var(--font-display); font-size: 0.9rem; text-transform: uppercase; }
.quick__card span:last-child { font-size: 0.8rem; }

/* -------------------------------------------------------------------------
   Repeatable rows
   ------------------------------------------------------------------------- */
.repeat { display: grid; gap: 1rem; }

.repeat__row {
    padding: 0.9rem 1rem 1.1rem;
    background: var(--paper-2);
    border: var(--border);
    min-width: 0;
}
.repeat__row--tight { padding-block: 0.75rem; }

.repeat__bar {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding-bottom: 0.7rem;
    margin-bottom: 0.9rem;
    border-bottom: 2px solid var(--ink);
}
.repeat__no {
    padding: 0.1rem 0.45rem;
    background: var(--ink);
    color: var(--paper);
    font-family: var(--font-mono);
    font-size: 0.75rem;
    font-weight: 700;
}
.repeat__handle {
    font-family: var(--font-display);
    font-size: 0.85rem;
    text-transform: uppercase;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.repeat__tools { display: flex; gap: 0.35rem; margin-left: auto; }

.repeat__empty {
    padding: 1.25rem;
    background: var(--paper-2);
    border: 2px dashed var(--ink);
    font-size: 0.9rem;
    text-align: center;
}

/* -------------------------------------------------------------------------
   Picture on a row
   The control on Projects and Experience: what is chosen now, the library to
   choose from, and a file input for something new.
   ------------------------------------------------------------------------- */
.field__legend {
    display: block;
    margin-bottom: 0.35rem;
    font-family: var(--font-display);
    font-size: 0.7rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.picfield {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: 0.9rem;
    align-items: start;
    padding: 0.75rem;
    background: var(--paper);
    border: 2px solid var(--ink);
}

.picfield__thumb {
    display: block;
    width: 5.5rem;
    height: 5.5rem;
    object-fit: cover;
    border: 2px solid var(--ink);
    box-shadow: var(--sh-sm);
}

.picfield__none {
    display: grid;
    place-items: center;
    width: 5.5rem;
    height: 5.5rem;
    border: 2px dashed var(--ink);
    font-size: 0.68rem;
    font-weight: 800;
    text-align: center;
    text-transform: uppercase;
    opacity: 0.6;
}

.picfield__controls { display: grid; gap: 0.6rem; min-width: 0; }

.picfield__pick > summary {
    cursor: pointer;
    font-size: 0.78rem;
    font-weight: 800;
}

.picfield__count {
    display: inline-block;
    margin-left: 0.3rem;
    padding: 0 0.35rem;
    background: var(--ink);
    color: var(--paper);
    font-family: var(--font-mono);
    font-size: 0.68rem;
}

.picfield__grid {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin-top: 0.6rem;
}

/* The radio itself is invisible but still focusable, so the box around it can
   show both the choice and the focus ring. */
.picfield__opt input {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
}

.picfield__optbox {
    display: grid;
    place-items: center;
    width: 3.4rem;
    height: 3.4rem;
    background: var(--paper-2);
    border: 2px solid var(--ink);
    cursor: pointer;
    font-size: 0.62rem;
    font-weight: 800;
    text-transform: uppercase;
}
.picfield__optbox img { width: 100%; height: 100%; object-fit: cover; }

.picfield__opt input:checked + .picfield__optbox {
    outline: 3px solid var(--ink);
    outline-offset: 2px;
    box-shadow: var(--sh-sm);
}
.picfield__opt input:focus-visible + .picfield__optbox {
    outline: 3px solid var(--danger);
    outline-offset: 2px;
}

.picfield__uploadlabel {
    display: block;
    margin-bottom: 0.25rem;
    font-size: 0.72rem;
    font-weight: 800;
    text-transform: uppercase;
}
.picfield__upload input[type="file"] { font-size: 0.82rem; max-width: 100%; }

@media (max-width: 34rem) {
    .picfield { grid-template-columns: minmax(0, 1fr); }
}

.form-actions { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-top: 1.25rem; }
.form-actions--sticky {
    position: sticky;
    bottom: 0;
    z-index: 20;
    padding: 0.85rem;
    margin-top: 1.5rem;
    background: var(--paper);
    border: var(--border);
    box-shadow: var(--sh);
}
.form-note { margin-top: 0.85rem; font-size: 0.8rem; opacity: 0.75; }

/* -------------------------------------------------------------------------
   Template picker
   ------------------------------------------------------------------------- */
.picker {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
    gap: 1.25rem;
}

.picker__card {
    display: flex;
    flex-direction: column;
    padding: 0.9rem;
    background: var(--paper);
    border: var(--border);
    box-shadow: var(--sh-sm);
    cursor: pointer;
    transition: transform 0.14s var(--ease-pop), box-shadow 0.14s var(--ease-pop);
}
.picker__card:hover { transform: translate(-3px, -3px); box-shadow: var(--sh); }
.picker__card.is-current { background: var(--yellow); }
.picker__card:focus-within { outline: 3px solid var(--ink); outline-offset: 3px; }

.picker__radio { position: absolute; opacity: 0; width: 0; height: 0; }

.picker__frame { display: block; border: 2px solid var(--ink); }
.picker__frame .tpl-preview { height: 170px; border: none; }

.picker__body { display: block; padding: 0.9rem 0.2rem 0.8rem; }
.picker__name { display: block; font-family: var(--font-display); font-size: 1.05rem; text-transform: uppercase; }
.picker__kind {
    display: block;
    font-family: var(--font-mono);
    font-size: 0.68rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
}
.picker__blurb { display: block; margin-top: 0.6rem; font-size: 0.875rem; }

.picker__traits { display: flex; flex-wrap: wrap; gap: 0.3rem; margin-top: 0.75rem; }
.picker__traits span {
    padding: 0.15rem 0.45rem;
    background: var(--paper);
    border: 2px solid var(--ink);
    font-size: 0.68rem;
    font-weight: 700;
}

.picker__state {
    margin-top: auto;
    padding: 0.55rem;
    background: var(--ink);
    color: var(--paper);
    font-family: var(--font-display);
    font-size: 0.72rem;
    text-align: center;
    text-transform: uppercase;
}
.picker__card.is-current .picker__state { background: var(--lime); color: var(--ink); }
.picker__radio:checked ~ .picker__state { background: var(--lime); color: var(--ink); }

/* The card you have just tapped, before you apply it. Without this the only
   feedback is the same green tag the template already in use wears, so on a
   phone — where the card you tapped is often the only one on screen — there is
   nothing to tell the two apart. */
/* The literal dash, not `\2014`: a space is how a CSS escape ends, so the one
   meant to follow the dash was eaten and it came out as "—not applied yet". */
.picker__radio:checked ~ .picker__state::after {
    content: " — not applied yet";
    font-size: 0.9em;
}
.picker__card.is-current .picker__radio:checked ~ .picker__state::after { content: ''; }

/* -------------------------------------------------------------------------
   The Design page's actions

   Only the submit is pinned. The links are somewhere to go rather than
   something to do here, so they sit in the flow of the page and are met at the
   end of it.
   ------------------------------------------------------------------------- */
.design-more { margin-top: 1.5rem; }

.design-apply { align-items: center; }
.design-apply__now {
    font-family: var(--font-mono);
    font-size: 0.78rem;
    /* Pushed to the far end on a wide screen; on a narrow one it wraps under
       the button, where it is still the answer to "which one am I on?". */
    margin-left: auto;
    opacity: 0.75;
}
.design-apply__now strong { font-weight: 800; opacity: 1; }

/* -------------------------------------------------------------------------
   Publish
   ------------------------------------------------------------------------- */
.url-box {
    padding: 0.8rem 1rem;
    background: var(--paper);
    border: var(--border);
    box-shadow: var(--sh-sm);
    font-family: var(--font-mono);
    font-size: 0.875rem;
    word-break: break-all;
}
.url-box a { font-weight: 700; }

.slug-input { display: flex; align-items: stretch; min-width: 0; }
.slug-input input { min-width: 0; }

.slug-input__prefix,
.slug-input__suffix {
    display: grid;
    place-items: center;
    padding-inline: 0.75rem;
    background: var(--ink);
    color: var(--paper);
    border: var(--border);
    font-family: var(--font-mono);
    font-size: 0.875rem;
    white-space: nowrap;
}
.slug-input__prefix { border-right: none; }
.slug-input__suffix { border-left: none; }
.slug-input__prefix + input { border-left: none !important; }
.slug-input input:has(+ .slug-input__suffix) { border-right: none; }

@media (max-width: 520px) {
    .slug-input { flex-wrap: wrap; }
    .slug-input__prefix, .slug-input__suffix { width: 100%; padding-block: 0.35rem; }
}

.notice {
    margin-top: 1.25rem;
    padding: 0.9rem 1rem;
    background: var(--paper);
    border: 2px dashed var(--ink);
    font-size: 0.875rem;
}
.notice ul { margin-top: 0.5rem; display: grid; gap: 0.3rem; }
.notice li::before { content: "— "; }
.notice a { font-weight: 700; }

.share { display: flex; flex-wrap: wrap; gap: 0.6rem; }

/* -------------------------------------------------------------------------
   Invitations

   The only page in the dashboard that shows somebody else's design rather than
   their own content, so it stays deliberately plain: a picture, an address, and
   the buttons that send it. Nothing here is editable, and nothing should look
   as though it is.
   ------------------------------------------------------------------------- */
.panel__sub {
    margin: 1.4rem 0 0.6rem;
    font-family: var(--font-display);
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

/* Capped rather than full width. An invitation cover is a portrait photograph,
   and at the full width of a dashboard panel it fills the screen and pushes
   the address — the thing the page is actually for — below the fold. */
.inv-cover { margin: 0 0 1.1rem; }
.inv-cover img {
    display: block;
    width: 100%;
    max-width: 20rem;
    aspect-ratio: 4 / 5;
    object-fit: cover;
    border: var(--border);
    box-shadow: var(--sh);
}

/* -------------------------------------------------------------------------
   The guest book

   Numbers first, then the messages. The order is the order they are wanted in:
   somebody opening this page a week before the day wants the headcount, and
   somebody opening it the evening the invitation went out wants to read what
   people wrote.
   ------------------------------------------------------------------------- */
.rsvp-tally {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 8rem), 1fr));
    gap: 0.7rem;
    margin: 1.2rem 0;
}

.rsvp-tally__cell {
    padding: 0.9rem 0.8rem;
    background: var(--paper);
    border: var(--border);
    box-shadow: var(--sh-sm);
}

/* The one number that goes to the caterer, so it is the one that is coloured. */
.rsvp-tally__cell--yes { background: var(--lime); }

.rsvp-tally__num {
    display: block;
    font-family: var(--font-display);
    font-size: 1.9rem;
    line-height: 1;
    font-variant-numeric: tabular-nums;
}

.rsvp-tally__label {
    display: block;
    margin-top: 0.3rem;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.replies { display: grid; gap: 0.8rem; margin: 1.2rem 0 0; padding: 0; list-style: none; }

.reply {
    padding: 0.9rem 1rem;
    background: var(--paper);
    border: var(--border);
    box-shadow: var(--sh-sm);
}

/* A hidden message is still readable here — the owner has to be able to see
   what they hid — but it is visibly not what the guests see. */
.reply.is-hidden { opacity: 0.62; background: var(--shade); }

.reply__head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.45rem;
    margin: 0 0 0.4rem;
}

.reply__name { font-weight: 800; overflow-wrap: anywhere; }

.reply__badge {
    padding: 0.1rem 0.45rem;
    border: 2px solid var(--ink);
    font-size: 0.66rem;
    font-weight: 800;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    white-space: nowrap;
}

.reply__badge--hadir { background: var(--lime); }
.reply__badge--tidak { background: var(--pink); }
.reply__badge--ragu  { background: var(--yellow); }
.reply__badge--off   { background: var(--ink); color: var(--paper); }

/* Checked in at the venue — the one badge that only the day itself can add. */
.reply__badge--in { background: var(--cyan); }

.rsvp-tally__cell--in { background: var(--cyan); }

.reply__count { font-size: 0.76rem; font-weight: 700; }

/* Somebody else's words, so long ones break rather than widening the panel. */
.reply__body {
    margin: 0;
    font-size: 0.92rem;
    line-height: 1.7;
    overflow-wrap: anywhere;
    white-space: pre-line;
}

.reply__foot {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
    margin: 0.7rem 0 0;
    padding-top: 0.6rem;
    border-top: 2px solid var(--shade);
}

.reply__foot form { display: inline-flex; margin: 0; }
.reply__when { margin-right: auto; font-size: 0.74rem; color: #5A5147; }

/* -------------------------------------------------------------------------
   The share panel

   A worklist, not a table: two hundred rows the owner walks down over days,
   pressing copy. The tick is the whole interface — it answers "where did I
   stop" at a glance.
   ------------------------------------------------------------------------- */
.share-forms { display: grid; gap: 1rem; }

@media (min-width: 44rem) {
    .share-forms { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.share-upload { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; }
.share-upload input[type="file"],
.share-upload input[type="text"] { flex: 1 1 14rem; min-width: 0; }

.share-example { margin-top: 1.2rem; }
.share-example > summary {
    cursor: pointer;
    font-size: 0.78rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

/* Somebody's message with their real link in it: monospace, scroll of its
   own, never allowed to widen the page. */
.share-example__pre {
    margin: 0.7rem 0 0;
    padding: 0.9rem 1rem;
    background: var(--paper);
    border: var(--border);
    box-shadow: var(--sh-sm);
    font-family: var(--font-mono);
    font-size: 0.78rem;
    line-height: 1.7;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.guest-list {
    display: grid;
    gap: 0.45rem;
    max-height: 34rem;
    margin: 1.2rem 0 1rem;
    padding: 0 0.2rem 0 0;
    list-style: none;
    overflow-y: auto;
    overscroll-behavior: contain;
}

.guest {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.55rem 0.8rem;
    background: var(--paper);
    border: var(--border);
    box-shadow: var(--sh-sm);
}

/* The tick pre-exists and is invisible, so ticking a row moves nothing —
   a list that reflows under the cursor loses the owner their place. */
.guest__tick {
    width: 1.2rem;
    font-weight: 800;
    color: var(--ink);
    opacity: 0;
}

.guest.is-done { background: var(--lime); }
.guest.is-done .guest__tick { opacity: 1; }

.guest__name { flex: 1 1 auto; min-width: 0; font-weight: 700; overflow-wrap: anywhere; }

.guest__actions { display: inline-flex; align-items: center; gap: 0.3rem; }
.guest__actions form { display: inline-flex; margin: 0; }

.share-clear { display: flex; flex-wrap: wrap; align-items: center; gap: 0.7rem; }
.share-clear .form-note { margin: 0; }

/* -------------------------------------------------------------------------
   Photo
   ------------------------------------------------------------------------- */
.photo-current {
    display: grid;
    grid-template-columns: minmax(0, 11rem) minmax(0, 1fr);
    gap: 1.25rem;
    align-items: start;
    margin-top: 1rem;
}

.photo-current__img {
    width: 100%;
    aspect-ratio: 4 / 5;
    object-fit: cover;
    object-position: center 30%;
    border: var(--border);
    box-shadow: var(--sh);
}

.photo-current__empty {
    display: grid;
    place-items: center;
    gap: 0.35rem;
    width: 100%;
    aspect-ratio: 4 / 5;
    background: var(--paper-2);
    border: 3px dashed var(--ink);
}
.photo-current__empty span {
    font-family: var(--font-display);
    font-size: 2.5rem;
    line-height: 1;
    opacity: 0.4;
}
.photo-current__empty p { font-size: 0.8rem; font-weight: 700; opacity: 0.7; }

.photo-current__side { display: grid; gap: 1rem; align-content: start; }
.photo-note { font-size: 0.875rem; }

.field input[type="file"] {
    width: 100%;
    padding: 0.6rem;
    background: var(--paper);
    border: 3px dashed var(--ink);
    font-size: 0.875rem;
    cursor: pointer;
}
.field input[type="file"]:focus { outline: none; background: var(--yellow); }
.field input[type="file"]:disabled { opacity: 0.5; cursor: not-allowed; }

.photo-themes { display: grid; gap: 0.6rem; margin-top: 0.5rem; }
.photo-themes li {
    padding: 0.7rem 0.9rem;
    background: var(--paper);
    border: 2px solid var(--ink);
    font-size: 0.875rem;
}
.photo-themes strong {
    display: block;
    font-family: var(--font-display);
    font-size: 0.85rem;
    text-transform: uppercase;
}

@media (max-width: 520px) {
    .photo-current { grid-template-columns: 1fr; }
    .photo-current__img, .photo-current__empty { max-width: 12rem; }
}

/* -------------------------------------------------------------------------
   Responsive
   ------------------------------------------------------------------------- */
/* Below a laptop the sidebar cannot be a sidebar. It becomes a row of chips
   that scrolls sideways rather than folding into a hamburger: nine sections
   behind a menu button cost two taps and a guess at what is inside, and the row
   also shows where you currently are without opening anything. */
@media (max-width: 900px) {
    /* `minmax(0, 1fr)` rather than a bare `1fr`. A bare `1fr` track keeps an
       automatic minimum of its content, and the chip row below refuses to be
       measured at less than all nine chips laid end to end — so the track grew
       to 968px and took the whole page sideways with it, scroll container or
       not. Letting the track reach zero is what makes the row scroll instead of
       the page. */
    .dash-shell { grid-template-columns: minmax(0, 1fr); padding-top: 1rem; }

    /* Nothing left to toggle. */
    .dash-top__toggle { display: none; }

    .dash-nav {
        position: static;
        display: block;

        /* A grid item defaults to `min-width: auto`; the scroller inside needs
           it to be able to give way. */
        min-width: 0;
        margin-bottom: 0.35rem;
        padding: 0.45rem;
    }

    .dash-nav ul {
        display: flex;
        gap: 0.35rem;
        overflow-x: auto;
        overscroll-behavior-x: contain;

        /* The row itself is the affordance; a scrollbar under nine chips is
           just noise. */
        scrollbar-width: none;
    }
    .dash-nav ul::-webkit-scrollbar { display: none; }

    .dash-nav li { flex: 0 0 auto; }

    .dash-nav__link {
        padding: 0.55rem 0.75rem;
        white-space: nowrap;
        background: var(--paper-2);
        border-color: var(--ink);
    }

    .dash-nav__state {
        margin-top: 0.5rem;
        padding-top: 0.5rem;
    }

    .field--grow { grid-column: auto; }
}

/* -------------------------------------------------------------------------
   Phones

   The thing that matters most here is text size. Any input under 16px makes
   iOS Safari zoom the page the moment it is focused, and it does not zoom back
   — you end up panning a form sideways to read your own answer. Nothing else
   on the page needs to grow; the fields do.
   ------------------------------------------------------------------------- */
@media (max-width: 700px) {

    /* -- fields big enough not to trigger a zoom ----------------------- */
    .field input[type="text"],
    .field input[type="email"],
    .field input[type="url"],
    .field input[type="password"],
    .field input[type="number"],
    .field input[type="search"],
    .field textarea,
    .field select {
        /* 16px exactly: the threshold iOS Safari uses. */
        font-size: 1rem;
    }

    /* -- reachable controls -------------------------------------------- */
    .btn-brut { padding: 0.8rem 1.15rem; }
    .btn-brut--sm { padding: 0.6rem 0.85rem; font-size: 0.72rem; }
    .icon-btn { width: 2.6rem; height: 2.6rem; }

    /* Row tools wrap under the label rather than squeezing it to nothing. */
    .repeat__bar { flex-wrap: wrap; }
    .repeat__tools { width: 100%; margin-left: 0; }
    .repeat__handle { flex: 1 1 auto; }

    .dash-head__title { font-size: clamp(1.5rem, 7vw, 2rem); }
    .panel { padding: 1rem; }
}

/* The save bar is worth more on a phone, not less: the form is longer than the
   screen, so without it you scroll to the bottom to keep your work. It used to
   be switched off below 620px. */
@media (max-width: 620px) {
    .form-actions--sticky {
        position: sticky;
        bottom: 0;
        padding: 0.7rem;
        margin-inline: -0.25rem;
    }
    .form-actions--sticky .btn-brut { flex: 1 1 auto; justify-content: center; }

    /* One row, and it stays one row. The pinned bar is a tax on every screenful
       of the list above it, so it holds the submit and the name of the template
       in use — and the name gets whatever is left rather than pushing the
       button onto a second line. */
    .design-apply { flex-wrap: nowrap; gap: 0.55rem; }
    .design-apply .btn-brut { flex: 0 1 auto; }
    .design-apply__now {
        min-width: 0;
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
        font-size: 0.72rem;
    }

    /* The links stack, but down here in the flow where that costs nothing. */
    .design-more .btn-brut { flex: 1 1 100%; justify-content: center; }

    /* A shorter preview so a whole card fits a phone screen with room to see
       the next one starting. Nothing is removed — the miniature is a hint at
       the layout, and 40px of it was not carrying that on its own. */
    .picker { gap: 1rem; }
    .picker__frame .tpl-preview { height: 132px; }
    .picker__body { padding: 0.7rem 0.15rem 0.6rem; }
    .picker__blurb { margin-top: 0.45rem; }
    .picker__traits { margin-top: 0.55rem; }
}

/* Narrower still, and the name of the template in use is not worth a line of
   its own next to the only button that does anything. */
@media (max-width: 26rem) {
    .design-apply__now { display: none; }

    /* The word stays. It used to be dropped here to buy room for the button and
       the menu, but 26rem is 416px and almost every phone is narrower than that
       — so the rule did not trim an edge case, it took the name off the panel
       for most of the people who ever open it. The bar shrinks around it. */
    .dash-top .brand { --brand-dot: 4px; --brand-gap: 2px; }
    .dash-top .brand__word { font-size: 0.875rem; letter-spacing: 0.5px; }
    .dash-top__right { gap: 0.4rem; }
}

/* Fingers, whatever the screen width. A 2.75rem minimum is 44px, which is the
   smallest target most guidance calls comfortable. */
@media (pointer: coarse) {
    .btn-brut,
    .dash-nav__link { min-height: 2.75rem; }

    /* The two things in the header that are not buttons and still get tapped:
       the mark, which goes home, and the account menu. Five 5px dots are a
       precise thing to hit with a thumb. */
    .dash-top .brand,
    .dash-acct__button { min-height: 2.75rem; }
    .dash-top .brand { padding-inline: 0.15rem; }

    .icon-btn { min-width: 2.6rem; min-height: 2.6rem; }
    .switch__box { width: 1.8rem; height: 1.8rem; }
}

/* -------------------------------------------------------------------------
   Auth on a phone: one screen, and only one

   Below 880px the brand panel drops away and the card lands on the yellow grid
   — and the whole thing is sized to the viewport so the page does not scroll at
   all. Signing in is four things to look at; a screen that moves under your
   thumb while you reach for the one button on it is a page pretending to be
   longer than it is.

   `svh` is the unit that makes this true rather than nearly true. `vh` on a
   phone is the height with the browser's chrome hidden, so a page sized to
   100vh is taller than what you can actually see and scrolls by exactly the
   height of the address bar. `svh` is the *small* viewport — chrome showing —
   so the page fits at the worst moment and simply has room to spare at the
   best, with no reflow when the bar slides away. The plain `vh` line before it
   is the fallback for a browser that has never heard of `svh`.
   ------------------------------------------------------------------------- */
@media (max-width: 880px) {
    /* The lock, and it is the <body> alone that does it — deliberately not
       `position: fixed`, which is the usual trick and the wrong one here. A
       fixed body does not shrink when the on-screen keyboard opens, and
       auth/complete.php has a text field to type a name into; the field would
       end up underneath the keyboard with no way to bring it back. Sizing the
       body to the small viewport means its content is never taller than what
       you can see, so there is nothing to scroll in the first place. */
    .auth-body {
        height: 100vh;
        height: 100svh;
        overflow: hidden;
        overscroll-behavior-y: none;

        /* The grid moves up here from .auth-main. When the address bar slides
           away the visible area grows past 100svh, and whatever fills that
           extra strip is the canvas — which takes its colour from the body. On
           .auth-main it left a band of plain paper along the bottom edge. */
        background-color: var(--yellow);
        background-image:
            linear-gradient(rgba(20, 18, 16, 0.07) 1px, transparent 1px),
            linear-gradient(90deg, rgba(20, 18, 16, 0.07) 1px, transparent 1px);
        background-size: 26px 26px;
    }

    .auth-split {
        grid-template-columns: 1fr;
        min-height: 0;
        height: 100%;
    }

    .auth-panel { display: none; }
    .auth-card__brand { display: inline-flex; }

    .auth-main {
        min-height: 0;
        height: 100%;
        padding: clamp(0.75rem, 3vh, 2rem) clamp(1rem, 4vw, 2rem);
        background: none;
        align-content: center;
    }

    /* includes/not-found.php wears the same body class but has no split — it is
       one card on its own. Left at min-height:100vh it would stand taller than
       a body sized in svh and lose its bottom edge to the lock. */
    .auth-standalone {
        min-height: 0;
        height: 100%;
        padding: clamp(0.75rem, 3vh, 2rem) clamp(1rem, 4vw, 2rem);
    }

    /* On the tinted background the form needs its own card.
       --auth-rhythm is every vertical gap inside it, tied to the height of the
       screen rather than fixed: the same card breathes on a tall phone and
       closes up on a short one, instead of fitting one and overflowing the
       other. */
    .auth-card {
        --auth-rhythm: clamp(0.5rem, 1.8vh, 1.5rem);

        max-height: 100%;
        padding: clamp(0.9rem, 2.4vh, 2.25rem) clamp(1.05rem, 5vw, 2rem);
        background: var(--paper);
        border: var(--border);
        box-shadow: 10px 10px 0 var(--ink);

        /* The escape hatch, and it should never open. Everything above is
           built to fit, but "fits" is a claim about a screen size and a text
           size, and a reader who has turned their text up to 200% has made
           both untrue. Rather than clip the sign-in button out of existence —
           it is the only way into the site — the card itself gives the content
           back. contain keeps that scroll from leaking out to the locked page
           behind it. */
        overflow-y: auto;
        overscroll-behavior: contain;
    }

    .auth-card__brand { margin-bottom: var(--auth-rhythm); }

    /* Three ceilings, and the smallest wins. The vh term keeps a heading off a
       short screen's budget; the vw term is what stops "What should we call
       you?" setting at 32px on a 320px phone, where it takes three lines and a
       sixth of the height before anybody has read a word. */
    .auth-card__title { font-size: min(2rem, 5.4vh, 8.6vw); }
    .auth-card__sub { margin-top: 0.35rem; font-size: 0.875rem; }

    .auth-form {
        margin-top: calc(var(--auth-rhythm) * 1.25);
        gap: var(--auth-rhythm);
    }

    /* The button is the whole page; it keeps its size. Everything around it
       gives up a little instead. */
    .auth-card .field__hint { font-size: 0.74rem; line-height: 1.45; }

    .auth-card__note {
        margin-top: var(--auth-rhythm);
        font-size: 0.78rem;
        line-height: 1.5;
    }

    .auth-card__alt {
        margin-top: var(--auth-rhythm);
        padding-top: var(--auth-rhythm);
        font-size: 0.85rem;
    }

    /* A flash sits above everything else and is the one part of the page whose
       height nobody chose — "that sign-in did not match the one this browser
       started" is three lines on a phone. Tightened so that arriving with an
       error does not push the button off the bottom. */
    .auth-card .flash {
        padding: 0.55rem 0.7rem;
        margin-bottom: var(--auth-rhythm);
        border-left-width: 7px;
        font-size: 0.875rem;
    }
}

/* The one case that still did not fit: a 320x568 phone — the oldest size still
   in real use — arriving with an error above everything else. A flash is the
   only part of these pages whose height nobody chose, and "that sign-in did not
   match the one this browser started" is three lines there, about a sixth of
   the screen.

   So on a short screen the error *replaces* the advisory paragraph rather than
   stacking on top of it. Somebody reading "that sign-in did not match" does not
   also need "all we keep from Google is your name" in the same glance, and the
   swap is scoped precisely: :has(.flash) means the note is only ever missing on
   a small screen that has something more urgent to say. With no error, every
   word is there — which is the state these pages are in almost always. */
@media (max-width: 880px) and (max-height: 620px) {
    .auth-card:has(.flash) .auth-card__note,
    .auth-card:has(.flash) .field__hint { display: none; }

    .auth-card .flash { font-size: 0.8rem; line-height: 1.35; }
}

/* Two things make one screen impossible rather than tight: a phone held
   sideways is about 360px tall, and a reader at 200% text has doubled
   everything on it. Both need the page back — WCAG 1.4.4 is not optional, and
   a locked screen with the button below the fold is worse than a scrollbar.
   So the lock lifts and the layout goes back to flowing. */
@media (max-width: 880px) and (max-height: 480px) {
    .auth-body { height: auto; min-height: 100svh; overflow: visible; }
    .auth-split { height: auto; }

    .auth-main,
    .auth-standalone { height: auto; min-height: 100svh; padding-block: 1.5rem; }

    .auth-card { max-height: none; overflow: visible; }
}

/* -------------------------------------------------------------------------
   Import from a CV
   ------------------------------------------------------------------------- */
.btn-brut--danger { background: var(--danger); color: var(--paper); }

.ai-form { display: grid; gap: 1rem; justify-items: start; }

/* The file input itself is hidden inside the label, so the whole slab is the
   target — a 44px button labelled "Choose file" is a poor thing to hand
   somebody on a phone when the entire panel could be the drop zone. */
.ai-drop {
    display: grid;
    justify-items: center;
    gap: 0.35rem;
    width: 100%;
    padding: clamp(1.5rem, 5vw, 2.5rem) 1rem;
    text-align: center;
    background: var(--paper-2);
    border: 3px dashed var(--ink);
    cursor: pointer;
    transition: background-color 0.14s ease, transform 0.14s var(--ease-pop);
}
.ai-drop:hover { background: var(--yellow); }
.ai-drop:focus-within {
    background: var(--yellow);
    outline: 3px solid var(--ink);
    outline-offset: 3px;
}
.ai-drop input[type="file"] {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
}

.ai-drop__mark { font-size: 2rem; line-height: 1; }
.ai-drop__label { font-family: var(--font-display); font-size: 1.05rem; text-transform: uppercase; }
.ai-drop__hint { font-size: 0.78rem; opacity: 0.75; }
.ai-drop__chosen {
    max-width: 100%;
    padding: 0.3rem 0.6rem;
    overflow-wrap: anywhere;
    background: var(--ink);
    color: var(--paper);
    font-family: var(--font-mono);
    font-size: 0.75rem;
}

.ai-wait {
    padding: 0.7rem 0.9rem;
    background: var(--cyan);
    border: var(--border);
    font-size: 0.85rem;
    font-weight: 700;
}

.ai-off {
    padding: 0.9rem 1rem;
    background: var(--paper-2);
    border: 3px dashed var(--ink);
    font-size: 0.9rem;
}

.ai-facts { display: grid; gap: 0.7rem; margin-top: 0.9rem; font-size: 0.875rem; }
.ai-facts li { padding-left: 1.1rem; border-left: 3px solid var(--ink); }
.ai-facts code { font-family: var(--font-mono); font-size: 0.9em; }
.ai-facts + .field__hint { margin-top: 1rem; }
.ai-facts a, .ai-off a { text-decoration: underline; text-underline-offset: 3px; }

/* =========================================================================
   Premium

   The paid tier wears purple. Yellow already means "the primary action here",
   lime means "this is the one in use" and cyan is the hover state, so the one
   colour left in the palette is the one that can mean "this costs money"
   without arguing with any of them.

   Nothing here invents a new visual language: the same 3px ink borders and
   hard shadows as everything else. The only new idea is the dashed border on
   .lockbox, which reads as an outline rather than a filled-in thing — a shape
   for "not yours yet" that does not need the word.
   ========================================================================= */
.tag-premium {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.12rem 0.45rem;
    background: var(--purple);
    border: 2px solid var(--ink);
    font-family: var(--font-display);
    font-size: 0.62rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    white-space: nowrap;
    vertical-align: middle;
}
.tag-premium--sm { font-size: 0.55rem; padding: 0.08rem 0.35rem; }

/* The banner over a picker that has locked cards in it. -------------------- */
.plan-note {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem 1rem;
    padding: 0.85rem 1.1rem;
    margin-bottom: 1.5rem;
    background: var(--purple);
    border: var(--border);
    box-shadow: var(--sh-sm);
}
.plan-note__text { display: flex; align-items: center; gap: 0.5rem; min-width: 0; font-size: 0.88rem; }
.plan-note__mark { flex-shrink: 0; font-size: 1.1rem; }

/* A template you cannot pick yet ------------------------------------------ */
.picker__card.is-locked { cursor: default; }

/* No lift on hover: the card does not respond because it is not a control.
   The shift from "this reacts to me" to "this does not" is doing the same job
   as the label, and it does it before anybody reads a word. */
.picker__card.is-locked:hover { transform: none; box-shadow: var(--sh-sm); }
.picker__card.is-locked .picker__frame { opacity: 0.72; }

.picker__state--locked {
    display: flex;
    align-items: stretch;
    padding: 0;
    background: transparent;
}
.picker__state--locked a {
    flex: 1;
    padding: 0.55rem 0.4rem;
    background: var(--paper-2);
    border: 2px solid var(--ink);
    font-family: var(--font-display);
    font-size: 0.7rem;
    text-align: center;
    text-transform: uppercase;
    text-decoration: none;
}
.picker__state--locked a + a { border-left: none; }
.picker__state--locked a:hover { background: var(--cyan); }
.picker__unlock { background: var(--purple); }
.picker__state--locked .picker__unlock:hover { background: var(--ink); color: var(--paper); }

.design-apply__kept { display: block; margin-top: 0.35rem; font-family: var(--font-body); opacity: 0.8; }

/* A field the free plan cannot fill in ------------------------------------ */
.lockbox {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem 0.75rem;
    padding: 0.7rem 0.9rem;
    background: var(--paper);
    border: 3px dashed var(--ink);
}
.lockbox__mark { font-size: 1.05rem; }
.lockbox__text { flex: 1; min-width: 12rem; font-size: 0.82rem; }
.lockbox__link {
    padding: 0.3rem 0.6rem;
    background: var(--purple);
    border: 2px solid var(--ink);
    font-family: var(--font-display);
    font-size: 0.65rem;
    text-transform: uppercase;
    text-decoration: none;
    white-space: nowrap;
}
.lockbox__link:hover { background: var(--ink); color: var(--paper); }

.field__ok { display: block; margin-top: 0.2rem; font-weight: 700; color: var(--ok); }

/* The Premium page -------------------------------------------------------- */
.plan-hero { background: var(--purple); }
.plan-hero--on { background: var(--lime); }

.plan-hero__eyebrow {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    font-family: var(--font-mono);
    font-size: 0.7rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
}
.plan-hero__mark { font-size: 1rem; }
.plan-hero__title {
    margin-top: 0.5rem;
    font-family: var(--font-display);
    font-size: clamp(1.5rem, 4vw, 2.4rem);
    text-transform: uppercase;
}
.plan-hero__lede { max-width: 46rem; margin-top: 0.75rem; }
.plan-hero__note { max-width: 46rem; margin-top: 0.9rem; font-size: 0.85rem; }
.plan-hero__fine { max-width: 46rem; margin-top: 0.9rem; font-size: 0.78rem; opacity: 0.75; }
.plan-hero__note a, .plan-hero__fine a { text-decoration: underline; text-underline-offset: 3px; }

.plan-features {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
    gap: 1rem;
    margin-top: 1.1rem;
}
.plan-feature { padding: 1rem; background: var(--paper-2); border: var(--border); }
.plan-feature__icon { display: block; font-size: 1.3rem; }
.plan-feature__title {
    margin-top: 0.4rem;
    font-family: var(--font-display);
    font-size: 0.95rem;
    text-transform: uppercase;
}
.plan-feature__blurb { margin-top: 0.4rem; font-size: 0.85rem; }

.panel__count {
    display: inline-block;
    padding: 0.05rem 0.4rem;
    margin-left: 0.35rem;
    background: var(--ink);
    color: var(--paper);
    font-size: 0.7rem;
    vertical-align: middle;
}

.plan-tpl {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
    gap: 1rem;
    margin-top: 1.1rem;
}
.plan-tpl__card {
    display: flex;
    flex-direction: column;
    padding: 0.8rem;
    background: var(--paper-2);
    border: var(--border);
}
.plan-tpl__card.is-current { background: var(--lime); }
.plan-tpl__frame { display: block; border: 2px solid var(--ink); }
.plan-tpl__frame .tpl-preview { height: 150px; border: none; }
.plan-tpl__name {
    margin-top: 0.7rem;
    font-family: var(--font-display);
    font-size: 1rem;
    text-transform: uppercase;
}
.plan-tpl__kind {
    font-family: var(--font-mono);
    font-size: 0.66rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    opacity: 0.75;
}
.plan-tpl__blurb { margin-top: 0.4rem; font-size: 0.82rem; }
.plan-tpl__link {
    margin-top: auto;
    padding-top: 0.7rem;
    font-family: var(--font-display);
    font-size: 0.7rem;
    text-transform: uppercase;
    text-decoration: underline;
    text-underline-offset: 3px;
}

.plan-facts { display: grid; gap: 0.8rem; margin-top: 0.4rem; font-size: 0.88rem; }
.plan-facts li { padding-left: 1.1rem; border-left: 3px solid var(--ink); }

/* The card on the Overview ------------------------------------------------ */
.plan-promo {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    background: var(--purple);
}
.plan-promo__text { flex: 1; min-width: min(100%, 18rem); }
.plan-promo__mark { margin-right: 0.3rem; }
.plan-promo .panel__lede { margin-bottom: 0; }

@media (max-width: 620px) {
    /* No wrapping. The bar holds the mark, one button and the account menu,
       which fit a 320px screen with room to spare — the old layout put five
       things in here and let them fall onto five lines. */
    .dash-top { flex-wrap: nowrap; gap: 0.6rem; padding-inline: 0.85rem; }
    .dash-top__previewfull { display: none; }
    .dash-top__previewshort { display: inline; }

    .field--narrow { max-width: none; }

    /* The button is the point of the page; at this width it gets the width. */
    .ai-form > .btn-brut { width: 100%; text-align: center; }

    /* Anything that was a row beside its button becomes two stacked blocks. */
    .plan-note { flex-direction: column; align-items: flex-start; }
    .plan-note .btn-brut,
    .plan-promo .btn-brut,
    .lockbox__link { width: 100%; text-align: center; }
}

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.001ms !important;
        transition-duration: 0.001ms !important;
    }
}
