/* ==========================================================================
   Memori - stylesheet
   Mobile first. Relative units throughout. No external requests: the font stack
   is the system one, which also means no Google Fonts and no GDPR problem.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Reset
   -------------------------------------------------------------------------- */

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

html {
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
    scrollbar-gutter: stable;
}

body {
    margin: 0;
    font-family: var(--font);
    font-size: 1rem;
    line-height: 1.6;
    color: var(--fg);
    background: var(--bg);
    /* Kills the 300 ms tap delay without disabling pinch zoom on the page. */
    touch-action: manipulation;
    -webkit-tap-highlight-color: transparent;
    overflow-wrap: break-word;
}

h1, h2, h3, h4, p, figure, blockquote, dl, dd, ul, ol {
    margin: 0;
}

ul, ol {
    padding: 0;
    list-style: none;
}

img, video, svg, canvas {
    display: block;
    max-width: 100%;
    height: auto;
}

button, input, select, textarea {
    font: inherit;
    color: inherit;
}

a {
    color: var(--accent-text);
    text-decoration-thickness: 0.08em;
    text-underline-offset: 0.15em;
}

/* --------------------------------------------------------------------------
   2. Design tokens
   -------------------------------------------------------------------------- */

:root {
    --font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

    --accent: hsl(248 62% 56%);

    /* Light theme */
    --bg: #f6f7f9;
    --bg-sunken: #eceef2;
    --surface: #ffffff;
    --surface-raised: #ffffff;
    --surface-overlay: rgba(255, 255, 255, 0.86);
    --fg: #171a1f;
    --fg-muted: #5b6472;
    --fg-subtle: #838d9b;
    --line: #e0e4ea;
    --line-strong: #c9d0da;

    --accent-text: color-mix(in srgb, var(--accent) 82%, #000 18%);
    --accent-contrast: #ffffff;
    --accent-soft: color-mix(in srgb, var(--accent) 12%, transparent);

    --ok: #0f8a4f;
    --warn: #a4680b;
    --danger: #c62828;
    --info: #1565c0;

    --radius-sm: 0.4rem;
    --radius: 0.75rem;
    --radius-lg: 1.15rem;
    --radius-pill: 999px;

    --shadow-sm: 0 1px 2px rgba(16, 20, 28, 0.06), 0 1px 3px rgba(16, 20, 28, 0.05);
    --shadow: 0 2px 6px rgba(16, 20, 28, 0.07), 0 8px 24px rgba(16, 20, 28, 0.07);
    --shadow-lg: 0 12px 40px rgba(16, 20, 28, 0.18);

    --ease: cubic-bezier(0.2, 0.8, 0.2, 1);
    --ease-out: cubic-bezier(0.16, 1, 0.3, 1);

    /* Touch target floor. Every interactive element respects this. */
    --tap: 2.75rem;

    --safe-top: env(safe-area-inset-top, 0px);
    --safe-bottom: env(safe-area-inset-bottom, 0px);
    --safe-left: env(safe-area-inset-left, 0px);
    --safe-right: env(safe-area-inset-right, 0px);

    --content-width: 68rem;
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --bg: #0e1014;
        --bg-sunken: #090b0e;
        --surface: #171a20;
        --surface-raised: #1d2128;
        --surface-overlay: rgba(16, 18, 22, 0.88);
        --fg: #e7eaef;
        --fg-muted: #9aa4b2;
        --fg-subtle: #737e8d;
        --line: #262b33;
        --line-strong: #363d47;

        --accent-text: color-mix(in srgb, var(--accent) 62%, #fff 38%);
        --accent-soft: color-mix(in srgb, var(--accent) 20%, transparent);

        --ok: #34c77b;
        --warn: #f0b429;
        --danger: #ff6b6b;
        --info: #63a4ff;

        --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
        --shadow: 0 2px 8px rgba(0, 0, 0, 0.45), 0 12px 32px rgba(0, 0, 0, 0.35);
        --shadow-lg: 0 16px 48px rgba(0, 0, 0, 0.6);
    }
}

/* Explicit choice from the theme toggle wins over the system preference. */
:root[data-theme="dark"] {
    --bg: #0e1014;
    --bg-sunken: #090b0e;
    --surface: #171a20;
    --surface-raised: #1d2128;
    --surface-overlay: rgba(16, 18, 22, 0.88);
    --fg: #e7eaef;
    --fg-muted: #9aa4b2;
    --fg-subtle: #737e8d;
    --line: #262b33;
    --line-strong: #363d47;
    --accent-text: color-mix(in srgb, var(--accent) 62%, #fff 38%);
    --accent-soft: color-mix(in srgb, var(--accent) 20%, transparent);
    --ok: #34c77b;
    --warn: #f0b429;
    --danger: #ff6b6b;
    --info: #63a4ff;
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
    --shadow: 0 2px 8px rgba(0, 0, 0, 0.45), 0 12px 32px rgba(0, 0, 0, 0.35);
    --shadow-lg: 0 16px 48px rgba(0, 0, 0, 0.6);
}

/* --------------------------------------------------------------------------
   3. Utilities
   -------------------------------------------------------------------------- */

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

.skip-link {
    position: absolute;
    top: 0;
    left: 50%;
    z-index: 100;
    padding: 0.6rem 1rem;
    background: var(--surface);
    border-radius: 0 0 var(--radius-sm) var(--radius-sm);
    transform: translate(-50%, -110%);
    transition: transform 0.15s var(--ease);
}

.skip-link:focus-visible {
    transform: translate(-50%, 0);
}

:where(a, button, [tabindex], input, select, textarea, summary):focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}

.stack > * + * {
    margin-top: var(--stack-gap, 1rem);
}

/* --- Layout utilities ----------------------------------------------------
   These exist because the Content-Security-Policy has no 'unsafe-inline' for
   style-src, which blocks `style` attributes as well as inline <style> blocks.
   A style attribute in a template is silently dropped by the browser, so every
   one-off rule needs a class. */

.mt-0  { margin-top: 0; }
.mt-xs { margin-top: 0.35rem; }
.mt-sm { margin-top: 0.5rem; }
.mt-md { margin-top: 0.75rem; }
.mt-lg { margin-top: 1rem; }
.mt-xl { margin-top: 1.25rem; }
.mt-2xl { margin-top: 1.5rem; }

.mb-0  { margin-bottom: 0; }
.mb-xs { margin-bottom: 0.35rem; }
.mb-sm { margin-bottom: 0.6rem; }
.mb-md { margin-bottom: 0.75rem; }
.mb-lg { margin-bottom: 1rem; }
.mb-xl { margin-bottom: 1.25rem; }
.mb-2xl { margin-bottom: 1.5rem; }

.text-center { text-align: center; }
.text-warn { color: var(--warn); }
.text-danger { color: var(--danger); }
.flex-1 { flex: 1; }
.align-start { align-items: flex-start; }

/* Vertically centred single panel, used by the login, PIN and error screens. */
.screen {
    display: grid;
    place-items: center;
    min-height: 70dvh;
}

.screen--tall { min-height: 75dvh; }

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

.panel--wide { max-width: 26rem; }

.center-block {
    display: grid;
    place-items: center;
}

/* Fills its parent and centres, for a placeholder icon inside a fixed box. */
.fill-center {
    display: grid;
    place-items: center;
    height: 100%;
}

.badge-circle {
    display: grid;
    place-items: center;
    width: 3.25rem;
    height: 3.25rem;
    background: var(--accent-soft);
    border-radius: 50%;
}

.title-lg {
    font-size: 1.5rem;
    font-weight: 800;
    letter-spacing: -0.025em;
}

.title-md {
    font-size: 1.4rem;
    font-weight: 800;
    letter-spacing: -0.02em;
}

.title-sm {
    font-size: 1.35rem;
    font-weight: 700;
}

.mb-xxs { margin-bottom: 0.25rem; }

/* Smaller trailing figure, e.g. "12 / 3" where 3 is the failure count. */
.stat__value-suffix { font-size: 0.7em; }

.lede {
    max-width: 28rem;
    margin: 0.75rem auto 0;
}

.error-code {
    font-size: clamp(3rem, 14vw, 5rem);
    font-weight: 800;
    line-height: 1;
    color: var(--fg-subtle);
}

/* Access code field: wide tracking makes a short code easier to check. */
.code-input {
    text-align: center;
    font-size: 1.25rem;
    letter-spacing: 0.15em;
}

.empty-state--slim { padding: 1.5rem 0; }

.install-hint { margin: 2rem 1rem 0; }

/*
 * The icon sprite. It must not be hidden with a style attribute: the CSP drops
 * that, the reset's `svg { display: block }` then applies, and the sprite takes
 * up 150 px at the top of every page.
 */
.icon-sprite {
    display: none;
}

.muted {
    color: var(--fg-muted);
}

.subtle {
    color: var(--fg-subtle);
    font-size: 0.875rem;
}

.mono {
    font-family: var(--font-mono);
    font-size: 0.85em;
    word-break: break-all;
}

.nowrap {
    white-space: nowrap;
}

.hidden {
    display: none !important;
}

.main {
    width: 100%;
    max-width: var(--content-width);
    margin-inline: auto;
    padding: 1rem max(1rem, var(--safe-left)) calc(3rem + var(--safe-bottom)) max(1rem, var(--safe-right));
}

.main--wide {
    max-width: none;
}

/* --------------------------------------------------------------------------
   4. Buttons
   -------------------------------------------------------------------------- */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    min-height: var(--tap);
    min-width: var(--tap);
    padding: 0.6rem 1.1rem;
    font-size: 1rem;
    font-weight: 600;
    line-height: 1.2;
    color: var(--accent-contrast);
    text-align: center;
    text-decoration: none;
    cursor: pointer;
    background: var(--accent);
    /* No border by default: hierarchy comes from weight and spacing. */
    border: 0;
    border-radius: var(--radius-pill);
    transition: transform 0.14s var(--ease), filter 0.14s var(--ease), background-color 0.14s var(--ease);
}

.btn:hover:not(:disabled) {
    filter: brightness(1.08);
}

.btn:active:not(:disabled) {
    transform: scale(0.97);
}

.btn:disabled,
.btn[aria-disabled="true"] {
    opacity: 0.5;
    cursor: not-allowed;
}

.btn--block {
    width: 100%;
}

.btn--lg {
    min-height: 3.25rem;
    padding-inline: 1.5rem;
    font-size: 1.0625rem;
}

.btn--ghost {
    color: var(--fg);
    background: transparent;
    box-shadow: inset 0 0 0 1px var(--line-strong);
}

.btn--ghost:hover:not(:disabled) {
    background: var(--bg-sunken);
    filter: none;
}

.btn--quiet {
    color: var(--fg-muted);
    background: transparent;
}

.btn--quiet:hover:not(:disabled) {
    color: var(--fg);
    background: var(--bg-sunken);
    filter: none;
}

.btn--danger {
    background: var(--danger);
}

.btn--icon {
    padding: 0.55rem;
    border-radius: var(--radius-pill);
}

.btn .icon {
    width: 1.25em;
    height: 1.25em;
    fill: currentColor;
    flex: none;
}

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

.btn-row--end {
    justify-content: flex-end;
}

/* --------------------------------------------------------------------------
   5. Forms
   -------------------------------------------------------------------------- */

.field {
    display: block;
    margin-bottom: 1.15rem;
}

.field > .label,
.field > label {
    display: block;
    margin-bottom: 0.35rem;
    font-size: 0.9375rem;
    font-weight: 600;
}

.field .hint {
    display: block;
    margin-top: 0.35rem;
    font-size: 0.875rem;
    font-weight: 400;
    color: var(--fg-muted);
}

.input,
.select,
.textarea {
    width: 100%;
    min-height: var(--tap);
    padding: 0.65rem 0.8rem;
    /* 16px minimum: anything smaller makes iOS Safari zoom in on focus. */
    font-size: 1rem;
    color: var(--fg);
    background: var(--surface);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius);
    transition: border-color 0.14s var(--ease), box-shadow 0.14s var(--ease);
    appearance: none;
}

.input:focus,
.select:focus,
.textarea:focus {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
    outline: none;
}

.textarea {
    min-height: 5.5rem;
    resize: vertical;
    line-height: 1.5;
}

.select {
    padding-right: 2.2rem;
    background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
        linear-gradient(135deg, currentColor 50%, transparent 50%);
    background-position: calc(100% - 1.1rem) 55%, calc(100% - 0.75rem) 55%;
    background-size: 0.35rem 0.35rem, 0.35rem 0.35rem;
    background-repeat: no-repeat;
}

.field-grid {
    display: grid;
    gap: 0 1.1rem;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr));
}

/* Switch-style checkbox with a comfortable hit area. */
.switch {
    display: flex;
    gap: 0.85rem;
    align-items: flex-start;
    padding: 0.55rem 0;
    min-height: var(--tap);
    cursor: pointer;
}

.switch input {
    position: absolute;
    opacity: 0;
    width: 1px;
    height: 1px;
}

.switch .track {
    position: relative;
    flex: none;
    width: 2.75rem;
    height: 1.6rem;
    margin-top: 0.1rem;
    background: var(--line-strong);
    border-radius: var(--radius-pill);
    transition: background-color 0.18s var(--ease);
}

.switch .track::after {
    content: "";
    position: absolute;
    inset: 0.2rem auto auto 0.2rem;
    width: 1.2rem;
    height: 1.2rem;
    background: #fff;
    border-radius: 50%;
    box-shadow: var(--shadow-sm);
    transition: transform 0.18s var(--ease-out);
}

.switch input:checked + .track {
    background: var(--accent);
}

.switch input:checked + .track::after {
    transform: translateX(1.15rem);
}

.switch input:focus-visible + .track {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.switch .switch-text {
    display: block;
    font-weight: 600;
    line-height: 1.35;
}

.switch .switch-hint {
    display: block;
    font-size: 0.875rem;
    font-weight: 400;
    color: var(--fg-muted);
}

/* Segmented control, used for the quality choice. */
.segmented {
    display: flex;
    gap: 0.25rem;
    padding: 0.25rem;
    background: var(--bg-sunken);
    border-radius: var(--radius-pill);
}

.segmented label {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 2.4rem;
    padding: 0.3rem 0.75rem;
    font-size: 0.9375rem;
    font-weight: 600;
    text-align: center;
    border-radius: var(--radius-pill);
    cursor: pointer;
    transition: background-color 0.16s var(--ease), color 0.16s var(--ease);
}

.segmented input {
    position: absolute;
    opacity: 0;
    width: 1px;
    height: 1px;
}

.segmented input:checked + span {
    color: var(--fg);
}

.segmented label:has(input:checked) {
    background: var(--surface);
    box-shadow: var(--shadow-sm);
}

.segmented label:has(input:focus-visible) {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}

/* --------------------------------------------------------------------------
   6. Cards, flashes, badges
   -------------------------------------------------------------------------- */

.card {
    padding: clamp(1rem, 3.5vw, 1.5rem);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
}

.card + .card {
    margin-top: 1rem;
}

.card__title {
    margin-bottom: 0.75rem;
    font-size: 1.0625rem;
    font-weight: 700;
    letter-spacing: -0.01em;
}

.flash-stack {
    display: grid;
    gap: 0.6rem;
    margin-bottom: 1.25rem;
}

.flash {
    display: flex;
    gap: 0.7rem;
    align-items: flex-start;
    padding: 0.75rem 0.9rem;
    font-size: 0.9375rem;
    border-radius: var(--radius);
    background: var(--surface);
    box-shadow: inset 0 0 0 1px var(--line);
}

.flash--success { color: var(--ok); background: color-mix(in srgb, var(--ok) 10%, var(--surface)); }
.flash--error   { color: var(--danger); background: color-mix(in srgb, var(--danger) 10%, var(--surface)); }
.flash--info    { color: var(--info); background: color-mix(in srgb, var(--info) 10%, var(--surface)); }

.flash .flash-close {
    margin-left: auto;
    padding: 0.1rem 0.4rem;
    color: inherit;
    background: none;
    border: 0;
    cursor: pointer;
    opacity: 0.65;
}

.badge {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.15rem 0.55rem;
    font-size: 0.8125rem;
    font-weight: 600;
    border-radius: var(--radius-pill);
    background: var(--bg-sunken);
    color: var(--fg-muted);
    white-space: nowrap;
}

.badge--warn   { color: var(--warn); background: color-mix(in srgb, var(--warn) 14%, transparent); }
.badge--ok     { color: var(--ok); background: color-mix(in srgb, var(--ok) 14%, transparent); }
.badge--danger { color: var(--danger); background: color-mix(in srgb, var(--danger) 14%, transparent); }
.badge--accent { color: var(--accent-text); background: var(--accent-soft); }

/* --------------------------------------------------------------------------
   7. App bar
   -------------------------------------------------------------------------- */

.appbar {
    position: sticky;
    top: 0;
    z-index: 30;
    padding: calc(0.6rem + var(--safe-top)) max(1rem, var(--safe-left)) 0.6rem max(1rem, var(--safe-right));
    background: var(--surface-overlay);
    border-bottom: 1px solid var(--line);
    backdrop-filter: blur(14px) saturate(140%);
}

.appbar__inner {
    display: flex;
    gap: 0.75rem;
    align-items: center;
    width: 100%;
    max-width: var(--content-width);
    margin-inline: auto;
}

.appbar__title {
    flex: 1;
    min-width: 0;
    font-size: 1.0625rem;
    font-weight: 700;
    letter-spacing: -0.015em;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.appbar__sub {
    display: block;
    font-size: 0.8125rem;
    font-weight: 400;
    color: var(--fg-muted);
    overflow: hidden;
    text-overflow: ellipsis;
}

.appbar__actions {
    display: flex;
    gap: 0.35rem;
    align-items: center;
    flex: none;
}

.icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--tap);
    height: var(--tap);
    color: var(--fg-muted);
    background: transparent;
    border: 0;
    border-radius: var(--radius-pill);
    cursor: pointer;
    transition: background-color 0.14s var(--ease), color 0.14s var(--ease);
}

.icon-btn:hover {
    color: var(--fg);
    background: var(--bg-sunken);
}

.icon-btn .icon {
    width: 1.4rem;
    height: 1.4rem;
    fill: currentColor;
}

/* --------------------------------------------------------------------------
   8. Guest hero
   -------------------------------------------------------------------------- */

.hero {
    padding: clamp(1.5rem, 6vw, 3rem) 0 clamp(1rem, 3vw, 1.75rem);
    text-align: center;
}

/* --- Cover variant -------------------------------------------------------
   A photo panel with the text sitting on top of it. On a phone it fills the
   width; on a wide screen it stays inside the content column as a rounded panel.
   A scrim keeps the title legible over any photograph. */

.hero--cover {
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    min-height: min(58dvh, 26rem);
    margin-inline: calc(-1 * max(1rem, var(--safe-left)));
    margin-top: -1rem;
    padding: clamp(3rem, 14vw, 7rem) max(1rem, var(--safe-left)) clamp(1.25rem, 4vw, 2rem);
    overflow: hidden;
    background: var(--cover-color, var(--bg-sunken));
    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
    text-align: left;
}

/*
 * The image and the scrim are taken out of flow. In flow, `height: 100%` on the
 * image would resolve against an auto-sized track and inflate the panel to the
 * photo's full height - a 1080px square turned a 26rem hero into 1200px. Out of
 * flow they size to the padding box and can never affect it.
 */
.hero__image,
.hero__scrim {
    position: absolute;
    inset: 0;
}

.hero__image {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.hero__scrim {
    /* Two stops rather than a linear fade: the title area gets enough contrast
       without dimming the whole photograph. */
    background: linear-gradient(
        to top,
        rgba(0, 0, 0, 0.78) 0%,
        rgba(0, 0, 0, 0.45) 35%,
        rgba(0, 0, 0, 0.05) 70%,
        rgba(0, 0, 0, 0.25) 100%
    );
}

.hero--cover .hero__title,
.hero--cover .hero__subtitle,
.hero--cover .hero__meta,
.hero--cover .hero__welcome {
    color: #fff;
    text-shadow: 0 1px 12px rgba(0, 0, 0, 0.5);
}

.hero--cover .hero__meta {
    justify-content: flex-start;
}

.hero--cover .hero__welcome {
    margin-inline: 0;
}

.hero__content {
    position: relative;
}

.hero__title {
    font-size: clamp(1.65rem, 7vw, 2.6rem);
    font-weight: 800;
    line-height: 1.15;
    letter-spacing: -0.03em;
    text-wrap: balance;
}

.hero__subtitle {
    margin-top: 0.5rem;
    font-size: clamp(1rem, 3.5vw, 1.15rem);
    color: var(--fg-muted);
    text-wrap: balance;
}

.hero__meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem 0.9rem;
    justify-content: center;
    margin-top: 1rem;
    font-size: 0.9375rem;
    color: var(--fg-subtle);
}

.hero__welcome {
    max-width: 34rem;
    margin: 1.25rem auto 0;
    color: var(--fg-muted);
    text-wrap: pretty;
}

/* --------------------------------------------------------------------------
   9. Upload widget
   -------------------------------------------------------------------------- */

.uploader {
    --stack-gap: 1rem;
}

.dropzone {
    display: grid;
    gap: 0.9rem;
    place-items: center;
    padding: clamp(1.5rem, 7vw, 2.75rem) 1.25rem;
    text-align: center;
    background: var(--surface);
    border: 2px dashed var(--line-strong);
    border-radius: var(--radius-lg);
    transition: border-color 0.18s var(--ease), background-color 0.18s var(--ease), transform 0.18s var(--ease);
}

.dropzone.is-dragover {
    border-color: var(--accent);
    background: var(--accent-soft);
    transform: scale(1.01);
}

.dropzone__icon {
    width: 3rem;
    height: 3rem;
    fill: var(--accent);
    opacity: 0.9;
}

.dropzone__title {
    font-size: 1.15rem;
    font-weight: 700;
    letter-spacing: -0.015em;
}

.dropzone__hint {
    font-size: 0.9375rem;
    color: var(--fg-muted);
}

.dropzone__buttons {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    justify-content: center;
    width: 100%;
}

.dropzone__buttons .btn {
    flex: 1 1 10rem;
    max-width: 16rem;
}

.dropzone input[type="file"] {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    pointer-events: none;
}

/* Quality choice */
.quality {
    display: grid;
    gap: 0.5rem;
}

.quality__hint {
    font-size: 0.875rem;
    color: var(--fg-muted);
    min-height: 1.4em;
}

/* Queue */
.queue {
    display: grid;
    gap: 0.5rem;
}

.queue-item {
    display: grid;
    grid-template-columns: 3rem 1fr auto;
    gap: 0.75rem;
    align-items: center;
    padding: 0.5rem;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    animation: slide-in 0.28s var(--ease-out) both;
}

@keyframes slide-in {
    from { opacity: 0; transform: translateY(0.4rem); }
    to   { opacity: 1; transform: none; }
}

.queue-item__thumb {
    position: relative;
    width: 3rem;
    height: 3rem;
    overflow: hidden;
    background: var(--bg-sunken);
    border-radius: var(--radius-sm);
    flex: none;
}

.queue-item__thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.queue-item__thumb .icon {
    position: absolute;
    inset: 50% auto auto 50%;
    width: 1.4rem;
    height: 1.4rem;
    fill: var(--fg-subtle);
    transform: translate(-50%, -50%);
}

.queue-item__body {
    min-width: 0;
}

.queue-item__name {
    font-size: 0.9375rem;
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.queue-item__meta {
    display: flex;
    gap: 0.5rem;
    font-size: 0.8125rem;
    color: var(--fg-muted);
}

.queue-item__bar {
    height: 0.3rem;
    margin-top: 0.35rem;
    overflow: hidden;
    background: var(--bg-sunken);
    border-radius: var(--radius-pill);
}

.queue-item__fill {
    height: 100%;
    width: 0;
    background: var(--accent);
    border-radius: inherit;
    transition: width 0.25s var(--ease);
}

.queue-item[data-state="done"] .queue-item__fill      { background: var(--ok); }
.queue-item[data-state="duplicate"] .queue-item__fill  { background: var(--warn); }
.queue-item[data-state="failed"] .queue-item__fill,
.queue-item[data-state="cancelled"] .queue-item__fill  { background: var(--danger); }

.queue-item[data-state="done"],
.queue-item[data-state="duplicate"] {
    /* Finished rows recede so attention stays on what is still running. */
    opacity: 0.7;
}

.queue-item__action {
    display: flex;
    gap: 0.2rem;
    align-items: center;
    flex: none;
}

.queue-item__status {
    font-size: 0.8125rem;
    color: var(--fg-muted);
    white-space: nowrap;
}

/* The tick on a finished upload. Used to be a style attribute inside an
   innerHTML string, which the CSP dropped without a word. */
.icon--done {
    width: 1.1rem;
    height: 1.1rem;
    fill: var(--ok);
}

/* --------------------------------------------------------------------------
   Settings tabs
   --------------------------------------------------------------------------
   Without JavaScript the strip stays hidden and every panel is visible in turn,
   which is a usable long form. app.js reveals the strip and hides the inactive
   panels, so nothing here may depend on script having run. */

.settings-tabs__list {
    display: flex;
    gap: 0.25rem;
    margin-bottom: 1rem;
    overflow-x: auto;
    /* On a narrow phone five labels do not fit. Scrolling with snap points beats
       wrapping into two rows that push the form around. */
    scroll-snap-type: x proximity;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
}

.settings-tabs__list::-webkit-scrollbar {
    display: none;
}

.settings-tabs__tab {
    flex: 0 0 auto;
    scroll-snap-align: start;
    /* 44px minimum touch target. */
    min-height: var(--tap);
    padding: 0.5rem 1rem;
    font: inherit;
    font-weight: 600;
    color: var(--fg-muted);
    background: transparent;
    border: 0;
    border-bottom: 2px solid transparent;
    border-radius: var(--radius-sm) var(--radius-sm) 0 0;
    cursor: pointer;
    white-space: nowrap;
}

.settings-tabs__tab:hover {
    color: var(--fg);
    background: var(--bg-sunken);
}

.settings-tabs__tab[aria-selected="true"] {
    color: var(--accent);
    border-bottom-color: var(--accent);
}

.settings-tabs__tab:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* Panels are separated only while they are all on screen at once. In tab mode a
   single panel shows and a divider above it would be a line to nowhere. */
.settings-tabs__panel + .settings-tabs__panel {
    margin-top: 2rem;
    padding-top: 2rem;
    border-top: 1px solid var(--line);
}

.settings-tabs--enhanced .settings-tabs__panel + .settings-tabs__panel {
    margin-top: 0;
    padding-top: 0;
    border-top: 0;
}

.settings-tabs--enhanced .settings-tabs__heading {
    display: none;
}

.settings-tabs__heading {
    margin: 0 0 1.25rem;
    font-size: 1.05rem;
    font-weight: 650;
}

/* Former album addresses, informational. Wraps rather than scrolls: a long list of
   short addresses reads better stacked than in a strip. */
.address-history {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    font-size: 0.9375rem;
}

.address-history li {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 0.6rem;
    align-items: baseline;
}

.address-history code {
    font-family: var(--font-mono);
    word-break: break-all;
}

.settings-tabs__actions {
    margin-top: 2rem;
}

.spinner {
    width: 1.1rem;
    height: 1.1rem;
    border: 2px solid var(--line-strong);
    border-top-color: var(--accent);
    border-radius: 50%;
    animation: spin 0.7s linear infinite;
}

@keyframes spin {
    to { transform: rotate(360deg); }
}

/* Sticky overall progress */
.upload-bar {
    position: sticky;
    bottom: 0;
    z-index: 25;
    display: grid;
    gap: 0.5rem;
    margin: 1rem calc(-1 * max(1rem, var(--safe-left))) calc(-1 * (3rem + var(--safe-bottom)));
    padding: 0.85rem max(1rem, var(--safe-left)) calc(0.85rem + var(--safe-bottom)) max(1rem, var(--safe-right));
    background: var(--surface-overlay);
    border-top: 1px solid var(--line);
    backdrop-filter: blur(14px) saturate(140%);
    animation: rise 0.3s var(--ease-out) both;
}

@keyframes rise {
    from { opacity: 0; transform: translateY(1rem); }
    to   { opacity: 1; transform: none; }
}

.upload-bar__row {
    display: flex;
    gap: 0.75rem;
    align-items: center;
    max-width: var(--content-width);
    margin-inline: auto;
    width: 100%;
}

.upload-bar__text {
    flex: 1;
    min-width: 0;
    font-size: 0.9375rem;
    font-variant-numeric: tabular-nums;
}

.upload-bar__eta {
    display: block;
    font-size: 0.8125rem;
    color: var(--fg-muted);
}

.upload-bar__track {
    max-width: var(--content-width);
    width: 100%;
    margin-inline: auto;
    height: 0.4rem;
    overflow: hidden;
    background: var(--bg-sunken);
    border-radius: var(--radius-pill);
}

.upload-bar__fill {
    height: 100%;
    width: 0;
    background: linear-gradient(90deg, var(--accent), color-mix(in srgb, var(--accent) 60%, #fff));
    border-radius: inherit;
    transition: width 0.3s var(--ease);
}

/* The adaptive "this is taking a while" offer. */
.slow-offer {
    display: grid;
    gap: 0.6rem;
    padding: 0.9rem 1rem;
    background: color-mix(in srgb, var(--warn) 12%, var(--surface));
    border-radius: var(--radius);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--warn) 35%, transparent);
    animation: slide-in 0.3s var(--ease-out) both;
}

.slow-offer__title {
    font-weight: 700;
}

.slow-offer__body {
    font-size: 0.9375rem;
    color: var(--fg-muted);
}

.slow-offer .btn-row {
    gap: 0.5rem;
}

/* Success panel */
.thanks {
    display: grid;
    gap: 0.75rem;
    place-items: center;
    padding: clamp(1.5rem, 6vw, 2.5rem) 1rem;
    text-align: center;
}

.thanks__check {
    display: grid;
    place-items: center;
    width: 4rem;
    height: 4rem;
    background: color-mix(in srgb, var(--ok) 16%, transparent);
    border-radius: 50%;
    animation: pop 0.4s var(--ease-out) both;
}

@keyframes pop {
    0%   { opacity: 0; transform: scale(0.6); }
    60%  { transform: scale(1.08); }
    100% { opacity: 1; transform: scale(1); }
}

.thanks__check .icon {
    width: 2rem;
    height: 2rem;
    fill: var(--ok);
}

.thanks__title {
    font-size: 1.35rem;
    font-weight: 800;
    letter-spacing: -0.02em;
}

/* --------------------------------------------------------------------------
   9b. Photo challenges
   -------------------------------------------------------------------------- */

.tasks {
    margin-top: clamp(1.25rem, 4vw, 2rem);
}

.tasks__head {
    margin-bottom: 0.75rem;
}

.tasks__title {
    font-size: 1.25rem;
    font-weight: 700;
    letter-spacing: -0.02em;
}

.tasks__intro {
    margin-top: 0.25rem;
    font-size: 0.9375rem;
    color: var(--fg-muted);
}

.tasks__list {
    display: grid;
    gap: 0.5rem;
}

.task-chip {
    display: flex;
    gap: 0.85rem;
    align-items: center;
    width: 100%;
    min-height: var(--tap);
    padding: 0.7rem 0.85rem;
    font: inherit;
    text-align: left;
    color: var(--fg);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    cursor: pointer;
    transition: border-color 0.16s var(--ease), transform 0.16s var(--ease), background-color 0.16s var(--ease);
}

.task-chip--static {
    cursor: default;
}

.task-chip:hover:not(.task-chip--static) {
    border-color: var(--line-strong);
    transform: translateY(-1px);
}

.task-chip:active:not(.task-chip--static) {
    transform: scale(0.99);
}

.task-chip.is-selected {
    border-color: var(--accent);
    background: var(--accent-soft);
}

.task-chip__mark {
    display: grid;
    place-items: center;
    flex: none;
    width: 2.25rem;
    height: 2.25rem;
    background: var(--bg-sunken);
    border-radius: 50%;
}

.task-chip__mark .icon {
    width: 1.1rem;
    height: 1.1rem;
    fill: var(--fg-muted);
}

/* Done challenges recede: the point is to draw the eye to what is still open. */
.task-chip[data-done] .task-chip__mark {
    background: color-mix(in srgb, var(--ok) 18%, transparent);
}

.task-chip[data-done] .task-chip__mark .icon {
    fill: var(--ok);
}

.task-chip[data-done] .task-chip__title {
    color: var(--fg-muted);
}

.task-chip__body {
    flex: 1;
    min-width: 0;
}

.task-chip__title {
    display: block;
    font-weight: 600;
    line-height: 1.3;
}

.task-chip__hint {
    display: block;
    font-size: 0.875rem;
    color: var(--fg-muted);
}

.task-chip__meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    align-items: center;
    flex: none;
    justify-content: flex-end;
}

.task-chip__count {
    font-size: 0.8125rem;
    color: var(--fg-subtle);
    white-space: nowrap;
}

/* --- Editor -------------------------------------------------------------- */

.task-list {
    display: grid;
    gap: 0.5rem;
    counter-reset: task;
}

.task-row {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 0.5rem 0.75rem;
    align-items: start;
    padding: 0.6rem;
    background: var(--bg-sunken);
    border-radius: var(--radius);
}

.task-row__handle {
    display: grid;
    place-items: center;
    width: 1.75rem;
    height: 2.75rem;
    font-size: 0.8125rem;
    font-weight: 700;
    color: var(--fg-subtle);
    font-variant-numeric: tabular-nums;
}

.task-row__fields {
    display: grid;
    gap: 0.35rem;
    min-width: 0;
}

.task-row__hint {
    font-size: 0.9375rem;
}

.task-row__side {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem;
    align-items: center;
    grid-column: 1 / -1;
}

@media (min-width: 44rem) {
    .task-row {
        grid-template-columns: auto 1fr auto;
    }

    .task-row__side {
        grid-column: auto;
        flex-wrap: nowrap;
    }
}

/* Eye icon doubles as the visibility checkbox. */
.task-row__active {
    display: grid;
    place-items: center;
    width: var(--tap);
    height: var(--tap);
    cursor: pointer;
    border-radius: var(--radius-pill);
}

.task-row__active input {
    position: absolute;
    opacity: 0;
    width: 1px;
    height: 1px;
}

.task-row__active .icon {
    width: 1.25rem;
    height: 1.25rem;
    fill: var(--fg-subtle);
    opacity: 0.4;
}

.task-row__active:has(input:checked) .icon {
    fill: var(--accent);
    opacity: 1;
}

.task-row__active:has(input:focus-visible) {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* Reuse the chevrons as up/down arrows. */
.task-row__up {
    transform: rotate(90deg);
}

.task-row__down {
    transform: rotate(90deg);
}

/* --------------------------------------------------------------------------
   10. Gallery
   -------------------------------------------------------------------------- */

.gallery {
    margin-top: clamp(1.5rem, 5vw, 2.5rem);
}

.gallery__head {
    display: flex;
    gap: 0.75rem;
    align-items: baseline;
    margin-bottom: 0.75rem;
}

.gallery__title {
    font-size: 1.25rem;
    font-weight: 700;
    letter-spacing: -0.02em;
}

.gallery__count {
    font-size: 0.9375rem;
    color: var(--fg-muted);
}

.date-group + .date-group {
    margin-top: 1.5rem;
}

.date-head {
    position: sticky;
    top: calc(3.5rem + var(--safe-top));
    z-index: 10;
    padding: 0.5rem 0;
    font-size: 0.9375rem;
    font-weight: 700;
    color: var(--fg-muted);
    background: linear-gradient(var(--bg) 65%, transparent);
    backdrop-filter: blur(4px);
}

/* Justified rows. Each row is a flex line whose height was computed from the
   real aspect ratios, so nothing shifts once the images decode. */
.rows {
    display: grid;
    gap: var(--gallery-gap, 0.375rem);
}

.row {
    display: flex;
    gap: var(--gallery-gap, 0.375rem);
}

.tile {
    position: relative;
    display: block;
    padding: 0;
    overflow: hidden;
    background: var(--tile-color, var(--bg-sunken));
    background-size: cover;
    background-position: center;
    border: 0;
    border-radius: var(--radius-sm);
    cursor: pointer;
    /* The flex-basis is set inline from the computed layout. */
    flex: none;
}

.tile img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 0;
    transition: opacity 0.35s var(--ease);
}

.tile img.is-loaded {
    opacity: 1;
}

@media (hover: hover) {
    .tile::after {
        content: "";
        position: absolute;
        inset: 0;
        background: rgba(0, 0, 0, 0);
        transition: background-color 0.18s var(--ease);
    }

    .tile:hover::after {
        background: rgba(0, 0, 0, 0.12);
    }
}

.tile:active {
    /* Touch feedback that does not rely on hover. */
    filter: brightness(0.92);
}

.tile__badge {
    position: absolute;
    inset: auto auto 0.4rem 0.4rem;
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.15rem 0.45rem;
    font-size: 0.75rem;
    font-weight: 600;
    color: #fff;
    background: rgba(0, 0, 0, 0.6);
    border-radius: var(--radius-sm);
    pointer-events: none;
    backdrop-filter: blur(4px);
}

.tile__badge .icon {
    width: 0.85rem;
    height: 0.85rem;
    fill: currentColor;
}

.tile__state {
    position: absolute;
    inset: 0.4rem 0.4rem auto auto;
    padding: 0.1rem 0.4rem;
    font-size: 0.7rem;
    font-weight: 700;
    color: #fff;
    background: rgba(0, 0, 0, 0.55);
    border-radius: var(--radius-sm);
    pointer-events: none;
    backdrop-filter: blur(4px);
}

/* Quieter than the processing badge: informative, not attention-seeking. */
.tile__state--own {
    background: color-mix(in srgb, var(--accent) 70%, transparent);
}

/* --- Likes ---------------------------------------------------------------
   The control sits inside the tile, which is itself a button, so it is a span
   with role="button". It keeps a 44 px hit area via padding while the visible
   heart stays small. */

.tile__like,
.lightbox__like {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.55rem;
    font-size: 0.8125rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    line-height: 1;
    color: #fff;
    cursor: pointer;
    background: none;
    border: 0;
    transition: transform 0.16s var(--ease);
}

.tile__like {
    position: absolute;
    inset: auto 0 0 auto;
    min-width: var(--tap);
    min-height: var(--tap);
    justify-content: center;
    text-shadow: 0 1px 6px rgba(0, 0, 0, 0.7);
}

.tile__like .icon,
.lightbox__like .icon {
    width: 1.35rem;
    height: 1.35rem;
    /* Outline until liked, then filled. */
    fill: none;
    stroke: currentColor;
    stroke-width: 1.8;
    filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.55));
    transition: fill 0.18s var(--ease), stroke 0.18s var(--ease);
}

.tile__like.is-liked,
.lightbox__like.is-liked {
    color: #ff5d7a;
}

.tile__like.is-liked .icon,
.lightbox__like.is-liked .icon {
    fill: currentColor;
    stroke: currentColor;
    animation: heart-pop 0.34s cubic-bezier(0.16, 1, 0.3, 1);
}

@keyframes heart-pop {
    0%   { transform: scale(1); }
    45%  { transform: scale(1.35); }
    100% { transform: scale(1); }
}

.tile__like:active,
.lightbox__like:active {
    transform: scale(0.9);
}

.tile__like:focus-visible,
.lightbox__like:focus-visible {
    outline: 2px solid #fff;
    outline-offset: -2px;
    border-radius: var(--radius-sm);
}

.lightbox__like {
    min-height: var(--tap);
    padding-inline: 0.7rem;
    font-size: 0.9375rem;
    border-radius: var(--radius-pill);
}

.lightbox__like:hover {
    background: rgba(255, 255, 255, 0.14);
}

.lightbox__like-slot {
    display: inline-flex;
    flex: none;
}

@media (prefers-reduced-motion: reduce) {
    .tile__like.is-liked .icon,
    .lightbox__like.is-liked .icon {
        animation: none;
    }
}

/* Selection mode */
.tile__check {
    position: absolute;
    inset: 0.4rem auto auto 0.4rem;
    display: grid;
    place-items: center;
    width: 1.6rem;
    height: 1.6rem;
    color: #fff;
    background: rgba(0, 0, 0, 0.35);
    border: 2px solid rgba(255, 255, 255, 0.85);
    border-radius: 50%;
    opacity: 0;
    transform: scale(0.7);
    transition: opacity 0.16s var(--ease), transform 0.16s var(--ease-out), background-color 0.16s var(--ease);
    pointer-events: none;
}

.gallery.is-selecting .tile__check {
    opacity: 1;
    transform: none;
}

.tile.is-selected .tile__check {
    background: var(--accent);
    border-color: var(--accent);
}

.tile.is-selected {
    outline: 3px solid var(--accent);
    outline-offset: -3px;
}

.tile__check .icon {
    width: 0.9rem;
    height: 0.9rem;
    fill: currentColor;
    opacity: 0;
}

.tile.is-selected .tile__check .icon {
    opacity: 1;
}

.selection-bar {
    position: fixed;
    inset: auto 0 0 0;
    z-index: 40;
    display: flex;
    gap: 0.6rem;
    align-items: center;
    padding: 0.75rem max(1rem, var(--safe-left)) calc(0.75rem + var(--safe-bottom)) max(1rem, var(--safe-right));
    background: var(--surface-overlay);
    border-top: 1px solid var(--line);
    backdrop-filter: blur(14px) saturate(140%);
    animation: rise 0.25s var(--ease-out) both;
}

.selection-bar__count {
    flex: 1;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

/* Empty and end states */
.empty-state {
    display: grid;
    gap: 0.5rem;
    place-items: center;
    padding: clamp(2rem, 8vw, 4rem) 1rem;
    text-align: center;
    color: var(--fg-muted);
}

.empty-state .icon {
    width: 2.5rem;
    height: 2.5rem;
    fill: var(--fg-subtle);
    margin-bottom: 0.5rem;
}

.empty-state__title {
    font-size: 1.15rem;
    font-weight: 700;
    color: var(--fg);
}

.gallery__sentinel {
    display: grid;
    place-items: center;
    min-height: 4rem;
    color: var(--fg-subtle);
    font-size: 0.875rem;
}

/* --------------------------------------------------------------------------
   11. Lightbox
   -------------------------------------------------------------------------- */

.lightbox {
    position: fixed;
    inset: 0;
    z-index: 90;
    display: grid;
    /*
     * minmax(0, 1fr), not plain 1fr. A bare `1fr` is `minmax(auto, 1fr)`, so the
     * row grows to fit its content and the stage never gets a definite height -
     * which makes `height: 100%` inside it behave as `auto` and lets a portrait
     * photo render at natural size and run off the bottom of the screen.
     */
    grid-template-rows: auto minmax(0, 1fr) auto;
    /* dvh, not vh: the mobile browser chrome must not crop the image. */
    height: 100dvh;
    background: #08090b;
    /* Stops the page behind from scrolling when the swipe runs out. */
    overscroll-behavior: contain;
    touch-action: none;
    animation: fade-in 0.2s var(--ease) both;
}

@keyframes fade-in {
    from { opacity: 0; }
    to   { opacity: 1; }
}

.lightbox[hidden] {
    display: none;
}

.lightbox__bar {
    display: flex;
    gap: 0.5rem;
    align-items: center;
    padding: calc(0.6rem + var(--safe-top)) max(0.75rem, var(--safe-left)) 0.6rem max(0.75rem, var(--safe-right));
    color: #f2f4f7;
    background: linear-gradient(rgba(0, 0, 0, 0.65), transparent);
    transition: opacity 0.25s var(--ease);
}

.lightbox__bar--bottom {
    padding: 0.6rem max(0.75rem, var(--safe-left)) calc(0.6rem + var(--safe-bottom)) max(0.75rem, var(--safe-right));
    background: linear-gradient(transparent, rgba(0, 0, 0, 0.65));
}

.lightbox.is-immersive .lightbox__bar {
    opacity: 0;
    pointer-events: none;
}

.lightbox__title {
    flex: 1;
    min-width: 0;
    font-size: 0.9375rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.lightbox__sub {
    display: block;
    font-size: 0.8125rem;
    opacity: 0.7;
}

.lightbox .icon-btn {
    color: #f2f4f7;
}

.lightbox .icon-btn:hover {
    background: rgba(255, 255, 255, 0.14);
}

.lightbox__stage {
    position: relative;
    overflow: hidden;
    /* Lets the grid row shrink instead of being inflated by the slide content. */
    min-height: 0;
}

/*
 * The track is absolutely positioned rather than laid out in flow. Two reasons:
 * its height becomes definite (so percentage heights below it resolve), and its
 * left edge is pinned to the stage. In flow it was a centred grid item, so the
 * 300%-wide track overhung equally on both sides and the visible window landed
 * on the *next* slide - the lightbox opened the wrong photo.
 */
.lightbox__track {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    display: flex;
    align-items: stretch;
    width: 100%;
    will-change: transform;
}

/*
 * Flex centring, not grid. On a grid item a percentage max-height resolves
 * against the grid area, and an auto-sized row grows to the image - so
 * `max-height: 100%` would mean "at most your own height" and a portrait photo
 * would render at full size and run off the screen. A flex item resolves it
 * against the container, whose height is definite here.
 */
.lightbox__slide {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 100%;
    height: 100%;
    min-height: 0;
    padding: 0.5rem;
}

.lightbox__slide img,
.lightbox__slide video {
    max-width: 100%;
    max-height: 100%;
    width: auto;
    height: auto;
    object-fit: contain;
    border-radius: 0.25rem;
}

/* Pinch and double-tap zoom happen inside this scroller, which gives native
   momentum and bounds for free. */
.lightbox__zoom {
    width: 100%;
    height: 100%;
    max-height: 100%;
    min-height: 0;
    /* Flex for the same reason as the slide: predictable percentage resolution. */
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: auto;
    overscroll-behavior: contain;
    touch-action: pinch-zoom pan-x pan-y;
    scrollbar-width: none;
}

.lightbox__zoom::-webkit-scrollbar {
    display: none;
}

.lightbox__nav {
    position: absolute;
    top: 50%;
    z-index: 2;
    width: 2.75rem;
    height: 2.75rem;
    color: #fff;
    background: rgba(0, 0, 0, 0.45);
    border: 0;
    border-radius: 50%;
    cursor: pointer;
    transform: translateY(-50%);
    transition: background-color 0.16s var(--ease), opacity 0.2s var(--ease);
}

.lightbox__nav:hover {
    background: rgba(0, 0, 0, 0.7);
}

.lightbox__nav[disabled] {
    opacity: 0.25;
    cursor: default;
}

.lightbox__nav--prev { left: 0.6rem; }
.lightbox__nav--next { right: 0.6rem; }

.lightbox__nav .icon {
    width: 1.5rem;
    height: 1.5rem;
    fill: currentColor;
    margin-inline: auto;
}

/* On touch devices the swipe gesture is the primary control. */
@media (pointer: coarse) {
    .lightbox__nav {
        display: none;
    }
}

.lightbox__counter {
    font-size: 0.875rem;
    font-variant-numeric: tabular-nums;
    opacity: 0.75;
}

/* Prevent background scroll while the lightbox is open. */
body.is-locked {
    overflow: hidden;
    /* iOS also needs the height pinned, or it scrolls under the overlay. */
    position: fixed;
    inset: 0;
    width: 100%;
}

/* --------------------------------------------------------------------------
   12. Admin and owner
   -------------------------------------------------------------------------- */

.page-head {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem 1rem;
    align-items: baseline;
    margin-bottom: 1.25rem;
}

.page-head__title {
    flex: 1;
    min-width: 12rem;
    font-size: clamp(1.4rem, 5vw, 1.9rem);
    font-weight: 800;
    letter-spacing: -0.025em;
}

/* Cover banner on the owner dashboard. Same out-of-flow trick as the guest hero. */
.owner-cover {
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    min-height: min(34dvh, 16rem);
    margin: 0 0 1.25rem;
    overflow: hidden;
    border-radius: var(--radius-lg);
    background: var(--bg-sunken);
}

.owner-cover img,
.owner-cover__scrim {
    position: absolute;
    inset: 0;
}

.owner-cover img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.owner-cover__scrim {
    background: linear-gradient(to top, rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0.1) 65%);
}

.owner-cover__text {
    position: relative;
    display: grid;
    gap: 0.15rem;
    padding: 1rem 1.15rem;
    color: #fff;
    text-shadow: 0 1px 10px rgba(0, 0, 0, 0.55);
}

.owner-cover__title {
    font-size: clamp(1.25rem, 4vw, 1.75rem);
    font-weight: 800;
    letter-spacing: -0.02em;
}

.owner-cover__sub {
    font-size: 0.9375rem;
    opacity: 0.9;
}

.stat-grid {
    display: grid;
    gap: 0.6rem;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 8.5rem), 1fr));
    margin-bottom: 1.25rem;
}

.stat {
    padding: 0.85rem 0.95rem;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
}

.stat__value {
    font-size: 1.4rem;
    font-weight: 800;
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
}

.stat__label {
    font-size: 0.8125rem;
    color: var(--fg-muted);
}

.album-list {
    display: grid;
    gap: 0.75rem;
}

.album-card {
    display: grid;
    grid-template-columns: auto 1fr auto;
    gap: 0.5rem 1rem;
    align-items: center;
    padding: 0.9rem 1rem;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    transition: border-color 0.16s var(--ease), transform 0.16s var(--ease);
}

.album-card__cover {
    display: grid;
    place-items: center;
    width: 3.5rem;
    height: 3.5rem;
    overflow: hidden;
    background: var(--bg-sunken);
    border-radius: var(--radius-sm);
    flex: none;
}

.album-card__cover img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.album-card__cover .icon {
    width: 1.4rem;
    height: 1.4rem;
    fill: var(--fg-subtle);
}

.album-card:hover {
    border-color: var(--line-strong);
    transform: translateY(-1px);
}

.album-card__title {
    font-size: 1.0625rem;
    font-weight: 700;
    letter-spacing: -0.01em;
}

.album-card__title a {
    color: inherit;
    text-decoration: none;
}

.album-card__title a:hover {
    text-decoration: underline;
}

.album-card__meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem 0.75rem;
    margin-top: 0.25rem;
    font-size: 0.875rem;
    color: var(--fg-muted);
}

.album-card__badges {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem;
    grid-column: 1 / -1;
}

@media (min-width: 40rem) {
    .album-card {
        /* cover · text · actions · badges */
        grid-template-columns: auto 1fr auto auto;
    }

    .album-card__badges {
        grid-column: auto;
    }
}

.link-box {
    display: flex;
    gap: 0.5rem;
    align-items: center;
    padding: 0.5rem 0.5rem 0.5rem 0.75rem;
    background: var(--bg-sunken);
    border-radius: var(--radius);
}

.link-box code {
    flex: 1;
    min-width: 0;
    font-family: var(--font-mono);
    font-size: 0.8125rem;
    overflow-x: auto;
    white-space: nowrap;
    scrollbar-width: none;
}

.link-box code::-webkit-scrollbar {
    display: none;
}

.table-wrap {
    /* Wide tables scroll inside their own box; the page never scrolls sideways. */
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

.table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.9375rem;
}

.table th,
.table td {
    padding: 0.55rem 0.6rem;
    text-align: left;
    border-bottom: 1px solid var(--line);
    vertical-align: top;
}

.table th {
    font-size: 0.8125rem;
    font-weight: 700;
    color: var(--fg-muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    white-space: nowrap;
}

.table tr:last-child td {
    border-bottom: 0;
}

.check-row {
    display: flex;
    gap: 0.6rem;
    align-items: baseline;
    padding: 0.35rem 0;
    border-bottom: 1px solid var(--line);
}

.check-row:last-child {
    border-bottom: 0;
}

.check-row__mark {
    flex: none;
    width: 1.25rem;
    font-weight: 800;
    text-align: center;
}

.check-row--ok .check-row__mark   { color: var(--ok); }
.check-row--warn .check-row__mark { color: var(--warn); }
.check-row--bad .check-row__mark  { color: var(--danger); }

.check-row__label {
    flex: 1;
}

.check-row__detail {
    font-size: 0.875rem;
    color: var(--fg-muted);
    text-align: right;
}

.fieldset {
    margin: 0 0 1.5rem;
    padding: 0;
    border: 0;
}

.fieldset__legend {
    padding: 0;
    margin-bottom: 0.75rem;
    font-size: 1rem;
    font-weight: 700;
}

.divider {
    height: 1px;
    margin: 1.5rem 0;
    background: var(--line);
    border: 0;
}

.tabs {
    display: flex;
    gap: 0.35rem;
    margin-bottom: 1rem;
    overflow-x: auto;
    scrollbar-width: none;
}

.tabs::-webkit-scrollbar {
    display: none;
}

.tabs a {
    flex: none;
    padding: 0.45rem 0.9rem;
    font-size: 0.9375rem;
    font-weight: 600;
    color: var(--fg-muted);
    text-decoration: none;
    background: var(--bg-sunken);
    border-radius: var(--radius-pill);
    white-space: nowrap;
}

.tabs a[aria-current="page"] {
    color: var(--accent-contrast);
    background: var(--accent);
}

.mod-grid {
    display: grid;
    gap: 0.6rem;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 9rem), 1fr));
}

.mod-card {
    display: grid;
    gap: 0.4rem;
    padding: 0.5rem;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
}

.mod-card__thumb {
    position: relative;
    aspect-ratio: 1;
    overflow: hidden;
    background: var(--bg-sunken);
    border-radius: var(--radius-sm);
}

.mod-card__thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.mod-card__meta {
    font-size: 0.75rem;
    color: var(--fg-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.mod-card__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem;
}

.mod-card__actions .btn {
    flex: 1;
    min-height: 2.25rem;
    padding-inline: 0.5rem;
    font-size: 0.8125rem;
}

.site-footer {
    padding: 2rem max(1rem, var(--safe-left)) calc(2rem + var(--safe-bottom));
    font-size: 0.875rem;
    color: var(--fg-subtle);
    text-align: center;
}

.site-footer a {
    color: inherit;
}

.site-footer nav {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem 1rem;
    justify-content: center;
    margin-bottom: 0.5rem;
}

/* --------------------------------------------------------------------------
   13. Motion and print preferences
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

@media (prefers-contrast: more) {
    :root {
        --line: var(--fg-muted);
        --line-strong: var(--fg);
    }
}

@media print {
    .appbar,
    .upload-bar,
    .selection-bar,
    .lightbox,
    .site-footer,
    .btn,
    .icon-btn {
        display: none !important;
    }

    body {
        background: #fff;
        color: #000;
    }
}
