/* ============================================================
   MEMO – Warm Premium Dark Theme
   Boutique photo-memories aesthetic · mobile-first · no Bootstrap
   ============================================================ */

/* ---- Design tokens ---- */
:root {
    /* Warm graphite surfaces */
    --bg-primary:      #141210;
    --bg-secondary:    #1a1714;
    --bg-card:         #1f1b17;
    --bg-card-hover:   #26221c;
    --bg-card-active:  #2f2a22;

    /* Soft gold accent */
    --accent:          #c9a96a;
    --accent-strong:   #b08d4f;
    --accent-soft:     #e4cf9f;
    --accent-glow:     rgba(201, 169, 106, 0.22);

    /* Warm ink / cream text */
    --text-primary:    #f5f1ea;
    --text-secondary:  #b8ae9e;
    --text-muted:      #8a8175;

    --border:          rgba(245, 241, 234, 0.08);
    --border-strong:   rgba(245, 241, 234, 0.14);
    --border-focus:    rgba(201, 169, 106, 0.55);

    --danger:          #e28a7a;

    --radius-sm:       10px;
    --radius-md:       16px;
    --radius-lg:       22px;
    --radius-xl:       30px;

    --shadow-card:     0 10px 34px rgba(0,0,0,0.42);
    --shadow-soft:     0 4px 18px rgba(0,0,0,0.30);
    --shadow-glow:     0 0 24px rgba(201,169,106,0.14);

    --font-body:       'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    --font-display:    'Fraunces', Georgia, 'Times New Roman', serif;

    --transition:      0.22s cubic-bezier(0.4, 0, 0.2, 1);
}

/* ---- Reset / Base ---- */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html {
    font-size: 16px;
    -webkit-text-size-adjust: 100%;
}

body {
    font-family: var(--font-body);
    background: var(--bg-primary);
    color: var(--text-primary);
    line-height: 1.6;
    min-height: 100vh;
    overflow-x: hidden;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

img { display: block; max-width: 100%; }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

button { cursor: pointer; border: none; background: none; font-family: inherit; font-size: inherit; color: inherit; }

/* ---- Typography ---- */
h1 { font-family: var(--font-display); font-size: clamp(2.5rem, 10vw, 5rem); font-weight: 500; letter-spacing: -0.02em; line-height: 1.05; }
h2 { font-family: var(--font-display); font-size: clamp(1.35rem, 4vw, 1.9rem); font-weight: 500; letter-spacing: -0.01em; }
h3 { font-size: 1.05rem; font-weight: 600; letter-spacing: -0.01em; }

/* ================================================================
   LANDING PAGE
   ================================================================ */
.landing {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
}

.landing-hero {
    width: 100%;
    min-height: 100svh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 2rem 1.5rem;
    background:
        radial-gradient(ellipse 90% 55% at 50% -5%, rgba(201,169,106,0.12) 0%, transparent 65%),
        radial-gradient(ellipse 70% 45% at 85% 90%, rgba(176,141,79,0.10) 0%, transparent 60%),
        var(--bg-primary);
    gap: 1.5rem;
}

.landing-logo {
    margin-bottom: 0.25rem;
}

.logo-text {
    display: block;
    font-family: var(--font-display);
    font-size: clamp(2.4rem, 13vw, 7rem);
    font-weight: 500;
    letter-spacing: 0.04em;
    background: linear-gradient(180deg, var(--accent-soft) 0%, var(--accent) 55%, var(--accent-strong) 100%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    text-shadow: 0 2px 40px rgba(201,169,106,0.18);
}

.btn-start {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 1.05rem 3.5rem;
    background: linear-gradient(180deg, var(--accent-soft), var(--accent) 60%, var(--accent-strong));
    color: #241d12;
    font-size: 1rem;
    font-weight: 700;
    letter-spacing: 0.22em;
    border-radius: 100px;
    transition: var(--transition);
    box-shadow: 0 8px 22px rgba(0,0,0,0.4), inset 0 1px 0 rgba(255,255,255,0.25);
    text-decoration: none;
    margin: 0.5rem 0;
}

.btn-start:hover {
    transform: translateY(-2px);
    box-shadow: 0 12px 30px rgba(0,0,0,0.5), 0 0 26px var(--accent-glow), inset 0 1px 0 rgba(255,255,255,0.3);
    text-decoration: none;
}

.landing-badge {
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.15em;
    color: var(--text-muted);
    padding: 0.5rem 1.5rem;
    border: 1px solid var(--border);
    border-radius: 100px;
    text-transform: uppercase;
    margin-top: 0.5rem;
}

/* ---- Landing: story + how-to ---- */
.landing-story {
    width: 100%;
    max-width: 720px;
    padding: 1rem 1.5rem 2rem;
    text-align: center;
    display: flex;
    flex-direction: column;
    gap: 1.15rem;
}

.story-subtitle {
    font-size: clamp(0.7rem, 2.5vw, 0.85rem);
    font-weight: 600;
    letter-spacing: 0.28em;
    color: var(--accent);
    text-transform: uppercase;
}

.story-lead {
    font-family: var(--font-display);
    font-size: clamp(1.25rem, 4vw, 1.75rem);
    font-weight: 500;
    line-height: 1.35;
    color: var(--text-primary);
}

.story-lead em {
    font-style: italic;
    color: var(--accent);
}

.story-text {
    font-size: 1rem;
    line-height: 1.7;
    color: var(--text-secondary);
}

.landing-how {
    width: 100%;
    max-width: 720px;
    padding: 1rem 1.5rem 4rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1.5rem;
}

.landing-how h2 {
    text-align: center;
    color: var(--text-primary);
}

.how-steps {
    list-style: none;
    width: 100%;
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
}

.how-steps li {
    display: flex;
    align-items: flex-start;
    gap: 1rem;
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    padding: 1.1rem 1.3rem;
    transition: var(--transition);
}

.how-steps li:hover {
    background: var(--bg-card-hover);
    border-color: var(--border-focus);
}

.how-num {
    flex-shrink: 0;
    width: 2rem;
    height: 2rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: linear-gradient(180deg, var(--accent-soft), var(--accent) 60%, var(--accent-strong));
    color: #241d12;
    font-weight: 700;
    font-size: 0.95rem;
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.25);
}

.how-steps li p {
    color: var(--text-secondary);
    line-height: 1.55;
    padding-top: 0.15rem;
}

.how-note {
    max-width: 620px;
    text-align: center;
    font-size: 0.95rem;
    line-height: 1.7;
    color: var(--text-muted);
}

/* ================================================================
   BUILDER
   ================================================================ */
.builder {
    min-height: 100svh;
    display: flex;
    flex-direction: column;
    background: var(--bg-primary);
}

/* Mobile-first: back arrow + five step dots + "Nyt magasin" have to share a
   375px phone, so the tighter padding and gap are the default and the roomier
   ones are restored at min-width: 480px below. */
.builder-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    /* Five step dots plus the back arrow and "Nyt magasin" stop fitting on one line somewhere
       under 320px. body carries overflow-x:hidden, so the row did not scroll — it was simply
       cut off, and "Nyt magasin" lost a third of itself past the right edge with no way to
       reach it. Wrapping costs nothing at ordinary phone widths, where it never triggers. */
    flex-wrap: wrap;
    gap: 0.4rem;
    padding: 0.75rem 0.7rem;
    background: rgba(26, 23, 20, 0.85);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border-bottom: 1px solid var(--border);
    position: sticky;
    top: calc(54px + env(safe-area-inset-top)); /* below fixed navbar */
    z-index: 100;
}

/* Just the arrow: the wordmark already sits in the navbar above this header. */
.builder-back {
    font-size: 1.25rem;
    line-height: 1;
    font-weight: 700;
    color: var(--text-secondary);
    text-decoration: none;
    padding: 0.25rem 0.5rem 0.25rem 0;
    flex-shrink: 0;
}

.builder-back:hover { color: var(--accent); text-decoration: none; }

.builder-newalbum {
    background: none;
    border: none;
    cursor: pointer;
    font-family: inherit;
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    color: var(--text-secondary);
    white-space: nowrap;
    flex-shrink: 0;
}

.builder-newalbum:hover { color: var(--accent); }

.step-indicators {
    display: flex;
    gap: 0.5rem;
    /* The dots read as one control, so they stay on a single line and keep their size: when
       the header runs out of room it is "Nyt magasin" that drops to the next line. */
    flex-shrink: 0;
}

.step-dot {
    width: 1.9rem;
    height: 1.9rem;
    border-radius: 50%;
    background: var(--bg-card);
    border: 1px solid var(--border-strong);
    color: var(--text-muted);
    font-size: 0.75rem;
    font-weight: 600;
    transition: var(--transition);
    display: flex;
    align-items: center;
    justify-content: center;
}

.step-dot.active {
    background: linear-gradient(180deg, var(--accent-soft), var(--accent) 65%, var(--accent-strong));
    border-color: var(--accent);
    color: #241d12;
    box-shadow: 0 2px 10px rgba(0,0,0,0.35), 0 0 14px var(--accent-glow);
    transform: scale(1.12);
}

.step-dot.done {
    background: rgba(201,169,106,0.12);
    border-color: rgba(201,169,106,0.45);
    color: var(--accent);
}

/* Above phone width the builder header can breathe again. The dots keep their
   full size at every width now that the back link is a single arrow. */
@media (min-width: 480px) {
    .builder-header { gap: 1rem; padding: 1rem 1.25rem; }
    .builder-newalbum { font-size: 0.8125rem; letter-spacing: 0.04em; }
}

.builder-content {
    flex: 1;
    padding: 1.25rem;
    max-width: 900px;
    width: 100%;
    margin: 0 auto;
}

.builder-nav {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 1rem;
    padding: 0.9rem 1.25rem calc(0.9rem + env(safe-area-inset-bottom));
    background: rgba(26, 23, 20, 0.9);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border-top: 1px solid var(--border);
    position: sticky;
    bottom: 0;
    z-index: 100;
}

.builder-nav .app-btn { flex: 0 1 auto; min-width: 7rem; justify-content: center; }

.builder-loading {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1rem;
    color: var(--text-secondary);
}

.spinner {
    width: 40px;
    height: 40px;
    border: 3px solid var(--border);
    border-top-color: var(--accent);
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
}

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

/* ---- StepCard ---- */
.step-card {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    padding: 1.5rem 1.35rem;
    margin-bottom: 1rem;
    box-shadow: var(--shadow-soft);
}

.step-card__title {
    font-family: var(--font-display);
    font-size: 1.5rem;
    font-weight: 500;
    letter-spacing: -0.01em;
    margin-bottom: 0.25rem;
}

.step-card__subtitle {
    color: var(--text-secondary);
    font-size: 0.85rem;
    margin-bottom: 1rem;
}

.step-card__content { margin-top: 1rem; }

/* ---- AppButton ---- */
.app-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.75rem 1.75rem;
    border-radius: 100px;
    font-weight: 700;
    font-size: 0.9rem;
    letter-spacing: 0.05em;
    transition: var(--transition);
    white-space: nowrap;
}

.app-btn--primary {
    background: linear-gradient(180deg, var(--accent-soft), var(--accent) 60%, var(--accent-strong));
    color: #241d12;
    box-shadow: 0 4px 14px rgba(0,0,0,0.35), inset 0 1px 0 rgba(255,255,255,0.25);
}

.app-btn--primary:hover:not(:disabled) {
    transform: translateY(-1px);
    box-shadow: 0 6px 18px rgba(0,0,0,0.4), 0 0 18px var(--accent-glow), inset 0 1px 0 rgba(255,255,255,0.3);
}

.app-btn--secondary {
    background: var(--bg-card-hover);
    color: var(--text-secondary);
    border: 1px solid var(--border);
}

.app-btn--secondary:hover:not(:disabled) {
    background: var(--bg-card-active);
    color: var(--text-primary);
}

.app-btn--disabled {
    opacity: 0.35;
    cursor: not-allowed;
}

.app-btn--danger {
    background: var(--danger);
    color: #241d12;
    border: 1px solid transparent;
}

.app-btn--danger:hover:not(:disabled) { filter: brightness(1.08); transform: translateY(-1px); }

.app-btn--danger-ghost {
    background: transparent;
    color: var(--danger);
    border: 1px solid var(--border-strong);
}

.app-btn--danger-ghost:hover:not(:disabled) { background: rgba(226,138,122,0.12); border-color: var(--danger); }

/* ---- Upload Zone ---- */
.upload-zone {
    border: 2px dashed var(--border);
    border-radius: var(--radius-md);
    padding: 2.5rem 1rem;
    text-align: center;
    transition: var(--transition);
    cursor: pointer;
    position: relative;
}

.upload-zone:hover, .upload-zone.drag-over {
    border-color: var(--accent);
    background: rgba(201,169,106,0.04);
}

.upload-input {
    position: absolute;
    inset: 0;
    opacity: 0;
    width: 100%;
    height: 100%;
    cursor: pointer;
}

.upload-label {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    align-items: center;
    color: var(--text-secondary);
    font-size: 0.9rem;
    pointer-events: none;
}

.upload-icon { font-size: 2rem; }

.upload-hint {
    font-size: 0.75rem;
    color: var(--text-muted);
}

.upload-progress {
    height: 4px;
    background: var(--border);
    border-radius: 2px;
    margin-top: 1rem;
    overflow: hidden;
}

.upload-progress-bar {
    height: 100%;
    background: var(--accent);
    border-radius: 2px;
    transition: width 0.3s ease;
}

.upload-status {
    font-size: 0.8rem;
    color: var(--text-secondary);
    margin-top: 0.5rem;
    text-align: center;
}

.upload-errors {
    margin-top: 0.75rem;
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

.upload-error-item {
    font-size: 0.8rem;
    color: #f87171;
    background: rgba(248,113,113,0.1);
    border-radius: var(--radius-sm);
    padding: 0.4rem 0.75rem;
}

.upload-empty {
    text-align: center;
    color: var(--text-muted);
    font-size: 0.9rem;
    padding: 1.5rem;
}

/* ---- Photo Grid ---- */
.photo-grid-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin: 1.25rem 0 0.75rem;
}

.photo-count {
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--text-secondary);
    letter-spacing: 0.05em;
    text-transform: uppercase;
}

.photo-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
    gap: 0.5rem;
}

/* Non-clipped wrapper so the quality-warning popup can escape the card's overflow:hidden. */
.photo-card-wrap {
    position: relative;
}

.photo-card {
    position: relative;
    border-radius: var(--radius-sm);
    overflow: hidden;
    aspect-ratio: 1;
    background: var(--bg-card-hover);
    border: 2px solid transparent;
    transition: var(--transition);
}

.photo-card.is-cover  { border-color: var(--accent); }
.photo-card.is-back-cover { border-color: #818cf8; }

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

/* Shown in a photo slot while its thumbnail is still being generated in the background. */
.photo-thumb-pending {
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--bg-card);
}

.spinner-sm {
    width: 20px;
    height: 20px;
    border: 2px solid var(--border);
    border-top-color: var(--accent);
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
}

.photo-badge {
    position: absolute;
    bottom: 2px;
    left: 2px;
    font-size: 0.6rem;
    font-weight: 700;
    padding: 1px 5px;
    border-radius: 4px;
    letter-spacing: 0.05em;
}

.cover-badge  { background: var(--accent); color: #241d12; }
.back-badge   { background: #818cf8; color: #fff; }

.photo-delete-btn {
    position: absolute;
    top: 3px;
    right: 3px;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: rgba(0,0,0,0.7);
    color: #fff;
    font-size: 0.65rem;
    display: flex;
    align-items: center;
    justify-content: center;
    /* Visible without hovering: the app is mobile-first, and a touch screen never produces the
       hover that used to be the only way to reveal this button. */
    opacity: 0.6;
    transition: var(--transition);
    border: none;
}

.photo-card:hover .photo-delete-btn,
.photo-delete-btn:focus-visible { opacity: 1; }
.photo-delete-btn:hover { background: #ef4444; opacity: 1; }

/* --- Reordering ------------------------------------------------------------------------- */

/* The one place on the card where a finger means "move this photo" instead of "scroll the
   page" — which is what touch-action says, and saying it here rather than on the card is the
   whole reason the drag has a handle at all. It sits opposite the delete button so a thumb
   reaching for one is not over the other. */
.photo-drag-handle {
    position: absolute;
    top: 3px;
    left: 3px;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    border: none;
    background: rgba(0,0,0,0.7);
    color: #fff;
    font-size: 0.7rem;
    line-height: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0.6;
    transition: var(--transition);
    cursor: grab;
    touch-action: none;
}

.photo-card:hover .photo-drag-handle,
.photo-drag-handle:focus-visible { opacity: 1; }
.photo-drag-handle:active { cursor: grabbing; }

/* Lifted, and out from under the pointer's own hit-testing so the cards underneath can still
   report where the drop would land. */
.photo-card-wrap.is-dragging {
    z-index: 10;
    pointer-events: none;
}

.photo-card-wrap.is-dragging .photo-card {
    box-shadow: 0 8px 24px rgba(0,0,0,0.35);
    opacity: 0.9;
    transform: scale(1.04);
}

/* Where it would land. A line down the edge of a card rather than a gap opening up: opening a
   gap means moving every card after it, and the drag deliberately moves nothing — see
   js/photo-reorder.js. */
.photo-grid.is-reordering .photo-card-wrap::after {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    width: 3px;
    border-radius: 2px;
    background: var(--accent, #2563eb);
    opacity: 0;
    transition: opacity 0.1s ease;
}

.photo-grid.is-reordering .photo-card-wrap.is-drop-before::after { left: -0.3rem; opacity: 1; }
.photo-grid.is-reordering .photo-card-wrap.is-drop-after::after { right: -0.3rem; opacity: 1; }

/* Nothing is grabbable while the server is renumbering — a second drag would be measured
   against a list the first one has already replaced. */
.photo-grid.is-busy .photo-drag-handle {
    opacity: 0.3;
    pointer-events: none;
}

.photo-grid-tools {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-wrap: wrap;
    justify-content: flex-end;
}

.photo-grid-hint {
    font-size: 0.75rem;
    color: var(--text-secondary);
}

.photo-sort-btn {
    padding: 0.3rem 0.7rem;
    font-size: 0.75rem;
}

/* Asks before the photo goes. It covers the card it belongs to, so there is never a question
   about which photo is being deleted. */
.photo-delete-confirm {
    position: absolute;
    inset: 0;
    z-index: 4;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    padding: 0.4rem;
    text-align: center;
    background: rgba(12, 10, 8, 0.92);
    backdrop-filter: blur(2px);
}

.photo-delete-confirm-text {
    font-size: 0.68rem;
    line-height: 1.35;
    color: var(--text-primary);
}

.photo-delete-confirm-text small { color: var(--text-muted); }

.photo-delete-confirm-actions {
    display: flex;
    gap: 0.35rem;
    flex-wrap: wrap;
    justify-content: center;
}

.photo-delete-confirm-actions .app-btn {
    padding: 0.25rem 0.5rem;
    font-size: 0.65rem;
    min-height: 0;
}

/* Low print-quality warning: small round red badge with "!" over the thumbnail. */
.photo-quality-warning {
    position: absolute;
    bottom: 4px;
    right: 4px;
    z-index: 3;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: #ef4444;
    color: #fff;
    font-size: 0.8rem;
    font-weight: 800;
    line-height: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 2px solid #fff;
    box-shadow: 0 1px 4px rgba(0,0,0,0.5);
    cursor: pointer;
}

.photo-quality-warning:hover,
.photo-quality-warning.is-open {
    background: #dc2626;
    transform: scale(1.08);
}

.photo-quality-info {
    position: absolute;
    bottom: calc(100% - 2px);
    right: 0;
    z-index: 10;
    width: max(200px, 100%);
    max-width: 260px;
    background: var(--bg-card-active);
    color: var(--text-primary);
    border: 1px solid var(--danger);
    border-radius: var(--radius-sm);
    padding: 0.7rem 0.8rem;
    font-size: 0.75rem;
    line-height: 1.45;
    box-shadow: var(--shadow-card);
}

.photo-quality-info strong { color: var(--danger); }

/* Cover selector selectable */
.cover-selectable {
    padding: 0;
    text-align: left;
    width: 100%;
    cursor: pointer;
}

/* ---- Layout Selector ---- */
.layout-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 0.75rem;
}

.layout-option {
    background: var(--bg-card-hover);
    border: 2px solid var(--border);
    border-radius: var(--radius-md);
    padding: 1rem 0.75rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.75rem;
    transition: var(--transition);
    cursor: pointer;
}

.layout-option:hover {
    border-color: rgba(201,169,106,0.4);
    background: var(--bg-card-active);
}

.layout-option.active {
    border-color: var(--accent);
    background: rgba(201,169,106,0.08);
    box-shadow: 0 0 16px var(--accent-glow);
}

.layout-preview {
    display: grid;
    gap: 3px;
    width: 72px;
    height: 72px;
}

.layout-preview--1  { grid-template-columns: 1fr; }
.layout-preview--2  { grid-template-columns: 1fr; grid-template-rows: repeat(2, 1fr); }
.layout-preview--4  { grid-template-columns: 1fr 1fr; }
.layout-preview--8  { grid-template-columns: repeat(2, 1fr); grid-template-rows: repeat(4, 1fr); }
.layout-preview--12 { grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(4, 1fr); }

.layout-cell {
    background: var(--text-muted);
    border-radius: 2px;
}

.layout-option.active .layout-cell { background: var(--accent); }

.layout-label {
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--text-secondary);
}

.layout-option.active .layout-label { color: var(--accent); }

/* The capacity sits under the name, a step quieter: it is what the choice buys, not what the
   choice is called. Tabular figures so 40 / 80 / 160 / 320 / 480 line up down the row. */
.layout-capacity {
    display: block;
    margin-top: 0.15rem;
    font-size: 0.7rem;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}

.layout-option.active .layout-capacity { color: var(--accent-soft); }

/* ---- Options (size/orientation) ---- */
.option-group { margin-bottom: 1.25rem; }
.option-group:last-child { margin-bottom: 0; }

.option-label {
    font-size: 0.8rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--text-muted);
    margin-bottom: 0.5rem;
}

.option-pills {
    display: flex;
    gap: 0.5rem;
    flex-wrap: wrap;
}

.pill {
    padding: 0.5rem 1.25rem;
    border: 1px solid var(--border);
    border-radius: 100px;
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--text-secondary);
    background: var(--bg-card-hover);
    transition: var(--transition);
    cursor: pointer;
}

.pill:hover { border-color: rgba(201,169,106,0.4); color: var(--text-primary); }

.pill.active {
    background: var(--accent);
    border-color: var(--accent);
    color: #241d12;
}

/* ---- Cover Sections ---- */
.cover-section { margin-bottom: 2rem; }
.cover-section:last-child { margin-bottom: 0; }

.cover-section-title {
    font-family: var(--font-display);
    font-size: 1.2rem;
    font-weight: 500;
    margin-bottom: 0.25rem;
}

.cover-hint {
    font-size: 0.8rem;
    color: var(--text-secondary);
    margin-bottom: 0.75rem;
}

.cover-text-label {
    display: block;
    font-size: 0.8rem;
    color: var(--text-secondary);
    margin: 0.25rem 0 0.35rem;
}

.cover-text-input {
    width: 100%;
    padding: 0.7rem 0.9rem;
    margin-bottom: 0.75rem;
    background: var(--bg-card-hover);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    color: var(--text-primary);
    font-family: inherit;
    font-size: 0.95rem;
    transition: var(--transition);
}

.cover-text-input::placeholder { color: var(--text-muted); }

.cover-text-input:focus {
    outline: none;
    border-color: var(--border-focus);
    box-shadow: 0 0 0 3px rgba(201,169,106,0.12);
}

/* ---- The inside greeting ----
   Not a third cover control but a note written into the book, so it gets a panel of its own,
   set back from the two cover sides above it rather than starting where they end. */
.cover-greeting {
    margin-top: 2rem;
    padding: 1.35rem 1.15rem 1.25rem;
    background: var(--bg-secondary);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
}

.cover-greeting > :last-child { margin-bottom: 0; }

/* Prose wants a measure. Run to the full width of the step the hint made a line too long to
   track back from, and left the field looking like it began mid-sentence. */
.cover-greeting-hint {
    max-width: 62ch;
    margin-bottom: 1.1rem;
}

/* Field and counter share one frame: the count belongs to the box it counts, and standing on
   its own under it, it read as a stray line of the page's own text. */
.greeting-field {
    background: var(--bg-card-hover);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    overflow: hidden;
    transition: var(--transition);
}

.greeting-field:focus-within {
    border-color: var(--border-focus);
    box-shadow: 0 0 0 3px rgba(201,169,106,0.12);
}

/* The greeting is prose, not a title: it gets several lines and grows downwards only, so
   dragging it wider cannot break the column the rest of the step is laid out in. It is centred
   and set in the cover's face because that is how it prints — the family comes from the
   component, which knows which face the cover was given. */
.greeting-input {
    display: block;
    width: 100%;

    /* Grows with the greeting where the browser can do it on its own, so a dedication of a few
       lines is read whole instead of scrolling inside a four-line box with its last line cut
       across the middle. Where it cannot, the field keeps the height below and the customer can
       still drag it taller — the same field, one convenience less. */
    field-sizing: content;
    min-height: 7rem;
    max-height: 24rem;
    padding: 1.2rem 1.2rem 0.85rem;
    background: none;
    border: none;
    color: var(--text-primary);
    font-size: 1.05rem;
    line-height: 1.75;
    text-align: center;
    resize: vertical;
}

.greeting-input:focus { outline: none; }
.greeting-input::placeholder { color: var(--text-muted); }

/* Quiet until the room is nearly used up: a reassurance while there is space and a warning when
   there is not, and it should only raise its voice for the second one. */
.greeting-counter {
    font-size: 0.72rem;
    color: var(--text-muted);
    text-align: right;
    padding: 0 1.2rem 0.75rem;
}

.greeting-counter--low { color: var(--accent); }

/* ---- Front/back cover side by side ----
   They print on the same sheet, so showing both at once keeps the customer from
   filling in the front and never noticing the back. Stacks on narrow screens. */
.cover-sides {
    display: grid;
    /* min() rather than a bare 240px: on a screen narrower than the track minimum the column
       refuses to shrink, and the card's right edge disappears under the viewport. */
    grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
    gap: 1.5rem;
}

.cover-side {
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    padding: 1rem;
}

.cover-side-drop { padding: 1.75rem 0.75rem; }

.cover-side-photo {
    border-radius: var(--radius-sm);
    overflow: hidden;
    background: var(--bg-card-hover);
    aspect-ratio: 3 / 4;
}

.cover-side-thumb {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.cover-side-actions {
    display: flex;
    gap: 0.5rem;
    margin-top: 0.6rem;
}

.cover-side-btn {
    position: relative;
    overflow: hidden;
    flex: 1;
    text-align: center;
    padding: 0.5rem 0.6rem;
    background: var(--bg-card-hover);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    color: var(--text-secondary);
    font-family: inherit;
    font-size: 0.8rem;
    cursor: pointer;
    transition: var(--transition);
}

.cover-side-btn:hover { border-color: var(--accent); color: var(--text-primary); }
.cover-side-btn-remove:hover { border-color: #ef4444; color: #f87171; }

/* ---- Cover design controls (photo placement + free text position) ---- */
.cover-control-heading {
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--text-secondary);
    margin: 1rem 0 0.55rem;
}

/* Photo placement: three clean framed thumbnails */
.photo-placement-picker {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 0.6rem;
    margin-bottom: 0.5rem;
}

.placement-opt {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.5rem;
    padding: 0.7rem 0.4rem;
    background: var(--bg-card-hover);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    color: var(--text-secondary);
    font-family: inherit;
    cursor: pointer;
    transition: var(--transition);
}

.placement-opt:hover { border-color: var(--border-strong); }

.placement-opt.is-active {
    border-color: var(--accent);
    background: rgba(201,169,106,0.12);
    color: var(--text-primary);
}

.placement-name { font-size: 0.72rem; font-weight: 600; }

.placement-mini {
    --mf: 4px;
    position: relative;
    display: block;
    width: 46px;
    height: 60px;
    border-radius: 5px;
    overflow: hidden;
    background: var(--bg-primary);
    border: 1px solid var(--border-strong);
}

.placement-mini .pm-photo {
    position: absolute;
    left: var(--mf);
    right: var(--mf);
    border-radius: 2px;
    background: linear-gradient(135deg, var(--accent-soft), var(--accent-strong));
}

.pm-fill   .pm-photo { top: var(--mf); bottom: var(--mf); }
.pm-top    .pm-photo { top: var(--mf); height: 58%; }
.pm-bottom .pm-photo { bottom: var(--mf); height: 58%; }

/* ---- Page-limit banner ---- */
.page-limit-banner {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    padding: 0.85rem 1rem;
    border-radius: var(--radius-md);
    margin-bottom: 1.25rem;
    border: 1px solid transparent;
}

.page-limit-banner--ok {
    background: rgba(201,169,106,0.10);
    border-color: rgba(201,169,106,0.35);
}

.page-limit-banner--warn {
    background: rgba(226,138,122,0.10);
    border-color: rgba(226,138,122,0.4);
}

.page-limit-icon { font-size: 1.1rem; line-height: 1.3; flex-shrink: 0; }
.page-limit-banner--ok .page-limit-icon { color: var(--accent); }
.page-limit-banner--warn .page-limit-icon { color: var(--danger); }

.page-limit-title { font-weight: 700; font-size: 0.9rem; margin-bottom: 0.15rem; }
.page-limit-hint { font-size: 0.8rem; color: var(--text-secondary); }
.page-limit-note { font-size: 0.72rem; color: var(--text-muted); margin-top: 0.3rem; }

/* ---- PDF Preview ---- */
.preview-info {
    display: flex;
    gap: 1rem;
    margin-bottom: 1.25rem;
    flex-wrap: wrap;
}

.preview-stat {
    flex: 1;
    min-width: 80px;
    background: var(--bg-card-hover);
    border-radius: var(--radius-md);
    padding: 0.75rem;
    text-align: center;
}

.preview-stat-value {
    display: block;
    font-size: 1.5rem;
    font-weight: 800;
    color: var(--accent);
}

.preview-stat-label {
    font-size: 0.7rem;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.08em;
}

/* ---- Album preview: readiness, view modes, spreads (.prv) ---- */
.prv-status {
    display: flex;
    align-items: center;
    gap: 1rem;
    flex-wrap: wrap;
    padding: 1rem;
    margin-bottom: 1rem;
    background: var(--bg-card-hover);
    border: 1px solid var(--border);
    border-left: 3px solid var(--accent);
    border-radius: var(--radius-md);
}

.prv-status--ok { border-left-color: #8fd6a4; }
.prv-status--warn { border-left-color: var(--danger); }

/* The ring reads "how far along is the album" before any number is parsed. */
.prv-ring {
    --pct: 0%;
    width: 76px;
    height: 76px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: conic-gradient(var(--ring-colour, var(--accent)) var(--pct), var(--bg-card-active) 0);
}

.prv-status--ok .prv-ring { --ring-colour: #8fd6a4; }

.prv-ring-face {
    width: 62px;
    height: 62px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: var(--bg-card-hover);
    line-height: 1.1;
}

.prv-ring-value { font-size: 1.35rem; font-weight: 800; color: var(--text-primary); }
.prv-ring-unit  { font-size: 0.62rem; color: var(--text-muted); }

.prv-status-text { flex: 1; min-width: 190px; }
.prv-status-title { font-weight: 700; font-size: 0.95rem; margin-bottom: 0.15rem; }
.prv-status--ok .prv-status-title { color: #8fd6a4; }
.prv-status-hint { font-size: 0.8rem; color: var(--text-secondary); }
.prv-status-note { font-size: 0.72rem; color: var(--text-muted); margin-top: 0.35rem; }

.prv-status-figures {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    font-size: 0.75rem;
    color: var(--text-secondary);
    white-space: nowrap;
}

.prv-status-figures b { color: var(--text-primary); font-size: 0.95rem; }

/* A resolution problem is only fixable if you can find which photo has it. */
.prv-alert {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    width: 100%;
    text-align: left;
    padding: 0.7rem 0.85rem;
    margin-bottom: 1rem;
    background: rgba(226,138,122,0.1);
    border: 1px solid rgba(226,138,122,0.35);
    border-radius: var(--radius-sm);
    color: var(--text-primary);
    font-size: 0.82rem;
    transition: var(--transition);
}

.prv-alert:hover { background: rgba(226,138,122,0.18); }
.prv-alert-icon { color: var(--danger); font-size: 1rem; line-height: 1.3; }
.prv-alert-hint { display: block; color: var(--text-secondary); font-size: 0.75rem; font-weight: 400; }

/* The photos that will print soft, listed in reading order. The list is the fix-it queue: the
   customer works down it and a row leaves as soon as its crop is good enough, so what is on screen
   is always what is left. */
.prv-soft { margin-bottom: 1rem; }
.prv-soft .prv-alert { margin-bottom: 0; }
.prv-soft-chevron { margin-left: auto; color: var(--text-secondary); font-size: 0.7rem; }

.prv-soft-list {
    list-style: none;
    margin: 0.4rem 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}

.prv-soft-item {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    width: 100%;
    padding: 0.4rem 0.6rem;
    text-align: left;
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    color: var(--text-primary);
    font-size: 0.8rem;
    cursor: pointer;
    transition: var(--transition);
}

.prv-soft-item:hover { background: var(--bg-card-hover); border-color: var(--border-strong); }

.prv-soft-thumb {
    width: 34px;
    height: 34px;
    object-fit: cover;
    border-radius: 3px;
    flex: none;
}

.prv-soft-where { display: flex; flex-direction: column; min-width: 0; }
.prv-soft-why { color: var(--text-secondary); font-size: 0.72rem; }
.prv-soft-go { margin-left: auto; color: var(--accent); font-size: 0.75rem; white-space: nowrap; }

.prv-toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    flex-wrap: wrap;
    margin-bottom: 0.85rem;
}

.prv-modes {
    display: flex;
    gap: 2px;
    padding: 2px;
    background: var(--bg-card-hover);
    border: 1px solid var(--border);
    border-radius: 100px;
}

.prv-mode {
    padding: 0.35rem 0.9rem;
    border-radius: 100px;
    font-size: 0.78rem;
    font-weight: 600;
    color: var(--text-secondary);
    white-space: nowrap;
    transition: var(--transition);
}

.prv-mode:hover { color: var(--text-primary); }
.prv-mode.is-active { background: var(--accent); color: #241d12; }

.prv-hint {
    font-size: 0.8rem;
    color: var(--text-secondary);
    line-height: 1.5;
    margin-bottom: 0.85rem;
}

/* Centred, not packed to the left: the pages are a fixed width, so whatever the row cannot use
   is left over — and on a phone, where two of them fit and the third does not, all of that
   leftover collected against the right edge and stood the whole album off-centre. */
.preview-pages {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.75rem;
    padding-bottom: 0.5rem;
}

.preview-page {
    flex-shrink: 0;
    /* The page stands in for a real sheet, whose width in mm and aspect ratio come from the
       component (they differ between magazine/A4 and portrait/landscape). Print measurements
       below are scaled through --mm, so they hold at any preview size. */
    --page-w: 120px;
    --page-mm-w: 210;
    --page-ar: 0.75;
    /* Paper. The photo pages print white, and the printer's 1 cm border is white paper — drawn in
       the app's card colour it read as part of the surrounding UI rather than as the margin the
       customer is being asked to judge. */
    --paper: #f4f2ee;
    --mm: calc(var(--page-w) / var(--page-mm-w));
    width: var(--page-w);
    height: calc(var(--page-w) / var(--page-ar));
    border-radius: var(--radius-sm);
    overflow: hidden;
    position: relative;
    background: var(--bg-card-hover);
    /* An outline, not a border: with box-sizing:border-box a border eats into --page-w, and
       the millimetre maths above would then lay out against a page 2 px smaller than it
       thinks — shaving the 1 cm print border. An outline draws without taking layout space. */
    outline: 1px solid var(--border);
    outline-offset: -1px;
}

/* The large view is the same page at a bigger --page-w: as wide as the viewport allows,
   but never so tall that it stops fitting on screen. */
.preview-page--lg {
    --page-w: min(84vw, calc(72vh * var(--page-ar)), 560px);
}

/* The slot is the button that opens the page — resets first, then the affordance that says so:
   the page lifts under the cursor and the caption warms with it, so a thumbnail reads as
   something to press rather than something to look at. */
.preview-page-slot {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.2rem;
    padding: 0;
    border: none;
    background: none;
    font: inherit;
    color: inherit;
    cursor: pointer;
}

.preview-page-slot .preview-page {
    transition: transform 130ms ease, box-shadow 130ms ease, outline-color 130ms ease;
}

.preview-page-slot:hover .preview-page {
    outline-color: var(--accent);
    transform: translateY(-2px);
    box-shadow: var(--shadow-card), var(--shadow-glow);
}

.preview-page-slot:hover .prv-page-caption { color: var(--accent-soft); }

/* Focus lands on the slot, but the page is what the customer is looking at, so that is where
   the ring goes. An outline again, not a border: the page's millimetre maths depend on it. */
.preview-page-slot:focus-visible { outline: none; }

.preview-page-slot:focus-visible .preview-page {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.prv-page-caption {
    font-size: 0.62rem;
    color: var(--text-muted);
    letter-spacing: 0.04em;
}

/* ---- Spreads: the magazine as it opens in the hand ---- */
.prv-spreads {
    display: flex;
    flex-direction: column;
    gap: 1.1rem;
    align-items: center;
}

/* Two pages meeting at the fold. The gradient down the join is the shadow a bound
   magazine casts into its own gutter — it is what makes a pair read as one opening. */
.prv-spread {
    align-items: stretch;
    position: relative;
    display: flex;
    background: var(--bg-card-hover);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-soft);
    padding: 0;
    border: none;
    font: inherit;
    color: inherit;
    cursor: pointer;
    transition: transform 130ms ease, box-shadow 130ms ease;
}

.prv-spread:hover {
    transform: translateY(-2px);
    box-shadow: var(--shadow-card), var(--shadow-glow);
}

.prv-spread:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
}

.prv-spread::after {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: 50%;
    width: 22px;
    transform: translateX(-50%);
    pointer-events: none;
    background: linear-gradient(to right,
        rgba(0,0,0,0) 0%, rgba(0,0,0,0.34) 45%, rgba(0,0,0,0.34) 55%, rgba(0,0,0,0) 100%);
    z-index: 5;
}

/* A cover has no facing page, so it has no fold to shade. */
.prv-spread--single::after { content: none; }

.prv-spread .preview-page { --page-w: min(38vw, 168px); }

.prv-spread-caption {
    position: absolute;
    bottom: -1.05rem;
    left: 50%;
    transform: translateX(-50%);
    font-size: 0.62rem;
    color: var(--text-muted);
    white-space: nowrap;
}

/* The unprinted faces of the cover sheet: real pages in the file, blank on paper. */
.preview-page--inside {
    background: var(--paper);
    display: flex;
    align-items: center;
    justify-content: center;
}

.prv-inside-hint {
    font-size: 0.55rem;
    line-height: 1.4;
    text-align: center;
    color: #9a938a;
    letter-spacing: 0.03em;
}

.prv-inside-hint small { font-size: 0.85em; opacity: 0.75; }

/* The greeting on page 2 or 43. Black on white, centred, and the line breaks the customer typed
   are the line breaks that print — so pre-wrap, not the collapsing whitespace of ordinary markup. */
.prv-inside-text {
    color: #000;
    text-align: center;
    line-height: 1.5;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.page-viewer-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(10,9,8,0.82);
    backdrop-filter: blur(2px);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
    z-index: 520;
}

.page-viewer {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.75rem;
    max-height: 100%;
}

.page-viewer-title {
    font-family: var(--font-display);
    font-size: 1.05rem;
    text-align: center;
}

.page-viewer-of { color: var(--text-secondary); font-size: 0.8rem; margin-left: 0.4rem; }

.page-viewer-note {
    font-size: 0.75rem;
    color: var(--text-secondary);
    text-align: center;
}

.page-viewer-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; justify-content: center; }

/* Turning a whole page at once. One button per content page on show — a spread gets two, each
   naming its own page — sitting under the sheet they act on and above the pager. */
.prv-turn-row { display: flex; gap: 0.5rem; flex-wrap: wrap; justify-content: center; }

.prv-turn {
    padding: 0.4rem 0.85rem;
    background: transparent;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    color: var(--text-secondary);
    font-size: 0.8rem;
    font-weight: 600;
    cursor: pointer;
    transition: var(--transition);
}

.prv-turn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent-soft); }
.prv-turn:disabled { opacity: 0.5; cursor: default; }

/* The backdrop takes focus so ← and → page through the album; the ring would only
   distract from the page it is wrapped around. */
.page-viewer-backdrop:focus { outline: none; }

/* A spread is shown as the two facing pages it really is, so each page is given half
   the room a single page gets. */
.page-viewer-sheet { display: flex; position: relative; }

.page-viewer-sheet--spread .preview-page--lg {
    --page-w: min(42vw, calc(72vh * var(--page-ar)), 320px);
}

.page-viewer-sheet--spread::after {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: 50%;
    width: 30px;
    transform: translateX(-50%);
    pointer-events: none;
    background: linear-gradient(to right,
        rgba(0,0,0,0) 0%, rgba(0,0,0,0.38) 45%, rgba(0,0,0,0.38) 55%, rgba(0,0,0,0) 100%);
    z-index: 5;
}

/* The strip is for looking at, so its photos carry no editor affordance. The compound
   selector is deliberate: the pencil is declared further down the file at the same
   specificity, so a lone .preview-photo-btn--static::after loses on source order. */
.preview-photo-btn--static { cursor: default; }
.preview-photo-btn.preview-photo-btn--static::after { content: none; }

/* ---- Ordering without an account ---- */
.guest-order {
    margin: 0.75rem 0 1rem;
    padding: 0.9rem 1rem;
    background: var(--bg-card-hover);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
}

/* The link is the customer's only way back to the order, so it is shown as something to keep
   rather than as an afterthought — and it wraps, because it is long and gets copied. */
.order-track-box {
    margin: 1rem auto 0;
    max-width: 34rem;
    padding: 0.9rem 1rem;
    border: 1px dashed var(--accent);
    border-radius: var(--radius-sm);
    background: rgba(176, 141, 79, 0.08);
    text-align: center;
}

.order-track-label {
    font-size: 0.7rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-muted);
    margin-bottom: 0.35rem;
}

.order-track-link {
    display: block;
    word-break: break-all;
    font-size: 0.8rem;
    color: var(--accent-soft);
    margin-bottom: 0.4rem;
}

/* ---- Back-cover imprint ---- */
/* Shown, not offered: the customer cannot change these words, but they should know they are
   coming. Set in the gold they print in, on both the panel and the previewed page. */
.cover-imprint-note {
    margin-top: 0.5rem;
    padding: 0.75rem 0.9rem;
    border: 1px dashed var(--border-strong);
    border-radius: var(--radius-sm);
}

.cover-imprint-preview {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.15rem;
    margin-bottom: 0.5rem;
    text-align: center;
}

/* The name leads and the promise follows, at the sizes BackCoverImprint prints them — the two
   swapped on 2026-08-28 and these have to swap with them, or the panel advertises a back cover
   the press does not produce. */
.cover-imprint-site {
    font-family: 'Playfair Display', Georgia, serif;
    font-size: 0.95rem;
    color: var(--accent);
}

.cover-imprint-tagline {
    font-family: 'Playfair Display', Georgia, serif;
    font-size: 0.72rem;
    color: var(--accent);
    opacity: 0.85;
}

/* On the previewed page the imprint sits at the foot of the lower half — the same place and the
   same proportions PdfGenerationService gives it, scaled through --mm. justify-content is the
   AlignBottom of the PDF: both moved off centre on 2026-08-28 on the client's instruction, and
   they have to move together or the preview stops being a preview. */
.preview-page--back .prv-imprint {
    position: absolute;
    left: var(--cf);
    right: var(--cf);
    bottom: var(--cf);
    height: calc(var(--back-panel-mm, 0) * var(--mm));
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-end;
    gap: calc(2 * var(--mm));
    text-align: center;
    pointer-events: none;
}

/* The gold changes with the cover colour — deeper on white, where the brand value is too pale
   to hold. The component sets --imprint-ink from BackCoverImprint, so both renderers pick the
   same one. */
.prv-imprint-tagline,
.prv-imprint-site {
    font-family: 'Playfair Display', Georgia, serif;
    color: var(--imprint-ink, #B08D4F);
    line-height: 1;
}

/* 4.6 mm and 3.4 mm are the 13 pt and 9.5 pt of BackCoverImprint. They belong to the sizes, not
   to the lines, so they swapped when the two lines did. */
.prv-imprint-site { font-size: max(4px, calc(4.6 * var(--mm))); }
.prv-imprint-tagline { font-size: max(3px, calc(3.4 * var(--mm))); }

/* The photo takes the top half, so the slot stops where the imprint begins. Both class names
   are on the selector deliberately: the generic .preview-page--cover rule that sets inset to the
   safe margin appears further down the file, and would otherwise win on source order. */
.preview-page--cover.preview-page--back .preview-photo-btn-wrap {
    inset: var(--cf) var(--cf) calc(var(--cf) + var(--back-panel-mm, 0) * var(--mm)) var(--cf);
}

/* ---- Cover typography ---- */
/* Each option is set in the face it offers: the label says "Klassisk", the sample says what that
   actually looks like, which is the only part of the choice that matters. */
.cover-font-picker {
    display: flex;
    gap: 0.5rem;
    margin-bottom: 0.75rem;
}

.cover-font-opt {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.15rem;
    padding: 0.5rem 0.35rem;
    background: var(--bg-card-hover);
    border: 2px solid var(--border);
    border-radius: var(--radius-sm);
    color: var(--text-secondary);
    transition: var(--transition);
}

.cover-font-opt:hover { border-color: var(--border-strong); color: var(--text-primary); }

.cover-font-opt.is-active {
    border-color: var(--accent);
    color: var(--text-primary);
    background: var(--bg-card-active);
}

.cover-font-sample { font-size: 1.35rem; line-height: 1.1; }

/* The name is UI, not a sample, so it steps out of the face being offered. */
.cover-font-name {
    font-family: var(--font-body);
    font-size: 0.62rem;
    letter-spacing: 0.03em;
    color: var(--text-muted);
}

.cover-font-opt.is-active .cover-font-name { color: var(--accent-soft); }

.cover-bg-picker { display: flex; gap: 0.5rem; margin-bottom: 0.5rem; }

.cover-bg-opt {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.75rem;
    background: var(--bg-card-hover);
    border: 2px solid var(--border);
    border-radius: var(--radius-sm);
    color: var(--text-primary);
    cursor: pointer;
    transition: var(--transition);
}

.cover-bg-opt:hover { border-color: var(--border-strong); }
.cover-bg-opt.is-active { border-color: var(--accent); }

.cover-bg-swatch {
    width: 20px;
    height: 26px;
    border-radius: 2px;
    /* An outline, so a white swatch still reads as a page on the dark card. */
    outline: 1px solid var(--border-strong);
    outline-offset: -1px;
}

.cover-bg-sort .cover-bg-swatch { background: #000; }
.cover-bg-hvid .cover-bg-swatch { background: #fff; }

/* The cover prints on white or black stock — never the app's card colour — so the preview
   shows the chosen background rather than the surrounding UI. */
.preview-page--cover {
    outline-color: var(--accent);
    background: var(--cover-bg, #000);
}

.preview-page--full { outline-color: var(--accent); }

.preview-page-star {
    position: absolute;
    top: 4px;
    right: 4px;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: var(--accent);
    color: #241d12;
    font-size: 0.7rem;
    line-height: 18px;
    text-align: center;
    pointer-events: none;
    z-index: 2;
}

/* Geometry here mirrors PdfGenerationService, so the preview shows what gets printed:
   the printer's 1 cm border, and a 4 mm gutter between photos (2 mm of cell padding on
   each of two neighbouring cells). Percentages are no use for this — a percentage padding
   resolves against the containing block's width, not the page's own. */
/* The grid divides the whole page inside the border — equal fractions on both axes, so the
   cells carry the sheet's proportions and nothing is left over. It used to size square cells
   against both axes and centre the block, which meant an 8-up page spent two thirds of its
   height on white. --cols/--rows come from the component. */
.preview-page--content {
    --gap: calc(4 * var(--mm));

    display: grid;
    grid-template-columns: repeat(var(--cols), 1fr);
    grid-template-rows: repeat(var(--rows), 1fr);
    gap: var(--gap);
    padding: calc(10 * var(--mm));
    background: var(--paper);
}

/* Fallback counts; the component overrides them inline per page. */
.preview-page--content { --cols: 2; --rows: 2; }

/* A promoted favourite is cropped to fill everything inside the border, so it is the one
   page that ignores the grid and takes the whole area. */
.preview-page--content.preview-page--full {
    grid-template-columns: 1fr;
    grid-template-rows: 1fr;
}

/* The page number, placed through --mm at the millimetres PageGeometry gives the PDF: 4 mm up
   from the trimmed edge, 10 mm in from the side. Odd pages are right-hand pages once the
   magazine is stitched, so the folio always lands on the outer edge. It is absolutely
   positioned, which also keeps it out of the grid flow above. The floor on the font size is
   for the thumbnails, where a true-to-scale 9 pt would be under two pixels tall. */
.preview-page-folio {
    position: absolute;
    bottom: calc(4 * var(--mm));
    font-size: max(4px, calc(3.2 * var(--mm)));
    line-height: 1;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
    pointer-events: none;
    z-index: 2;
}

.preview-page-folio.is-left { left: calc(10 * var(--mm)); }
.preview-page-folio.is-right { right: calc(10 * var(--mm)); }

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

/* ---- Tap-to-edit photo (zoom/position within its slot) ---- */
/* The wrapper is the slot; the button is the photo inside it, and the badge sits on top
   without being part of the button (a warning you can click is a warning you can misfire). */
.preview-photo-btn-wrap {
    position: relative;
    display: block;
    width: 100%;
    height: 100%;
}

.preview-photo-btn {
    position: absolute;
    inset: 0;
    display: block;
    padding: 0;
    border: none;
    background: none;
    overflow: hidden;
    cursor: pointer;
}

/* ---- Quarter turns ---- */
/* The turn happens on this wrapper rather than on the photo, which already spends its own
   transform on the zoom about the focal point. Used by both the preview and the crop editor, so
   a turned photo looks the same in the strip, on the opened page and in the dialog. */
.photo-turn {
    position: absolute;
    inset: 0;
    display: block;
}

.photo-turn--turned {
    inset: auto;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%) rotate(var(--turn, 0deg));
}

/* A quarter turn also has to swap the box it turns inside, or the result stops covering the
   frame. --slot-ar is the frame's width ÷ height, so the frame's height expressed against its
   own width is 100%/ar, and its width against its own height is 100%·ar — which is exactly the
   swap, without either measurement having to reach JavaScript. */
.photo-turn--quarter {
    width: calc(100% / var(--slot-ar));
    height: calc(100% * var(--slot-ar));
}

/* Covers keep the photo a safe distance (--cf) from the page edge, framed by the cover colour —
   the print safe-margin. It is measured through --mm from the millimetres PageGeometry gives the
   PDF, not set in pixels: a fixed value is only ever right at one preview size, and it was tuned
   for the thumbnail, so on the opened page — the one place the customer inspects the margin — the
   frame was drawn several times too narrow. */
/* Sets --cf only. It used to repaint the page in --bg-primary as well, which — same selector,
   further down the file — quietly beat the --cover-bg rule above and painted every cover in the
   app's near-black: a white cover looked black in the preview and white in the PDF. */
.preview-page--cover { --cf: calc(var(--cover-margin-mm, 12) * var(--mm)); }
/* The slot is placed by its insets alone. The base rule above sizes it 100% x 100% — right for
   the grid cell it usually sits in, wrong here: an absolutely positioned box that is given left,
   right and a width is over-constrained, so CSS drops the right offset and keeps the width (and
   the same for bottom against the height). The cover photo was therefore laid out a full margin
   wide and tall, shifted down and to the right, hanging off the page instead of stopping at the
   safe frame — and on a top/bottom cover it ran straight over the panel the band leaves for the
   colour. Resetting both to auto hands the box back to the insets. */
.preview-page--cover .preview-photo-btn-wrap {
    position: absolute;
    inset: var(--cf);
    width: auto;
    height: auto;
    border-radius: 2px;
}

/* Flags a photo whose current crop falls below what prints acceptably at this size. It rides the
   thumbnails in Sider and Opslag as well as the opened page: the banner says how many and the list
   says where, but neither answers "is this one of them" while you are looking at a page. Sized off
   the page rather than in fixed pixels for that reason — on a strip thumbnail a 15 px disc is most
   of the photo, and a mark that swamps what it marks cannot be read against its neighbours. */
.prv-soft-badge {
    position: absolute;
    top: 3px;
    left: 3px;
    width: clamp(10px, calc(9 * var(--mm)), 15px);
    height: clamp(10px, calc(9 * var(--mm)), 15px);
    font-size: clamp(0.4rem, calc(5 * var(--mm)), 0.55rem);
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: var(--danger);
    color: #2a120e;
    font-weight: 700;
    pointer-events: none;
    z-index: 3;
}

/* ---- Moving a photo along the printed album ---- */
/* Sits on the photo, at the foot of its slot, and only ever on an opened page — the strip
   renders its slots non-interactive. Sized off the page like the marks above it: a 12-up grid
   gives each photo a third of the sheet, and a fixed-pixel control that is comfortable there is
   most of the picture on a spread, where two pages share the width. */
.prv-move {
    position: absolute;
    left: 50%;
    bottom: 4px;
    transform: translateX(-50%);
    display: flex;
    gap: 2px;
    z-index: 3;
}

.prv-move-btn {
    width: clamp(18px, calc(11 * var(--mm)), 26px);
    height: clamp(18px, calc(11 * var(--mm)), 26px);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: rgba(20, 18, 16, 0.72);
    color: var(--text-primary);
    font-size: clamp(0.6rem, calc(7 * var(--mm)), 0.85rem);
    line-height: 1;
    cursor: pointer;
}

.prv-move-btn:hover:not(:disabled) {
    background: var(--accent);
    color: #1a1712;
}

/* Kept in place rather than hidden: the row would otherwise shift under the finger every time a
   photo reached the end of the album, and the arrow that is left would move to where the other
   one just was. */
.prv-move-btn:disabled {
    opacity: 0.3;
    cursor: not-allowed;
}

.preview-photo-btn::after {
    content: "✎";
    position: absolute;
    bottom: 3px;
    right: 3px;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: rgba(20,18,16,0.65);
    color: var(--text-primary);
    font-size: 0.55rem;
    display: flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
}

/* ---- Cover photo placement in the album preview (framed by --cf) ---- */
/* The panel beside the photo is what is left of the framed area once the photo has taken its
   band — PageGeometry.CoverPhotoBandFraction of it — so the component hands the leftover down in
   millimetres. It used to be 40% of the whole page, which is a different measurement entirely and
   only looked close by coincidence. */
.preview-page--cover.cph-fill   .preview-photo-btn-wrap { inset: var(--cf); }

.preview-page--cover.cph-top .preview-photo-btn-wrap {
    inset: var(--cf) var(--cf) calc(var(--cf) + var(--cover-panel-mm, 0) * var(--mm)) var(--cf);
}

.preview-page--cover.cph-bottom .preview-photo-btn-wrap {
    inset: calc(var(--cf) + var(--cover-panel-mm, 0) * var(--mm)) var(--cf) var(--cf) var(--cf);
}

/* ---- Draggable cover text ---- */
/* The layer spans the safe framed area; only the text chip captures pointers,
   so tapping elsewhere still opens the photo editor underneath. */
.cover-text-layer {
    position: absolute;
    inset: var(--cf);
    pointer-events: none;
    z-index: 3;
}

.preview-page-text {
    position: absolute;
    transform: translate(-50%, -50%);
    max-width: 90%;
    padding: 3px 6px;
    background: var(--cover-chip, rgba(0,0,0,0.6));
    color: var(--cover-ink, #fff);
    font-size: 0.58rem;
    font-weight: 700;
    text-align: center;
    line-height: 1.2;
    border-radius: 3px;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
}

/* The chip is a control, so it is not text to select: a drag that highlights the words instead of
   moving them reads as a broken gesture rather than the wrong one. */
.cover-text-drag {
    pointer-events: auto;
    cursor: grab;
    touch-action: none;
    user-select: none;
    -webkit-user-select: none;
    border: 1px dashed transparent;
}

.cover-text-drag:hover { border-color: rgba(228,207,159,0.6); }
.cover-text-drag.dragging { cursor: grabbing; border-color: var(--accent); background: var(--cover-chip-active, rgba(20,18,16,0.8)); }

/* Sizing looks the same as moving on purpose — one handle, two gestures — so the chip says
   "I am the thing you are changing" either way. */
.cover-text-drag.is-resizing { border-color: var(--accent); background: var(--cover-chip-active, rgba(20,18,16,0.8)); }

/* It takes focus for the +/- keys, so it has to show where the focus went. */
.cover-text-drag:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-color: var(--accent);
}

/* Blank cover (no photo/text): a subtle centered hint inside the frame. */
/* An untouched cover is no longer empty — it carries the name — so this is the name as the press
   will set it: gold, near the top, at DefaultCover's 34 pt scaled through --mm (34 pt is 12 mm).
   The dashed placeholder box that used to say "Tom forside" is gone with the blankness. */
.prv-default-cover {
    position: absolute;
    left: var(--cf);
    right: var(--cf);
    top: calc(var(--cf) + 18 * var(--mm));
    text-align: center;
    font-family: 'Playfair Display', Georgia, serif;
    font-size: max(7px, calc(12 * var(--mm)));
    line-height: 1;
    color: var(--imprint-ink, #B08D4F);
    pointer-events: none;
}

/* ---- Download ---- */
.download-section {
    text-align: center;
    padding: 1.5rem 0.5rem;
}

.download-icon { font-size: 3rem; margin-bottom: 0.75rem; }

.download-title {
    margin-bottom: 0.5rem;
}

.download-subtitle {
    color: var(--text-secondary);
    font-size: 0.9rem;
    margin-bottom: 1.5rem;
}

.btn-download {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.9rem 2.5rem;
    background: var(--accent);
    color: #241d12;
    font-size: 1rem;
    font-weight: 800;
    border-radius: 100px;
    text-decoration: none;
    transition: var(--transition);
    box-shadow: 0 0 24px var(--accent-glow);
}

.btn-download:hover {
    background: var(--accent-strong);
    transform: translateY(-2px);
    text-decoration: none;
}

.download-note {
    margin-top: 1.25rem;
    color: var(--text-muted);
    font-size: 0.8rem;
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

.download-made { font-weight: 700; }

.download-error { color: #f87171; font-size: 0.9rem; }

.order-teaser {
    margin-top: 1.5rem;
    padding: 1.25rem;
    background: var(--bg-card-hover);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    text-align: center;
}

.order-teaser h3 { margin-bottom: 0.35rem; }
.order-teaser p  { color: var(--text-secondary); font-size: 0.875rem; margin-bottom: 1rem; }

/* ---- Order history (Min konto) ---- */
.order-history {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.order-history-item {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    padding: 0.75rem;
    background: var(--bg-card-hover);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
}

.order-history-thumb {
    width: 52px;
    height: 52px;
    border-radius: var(--radius-sm);
    object-fit: cover;
    flex-shrink: 0;
}

.order-history-thumb--empty {
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--bg-card-active);
    font-size: 1.4rem;
}

.order-history-info { flex: 1; min-width: 0; }

.order-history-date { font-weight: 700; font-size: 0.9rem; }
.order-history-meta { color: var(--text-secondary); font-size: 0.8rem; margin-bottom: 0.3rem; }

.order-history-status {
    display: inline-block;
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    padding: 0.15rem 0.55rem;
    border-radius: 100px;
    background: var(--bg-card-active);
    color: var(--text-secondary);
}

.order-history-status--pending   { background: rgba(201,169,106,0.18); color: var(--accent); }
.order-history-status--confirmed,
.order-history-status--completed { background: rgba(122,190,140,0.18); color: #8fd6a4; }
.order-history-status--cancelled { background: rgba(226,138,122,0.18); color: var(--danger); }

.btn-order {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.65rem 1.75rem;
    border: 2px solid var(--accent);
    color: var(--accent);
    font-weight: 700;
    border-radius: 100px;
    font-size: 0.9rem;
    text-decoration: none;
    transition: var(--transition);
}

.btn-order:hover {
    background: var(--accent);
    color: #241d12;
    text-decoration: none;
}

/* ---- Error banner ---- */
.error-banner {
    background: rgba(248,113,113,0.1);
    border: 1px solid #f87171;
    color: #f87171;
    border-radius: var(--radius-sm);
    padding: 0.75rem 1rem;
    font-size: 0.875rem;
    margin-top: 0.75rem;
}

/* ---- Auth pages ---- */
.auth-page {
    min-height: 100svh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
    background:
        radial-gradient(ellipse 60% 40% at 50% 0%, rgba(201,169,106,0.06) 0%, transparent 70%),
        var(--bg-primary);
}

.auth-card {
    width: 100%;
    max-width: 420px;
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius-xl);
    padding: 2rem 1.75rem;
    box-shadow: var(--shadow-card);
}

.auth-logo {
    text-align: center;
    font-family: var(--font-display);
    font-size: 2.4rem;
    font-weight: 500;
    letter-spacing: 0.12em;
    color: var(--accent);
    display: block;
    margin-bottom: 0.25rem;
}

.auth-title {
    text-align: center;
    font-size: 1.15rem;
    font-weight: 700;
    margin-bottom: 0.25rem;
}

.auth-subtitle {
    text-align: center;
    font-size: 0.85rem;
    color: var(--text-secondary);
    margin-bottom: 1.75rem;
}

.form-field {
    margin-bottom: 1rem;
}

/* Postnr. and by belong on one line — they are one thought, and stacking them makes a short
   Danish address look like a long form. The postcode is four digits, so it gets just enough
   room for them; the city takes the rest. Collapses to stacked on a narrow phone.

   A grid rather than a flex row, and the reason is the one thing flex cannot do: put the boxes
   of two fields at the same height. Side by side, each field was its own column of label →
   box → hint, so a label that took two lines pushed its box down and the pair no longer lined
   up — 21 px apart on /admin/rabatkoder at 430 px, where "Kampagnepris pr. stk." wraps and
   "Kampagnens navn" does not, and again under it where "Fragt, hjemmelevering" wraps and
   "Fragt, pakkeshop" does not. Nothing about a label's length should move a box, and no
   min-height guessed per label survives the next label.

   So the row owns three bands — label, box, hint — and each field is a subgrid spanning them.
   The label band is as tall as the tallest label in the row, which is what makes every box
   in the row start at the same y whatever its label did. Three is the shape every field in
   the app has (a `<label>`, one control, an optional `<small class="cover-hint">`); a fourth
   child would land in an implicit row outside the span and would need a fourth band here.

   `auto-fit` + a 10 rem minimum reproduces exactly when the old `flex: 1 1 10rem` wrapped —
   two fields share a line from 332 px of row and stack below it, which is a small phone —
   and an only child still takes the full width, because auto-fit collapses the empty track.
   `column-gap` alone: a `gap` would also space the three bands apart, and the label's own
   0.4rem margin already does that. */
.form-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
    grid-template-rows: auto auto auto;
    column-gap: 0.75rem;
}

/* `min-width: 0` is what lets a field shrink below what is inside it — without it a date
   input, whose own minimum is wide, pushed the page 21 px off the side of a 390 px phone.
   `align-items: start` keeps a control its natural height inside a band that a taller
   neighbour has stretched; the label is the exception and hangs from the bottom of its band,
   so a one-line label sits against its own box instead of floating a line above it. */
.form-row .form-field {
    display: grid;
    grid-template-rows: subgrid;
    grid-row: span 3;
    align-items: start;
    min-width: 0;
}

.form-row .form-field > label { align-self: end; }

/* In a grid a width belongs to the track, not to the item in it, so the postcode's 7.5 rem is
   set here rather than on `.form-field--postal` — which is also what stops it losing to
   `.form-row .form-field` on specificity, the way it silently had been. The city takes the
   rest, with a `minmax(0, …)` so a long by-name cannot push the row wider than the card. */
.form-row:has(.form-field--postal) {
    grid-template-columns: 7.5rem minmax(0, 1fr);
}

@media (max-width: 380px) {
    .form-row:has(.form-field--postal) { grid-template-columns: minmax(0, 1fr); }
}

/* "(valgfrit)" sits inside the label, so it must not inherit the label's uppercase shouting. */
.cover-hint-inline {
    text-transform: none;
    letter-spacing: normal;
    font-weight: 400;
    opacity: 0.75;
}

.form-field label {
    display: block;
    font-size: 0.8rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-muted);
    margin-bottom: 0.4rem;
}

.form-field input {
    width: 100%;
    background: var(--bg-card-hover);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: 0.75rem 1rem;
    font-size: 0.95rem;
    color: var(--text-primary);
    font-family: inherit;
    transition: var(--transition);
    outline: none;
}

.form-field input:focus {
    border-color: var(--border-focus);
    box-shadow: 0 0 0 3px rgba(201,169,106,0.1);
}

.form-field input::placeholder { color: var(--text-muted); }

.auth-submit {
    width: 100%;
    padding: 0.85rem;
    background: var(--accent);
    color: #241d12;
    font-size: 0.95rem;
    font-weight: 800;
    border-radius: 100px;
    border: none;
    cursor: pointer;
    transition: var(--transition);
    box-shadow: 0 0 20px var(--accent-glow);
    margin-top: 0.5rem;
}

.auth-submit:hover { background: var(--accent-strong); transform: translateY(-1px); }
.auth-submit:disabled { opacity: 0.4; cursor: not-allowed; transform: none; }

.auth-divider {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin: 1.1rem 0;
    color: var(--text-muted);
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.1em;
}

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

.auth-google-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.65rem;
    width: 100%;
    padding: 0.8rem;
    background: #ffffff;
    color: #1f1f1f;
    font-family: inherit;
    font-size: 0.95rem;
    font-weight: 600;
    border-radius: 100px;
    text-decoration: none;
    transition: var(--transition);
}

.auth-google-btn:hover {
    text-decoration: none;
    transform: translateY(-1px);
    box-shadow: 0 6px 18px rgba(0,0,0,0.35);
}

.auth-google-icon { width: 18px; height: 18px; }

.auth-error {
    background: rgba(248,113,113,0.1);
    border: 1px solid rgba(248,113,113,0.4);
    color: #fca5a5;
    border-radius: var(--radius-sm);
    padding: 0.65rem 1rem;
    font-size: 0.85rem;
    margin-bottom: 1rem;
}

.auth-link-row {
    text-align: center;
    margin-top: 1.25rem;
    font-size: 0.85rem;
    color: var(--text-secondary);
}

.auth-link-row a { color: var(--accent); font-weight: 600; }

/* ---- Bestil (Order) page ---- */
.order-page {
    min-height: 100svh;
    padding: 1.5rem;
    max-width: 600px;
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.order-header {
    display: flex;
    align-items: center;
    gap: 1rem;
    margin-bottom: 0.5rem;
}

/* ---- Validation ---- */
.validation-message { color: #f87171; font-size: 0.8rem; margin-top: 0.25rem; display: block; }
.valid.modified:not([type=checkbox]) { border-color: var(--accent) !important; }
.invalid { border-color: #f87171 !important; }

/* ================================================================
   RESPONSIVE: Tablet and up
   ================================================================ */
@media (min-width: 640px) {
    .photo-grid { grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); }
    .layout-grid { grid-template-columns: repeat(4, 1fr); }
    .landing-hero { padding: 3rem 2rem; }
}

@media (min-width: 900px) {
    .builder-content { padding: 1.75rem 2rem; }
}

/* ================================================================
   Blazor error UI
   Lives in the GLOBAL stylesheet (not component-scoped) so it stays
   applied after enhanced navigation, which can drop the scoped CSS
   bundle from <head>. #blazor-error-ui is a framework-level element,
   so it belongs here — matching the default Blazor template.
   ================================================================ */
#blazor-error-ui {
    background: #1a0a0a;
    color: #ff6b6b;
    bottom: 0;
    box-shadow: 0 -2px 8px rgba(0,0,0,0.5);
    display: none;
    left: 0;
    padding: 0.75rem 1.5rem;
    position: fixed;
    width: 100%;
    z-index: 9999;
    font-size: 0.9rem;
    border-top: 1px solid #ff6b6b44;
}

#blazor-error-ui .dismiss {
    cursor: pointer;
    position: absolute;
    right: 1rem;
    top: 50%;
    transform: translateY(-50%);
    opacity: 0.7;
}

#blazor-error-ui .reload {
    color: #c9a96a;
    margin-left: 0.5rem;
}

/* ================================================================
   Photo crop editor (.pfx) — pan, zoom, and what it costs in print
   ================================================================ */
/* Above the page viewer (520): the editor is opened from inside it. */
.pfx-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(10,9,8,0.86);
    backdrop-filter: blur(4px);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
    z-index: 560;
}

.pfx {
    width: 100%;
    max-width: 420px;
    max-height: 100%;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    background: var(--bg-card);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-lg);
    padding: 1.1rem;
    box-shadow: var(--shadow-card);
}

/* The dialog scrolls when it runs out of room; nothing inside it gets squashed to make room. */
.pfx > * { flex-shrink: 0; }

.pfx-head {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
}

.pfx-head-text { flex: 1; min-width: 0; }

.pfx-title {
    font-family: var(--font-display);
    font-size: 1.05rem;
    font-weight: 500;
    line-height: 1.2;
}

.pfx-sub {
    font-size: 0.75rem;
    color: var(--text-secondary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.pfx-step { display: flex; align-items: center; gap: 0.2rem; }

.pfx-step-count {
    font-size: 0.72rem;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.pfx-icon {
    width: 28px;
    height: 28px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: var(--bg-card-hover);
    color: var(--text-secondary);
    font-size: 0.95rem;
    line-height: 1;
    transition: var(--transition);
}

.pfx-icon:hover:not(:disabled) { background: var(--bg-card-active); color: var(--text-primary); }
.pfx-icon:disabled { opacity: 0.3; cursor: default; }

/* ---- The frame is the printed slot ---- */
/* Sized from the slot's own aspect (--stage-ar, set by the component from the print geometry)
   rather than from the space the column has left over. flex:none is load-bearing: as a flex item
   it would otherwise be shrunk to fit the dialog, and a squashed frame is a frame showing a crop
   that is not the one being printed — which is the whole thing this editor promises. Height is
   bounded by capping the width, so the shape survives on short screens too. */
.pfx-stage {
    position: relative;
    flex: none;
    --stage-ar: 1;
    width: min(100%, calc(52vh * var(--stage-ar)));
    aspect-ratio: var(--stage-ar);
    margin: 0 auto;
    overflow: hidden;
    border-radius: var(--radius-md);
    background: var(--bg-primary);
    outline: 1px solid var(--border-strong);
    outline-offset: -1px;
    touch-action: none;
    cursor: grab;
}

.pfx-stage:focus-visible { outline: 2px solid var(--accent); }
.pfx-stage.is-adjusting { cursor: grabbing; }

.pfx-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    user-select: none;
    -webkit-user-drag: none;
    pointer-events: none;
}

/* Rule-of-thirds guides, shown only while the crop is being moved: they are a tool for
   the gesture, not decoration to look at for the rest of the time. */
.pfx-guides {
    position: absolute;
    inset: 0;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.18s ease;
    background:
        linear-gradient(to right, transparent calc(33.333% - 0.5px), rgba(255,255,255,0.45) calc(33.333% - 0.5px),
            rgba(255,255,255,0.45) calc(33.333% + 0.5px), transparent calc(33.333% + 0.5px),
            transparent calc(66.666% - 0.5px), rgba(255,255,255,0.45) calc(66.666% - 0.5px),
            rgba(255,255,255,0.45) calc(66.666% + 0.5px), transparent calc(66.666% + 0.5px)),
        linear-gradient(to bottom, transparent calc(33.333% - 0.5px), rgba(255,255,255,0.45) calc(33.333% - 0.5px),
            rgba(255,255,255,0.45) calc(33.333% + 0.5px), transparent calc(33.333% + 0.5px),
            transparent calc(66.666% - 0.5px), rgba(255,255,255,0.45) calc(66.666% - 0.5px),
            rgba(255,255,255,0.45) calc(66.666% + 0.5px), transparent calc(66.666% + 0.5px));
}

.pfx-stage.is-adjusting .pfx-guides { opacity: 1; }

.pfx-hint {
    font-size: 0.72rem;
    color: var(--text-secondary);
    text-align: center;
    line-height: 1.45;
}

.pfx-hint-keys { display: block; color: var(--text-muted); font-size: 0.68rem; }

/* ---- Zoom ---- */
.pfx-zoom { display: flex; align-items: center; gap: 0.6rem; }

.pfx-zoom-label {
    font-size: 0.72rem;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.08em;
}

/* The mark on the track is where this photo stops printing at 300 dpi. Everything to the
   left of it is press-sharp; the tinted stretch to the right is the part you pay for. */
.pfx-track {
    position: relative;
    flex: 1;
    display: flex;
    align-items: center;
}

.pfx-track::before {
    content: "";
    position: absolute;
    left: var(--quality-stop, 100%);
    right: 0;
    top: 50%;
    height: 4px;
    transform: translateY(-50%);
    border-radius: 2px;
    background: repeating-linear-gradient(45deg,
        rgba(226,138,122,0.5) 0 3px, rgba(226,138,122,0.15) 3px 6px);
    pointer-events: none;
}

.pfx-range { width: 100%; accent-color: var(--accent); position: relative; }

.pfx-mini {
    font-size: 0.72rem;
    font-weight: 600;
    color: var(--text-secondary);
    padding: 0.3rem 0.6rem;
    border: 1px solid var(--border);
    border-radius: 100px;
    white-space: nowrap;
    transition: var(--transition);
}

.pfx-mini:hover { border-color: var(--border-strong); color: var(--text-primary); }

/* ---- Print resolution readout ---- */
.pfx-quality {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.5rem 0.7rem;
    background: var(--bg-card-hover);
    border-radius: var(--radius-sm);
}

.pfx-meter {
    flex: 1;
    height: 5px;
    border-radius: 3px;
    background: var(--bg-card-active);
    overflow: hidden;
    position: relative;
}

.pfx-meter::after {
    content: "";
    position: absolute;
    inset: 0 auto 0 0;
    width: var(--fill, 0%);
    border-radius: 3px;
    background: var(--accent);
    transition: width 0.12s linear, background 0.2s ease;
}

.pfx-meter[data-level="sharp"]::after { background: #8fd6a4; }
.pfx-meter[data-level="ok"]::after    { background: var(--accent); }
.pfx-meter[data-level="soft"]::after  { background: var(--danger); }

.pfx-dpi {
    font-size: 0.75rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    color: var(--text-secondary);
}

.pfx-dpi[data-level="sharp"] { color: #8fd6a4; }
.pfx-dpi[data-level="ok"]    { color: var(--accent-soft); }
.pfx-dpi[data-level="soft"]  { color: var(--danger); }

.pfx-quality-help {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    border: 1px solid var(--border-strong);
    color: var(--text-muted);
    font-size: 0.6rem;
    font-weight: 700;
    cursor: help;
}

/* ---- Toggles + actions ---- */
.pfx-tools { display: flex; flex-direction: column; gap: 0.4rem; }

.pfx-toggle {
    width: 100%;
    padding: 0.6rem 0.9rem;
    background: transparent;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    color: var(--text-secondary);
    font-size: 0.85rem;
    font-weight: 600;
    transition: var(--transition);
}

.pfx-toggle:hover:not(:disabled) { border-color: var(--accent); color: var(--accent-soft); }
.pfx-toggle:disabled { opacity: 0.4; cursor: default; }

.pfx-toggle--danger { color: var(--danger); }
.pfx-toggle--danger:hover:not(:disabled) { border-color: var(--danger); color: var(--danger); }

/* What the button above it will cost the page count. In the album's own gold rather than the
   delete red: pressing it is allowed and undoable — this is a price, not a refusal. */
.pfx-note {
    /* Closer to the button it belongs to than to the one below it — inside an evenly spaced
       stack, a note the same distance from both reads as a note about the wrong button. */
    margin-top: -0.15rem;
    padding: 0.55rem 0.75rem;
    background: rgba(201,169,106,0.10);
    border-radius: var(--radius-sm);
    color: var(--accent-soft);
    font-size: 0.72rem;
    line-height: 1.45;
    text-align: center;
}

/* The delete confirmation sits between the tools and the footer, where the button that opened
   it is — rather than as a second dialog on top of the one the customer is already in. */
.pfx-confirm {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    padding: 0.75rem 0.9rem;
    border: 1px solid var(--danger);
    border-radius: var(--radius-sm);
    background: rgba(226, 138, 122, 0.08);
}

.pfx-confirm-text {
    font-size: 0.8rem;
    line-height: 1.45;
    color: var(--text-secondary);
}

.pfx-confirm-actions { display: flex; gap: 0.5rem; justify-content: flex-end; }

.pfx-toggle.is-active {
    background: var(--accent);
    border-color: var(--accent);
    color: #241d12;
}

.pfx-actions { display: flex; gap: 0.65rem; }
.pfx-actions .app-btn { flex: 1; justify-content: center; }

/* ---- Admin orders panel ---- */
.admin-orders { display: flex; flex-direction: column; gap: 0.6rem; }

.admin-order-row {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.75rem;
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    flex-wrap: wrap;
}

.admin-order-main { flex: 1; min-width: 12rem; }
.admin-order-customer { margin: 0; display: flex; flex-direction: column; }
.admin-order-email { color: var(--text-secondary); font-size: 0.8rem; }
.admin-order-meta { margin: 0.25rem 0 0; color: var(--text-muted); font-size: 0.8rem; }

/* Was the status <select>. Nothing in the panel sets a status any more — the payment does — so
   this is a badge that reports one. */
.admin-order-status {
    font-size: 0.75rem;
    font-weight: 600;
    padding: 0.3rem 0.65rem;
    border: 1px solid var(--border-strong);
    border-radius: 100px;
    color: var(--text-secondary);
    white-space: nowrap;
}

.admin-order-status--paid { color: var(--accent-soft); border-color: rgba(228,207,159,0.35); }

.admin-order-nopdf { color: var(--text-muted); font-size: 0.8rem; }

/* A print job is two downloads — the cover and the interior — so they stack as a small block
   rather than sitting loose in the row next to the order's own details. */
.print-job-files { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }
.print-job-hint {
    flex-basis: 100%;
    color: var(--text-muted);
    font-size: 0.75rem;
}
/* A job coming back through the press. It has to read as a badge rather than as part of the
   reference, because the reference is what the shop matches against work they have already done. */
.print-job-reprint {
    margin-left: 0.5rem;
    padding: 0.1rem 0.4rem;
    border-radius: 999px;
    background: #b45309;
    color: #fff;
    font-size: 0.65rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    vertical-align: middle;
}

/* An order with nowhere to ship it needs to stand out — it can't be fulfilled. */
.admin-order-noaddress { color: #b45309; }

/* The print shop posts the parcel, so this is a label to copy rather than a line of metadata:
   set as an address block, one element per line, in the app's reading colour rather than the
   muted one the surrounding job details use. */
.print-job-address {
    display: flex;
    flex-direction: column;
    margin-top: 0.5rem;
    padding: 0.5rem 0.65rem;
    background: var(--bg-card-hover);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    font-style: normal;
    font-size: 0.85rem;
    line-height: 1.35;
    color: var(--text-primary);
    width: fit-content;
    min-width: 12rem;
}

.print-job-address-label {
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-muted);
    margin-bottom: 0.2rem;
}

.print-job-address-phone { color: var(--text-secondary); margin-top: 0.2rem; }

.order-price-row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    margin: 0.75rem 0;
    padding-top: 0.75rem;
    border-top: 1px solid var(--border);
    font-size: 1.05rem;
}
.order-price-row strong { color: var(--accent); }
/* Sits under the total: the moms is part of that figure, not another charge. */
.order-price-vat { color: var(--text-muted); font-size: 0.85rem; margin: 0.25rem 0 0; text-align: right; }

/* ---- Checkout: the terms consent and the card marks ---- */

/* The tick box between the customer and the order button. Given a height floor the same way the
   delivery options were: one line of text is 29 px, under the ~44 px a fingertip actually covers,
   and a consent that is awkward to hit on a phone is a consent that gets pressed twice. */
.terms-consent {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    box-sizing: border-box;
    min-height: 2.75rem;
    margin-top: 1.25rem;
    padding: 0.5rem 0;
    font-size: 0.9rem;
    line-height: 1.5;
    color: var(--text-primary);
    cursor: pointer;
}
.terms-consent input {
    width: auto;
    margin-top: 0.25rem;
    flex-shrink: 0;
    accent-color: var(--accent);
    cursor: pointer;
}
.terms-consent a { color: var(--accent); }

/* The order button spends the first part of every visit disabled — the terms are not ticked yet —
   so it has to look it. An enabled-looking button that does nothing reads as the page being
   broken, which is the same reason the soft-photo dialog's button carries this. */
.order-actions .app-btn[disabled] {
    opacity: 0.45;
    cursor: not-allowed;
}

.order-payment-marks { margin-top: 1.25rem; text-align: center; }

.payment-marks {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.5rem;
}

/* The tiles are white — the marks are drawn for light backgrounds — so they need an edge of their
   own, or they read as holes cut in the page rather than as cards. */
.payment-mark {
    display: inline-flex;
    line-height: 0;
    border-radius: 4px;
    box-shadow: 0 0 0 1px var(--border-strong);
}

.order-payment-note {
    margin-top: 0.5rem;
    color: var(--text-muted);
    font-size: 0.8rem;
}

/* ---- Admin shell (dashboard / orders / messages) ---- */
.admin-page {
    min-height: 100svh;
    padding: 1.5rem;
    max-width: 880px;
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.admin-nav {
    display: flex;
    gap: 0.4rem;
    flex-wrap: wrap;
    margin-bottom: 0.25rem;
}

.admin-nav-link {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.5rem 1rem;
    border-radius: 100px;
    font-size: 0.85rem;
    font-weight: 700;
    letter-spacing: 0.03em;
    color: var(--text-secondary);
    background: var(--bg-card);
    border: 1px solid var(--border);
    text-decoration: none;
}

.admin-nav-link:hover { color: var(--text-primary); background: var(--bg-card-hover); text-decoration: none; }

.admin-nav-link--active {
    color: #241d12;
    background: linear-gradient(180deg, var(--accent-soft), var(--accent) 60%, var(--accent-strong));
    border-color: transparent;
}

.admin-nav-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.25rem;
    height: 1.25rem;
    padding: 0 0.35rem;
    border-radius: 100px;
    background: var(--danger);
    color: #241d12;
    font-size: 0.7rem;
    font-weight: 800;
}

.admin-nav-link--active .admin-nav-badge { background: #241d12; color: var(--accent-soft); }

/* Dashboard KPI grid */
.admin-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: 0.75rem;
}

.admin-stat {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    padding: 1.1rem 1.2rem;
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}

.admin-stat__value {
    font-family: var(--font-display);
    font-size: 1.9rem;
    font-weight: 600;
    color: var(--text-primary);
    line-height: 1;
}

.admin-stat__label {
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-muted);
}

.admin-stat--accent .admin-stat__value { color: var(--accent); }

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

/* Messages inbox */
.admin-messages { display: flex; flex-direction: column; gap: 0.6rem; }

.admin-msg {
    padding: 0.9rem 1rem;
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}

.admin-msg--unread { border-color: var(--border-focus); background: var(--bg-card-hover); }

.admin-msg-top {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.75rem;
    flex-wrap: wrap;
}

.admin-msg-from { font-weight: 700; color: var(--text-primary); font-size: 0.95rem; }
.admin-msg-from a { color: var(--text-secondary); font-weight: 500; font-size: 0.85rem; margin-left: 0.4rem; }
.admin-msg-date { font-size: 0.75rem; color: var(--text-muted); white-space: nowrap; }
.admin-msg-subject { font-weight: 600; color: var(--accent); font-size: 0.9rem; }
.admin-msg-body { color: var(--text-secondary); font-size: 0.9rem; line-height: 1.55; white-space: pre-wrap; }
.admin-msg-actions { display: flex; gap: 0.5rem; margin-top: 0.2rem; flex-wrap: wrap; align-items: center; }

/* Only when something follows it. On /admin/ordrer this row is the PostNord download and the
   order list comes straight after — with nothing between them the button's edge touched the
   first order's card, measured at 0 px, and controls do not sit flush against what they act on.
   In a message card the same row is the last thing in the card, and a margin there would only
   pad the bottom of every message. */
.admin-msg-actions:not(:last-child) { margin-bottom: 1rem; }

/* The row that holds the long ones. "Hent 4 forsendelser som CSV til PostNord Send" is a
   sentence, not a label, and .app-btn's nowrap — the rule that keeps "Næste →" on one line — ran
   it off the side of the card on a phone: the row wraps, but a pill whose text cannot break has
   nothing to wrap. Here a button may break its lines, centre them, and never grow past the row.
   The short buttons in this row are unaffected: text that fits does not wrap. */
.admin-msg-actions .app-btn {
    white-space: normal;
    text-align: center;
    max-width: 100%;
    /* A pill's 12 px of vertical padding is judged against one line of text. Two lines fill it to
       the edges, so the label reads as crammed into the shape rather than set inside it. */
    padding-top: 0.85rem;
    padding-bottom: 0.85rem;
}
.admin-msg-confirm { color: var(--danger); font-size: 0.85rem; font-weight: 600; margin-left: auto; }

.admin-unread-dot {
    display: inline-block;
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    background: var(--accent);
    margin-right: 0.4rem;
}

/* ---- Contact form extras ---- */
.auth-card--wide { max-width: 520px; }

.form-field textarea {
    width: 100%;
    background: var(--bg-card-hover);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: 0.75rem 1rem;
    font-size: 0.95rem;
    color: var(--text-primary);
    font-family: inherit;
    line-height: 1.5;
    resize: vertical;
    transition: var(--transition);
}

.form-field textarea:focus {
    border-color: var(--border-focus);
    box-shadow: 0 0 0 3px rgba(201,169,106,0.1);
}

.form-field textarea::placeholder { color: var(--text-muted); }

.auth-success {
    background: rgba(201,169,106,0.1);
    border: 1px solid var(--border-focus);
    color: var(--text-primary);
    border-radius: var(--radius-sm);
    padding: 0.85rem 1rem;
    font-size: 0.9rem;
    line-height: 1.5;
    margin-bottom: 1rem;
}

.auth-submit--link { display: block; text-align: center; text-decoration: none; }
.auth-submit--link:hover { text-decoration: none; }

/* ---- Sticky footer layout ---- */
.memo-app { display: flex; flex-direction: column; min-height: 100vh; }
.memo-main { flex: 1 0 auto; }

/* ---- Site footer ---- */
.site-footer {
    flex-shrink: 0;
    margin-top: 3rem;
    padding: 2.5rem 1.25rem calc(2.5rem + env(safe-area-inset-bottom));
    background: rgba(26, 23, 20, 0.6);
    border-top: 1px solid var(--border);
}

/* Three bands — the pages, the marks, the company — and the gap between them is deliberately
   larger than any gap inside one. That is the whole of the grouping: every row sat 1rem from the
   next before, so six links, two glyphs, two cards and five scraps of company detail read as one
   undifferentiated heap. */
.site-footer-inner {
    max-width: 900px;
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    gap: 2rem;
}

/* Wide apart across, close together down: on a phone these wrap to two lines, and the wrap has to
   look like one list continuing rather than two rows of something. */
.site-footer-links { display: flex; flex-wrap: wrap; gap: 0.6rem 1.75rem; }
.site-footer-links a { color: var(--text-secondary); text-decoration: none; font-size: 0.875rem; }
.site-footer-links a:hover { color: var(--accent); }

/* The glyphs and the cards on one line, with a gap wide enough that they read as two groups and
   not as four icons in a row. */
.site-footer-marks {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 1rem 2rem;
}

/* The social marks. Sized as touch targets rather than as glyphs — the icon is 20 px and the
   thing you press is 44, which is the difference between a link and a link a thumb can hit. */
.site-footer-social { display: flex; gap: 0.5rem; }

.site-social {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.75rem;
    height: 2.75rem;
    margin: -0.6rem 0;
    border-radius: 50%;
    color: var(--text-secondary);
    transition: var(--transition);
}

.site-social:hover,
.site-social:focus-visible {
    color: var(--accent);
    background: var(--bg-card-hover);
}

/* The marks are centred where they sit at the end of the checkout, under the button they belong
   to. Down here they are one item in a column of left-aligned rows, and centring them would be
   the only thing in the footer that does not start at the same edge. */
.site-footer .payment-marks { justify-content: flex-start; }

/* One detail per line on a phone. Wrapping them was what made this corner look like a heap: five
   scraps of company laid out by whatever happened to fit turned into a two-column table by
   accident — "MemoMinder ApS" over there, "CVR: …" over here — with no rule behind either column
   and a different shape at every width. */
.site-footer-legal {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    color: var(--text-muted);
    font-size: 0.8rem;
    line-height: 1.5;
    border-top: 1px solid var(--border);
    padding-top: 1.25rem;
}
.site-footer-legal a { color: var(--text-muted); }

/* Where the whole run fits on one line it can have it, and then the details need a separator of
   their own: a dot rather than a wider gap, because at that point they are one sentence's worth
   of company rather than a list. Kept to the width where they genuinely fit — a dot left hanging
   at the end of a wrapped line is exactly the sloppiness this is here to avoid, and measuring is
   the only way to know: the run wants ~720 px, so it wraps at 720 and clears at 900 with room to
   spare for a machine that has to fall back from Inter to something wider. */
@media (min-width: 900px) {
    .site-footer-legal { flex-direction: row; flex-wrap: wrap; gap: 0.3rem 0.85rem; }

    .site-footer-legal span:not(:last-child)::after {
        content: "·";
        margin-left: 0.85rem;
        color: var(--border-strong);
    }
}

/* ---- Legal / info pages ---- */
.legal-page {
    max-width: 760px;
    margin: 0 auto;
    padding: 1.5rem 1.25rem 2rem;
    line-height: 1.7;
}
.legal-page h1 { font-family: 'Fraunces', serif; margin-bottom: 0.5rem; }

/* Prose spacing. The reset zeroes every margin, which on a page that is nothing but prose meant
   two paragraphs ran together as one block — "…bæredygtighed for øje." and "Vi holder til på Als"
   read as a single thought on /om-os, and the terms and the privacy policy are pages of that.
   Only unclassed paragraphs: the ones that carry a class (.legal-lead, .how-note,
   .product-price-offer) are laid out by their own rule, and this selector would outrank it. */
.legal-page p:not([class]) { margin-bottom: 0.9rem; }

/* The same reset takes the indent off lists, and a marker with nothing to sit in is drawn
   *outside* the text column — into the page's own 1.25rem of padding, which on a phone left the
   bullets a few pixels from the edge of the screen. The indent puts them back inside the measure
   they belong to. Bare lists only; .how-steps and the rest bring their own layout. */
.legal-page ul,
.legal-page ol {
    margin: 0 0 1rem;
    padding-left: 1.35rem;
}

.legal-page li { margin-bottom: 0.35rem; }
.legal-page li:last-child { margin-bottom: 0; }
.legal-page h2 { margin: 1.75rem 0 0.5rem; font-size: 1.15rem; color: var(--accent); }
/* Subheadings inside a numbered clause. The reset zeroes every margin, so without this they sit
   flush against the paragraph above and read as part of it. Not accent-coloured: the colour is
   what tells a clause heading apart from a heading inside one. */
.legal-page h3 { margin: 1.25rem 0 0.4rem; font-size: 1rem; }
/* Prose links only. A link that is dressed as a button brings its own colours, and this rule
   outranks them — `.legal-page a` is a class plus an element, `.btn-start` is one class — so
   without the exclusion the gold here lands on top of the gold button and the label disappears.
   That is what happened to "START DIT MAGASIN" on /magasinet. */
.legal-page a:not(.btn-start):not(.app-btn) { color: var(--accent); }
.legal-lead { font-size: 1.05rem; color: var(--text-secondary); }
.legal-updated { margin-top: 2rem; color: var(--text-muted); font-size: 0.8rem; }

/* ---- Price breakdown, discount code and mængderabat ---------------------------------
   The lines above the total. They are deliberately quieter than .order-price-row: that row
   is the number the customer is agreeing to, and everything here is the arithmetic behind it. */
.order-price-lines {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    margin-top: 1rem;
}
.order-price-line {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 1rem;
    color: var(--text-secondary);
    font-size: 0.92rem;
}
.order-price-line--save { color: var(--accent-soft); }

/* ---- Delivery method, on the order form -------------------------------------------
   Two radios under the address. The whole row is the label, so the price is as clickable
   as the dot is — on a phone the dot alone is a target worth missing.

   Qualified as label.delivery-option, and the four reverts below are not decoration. These
   radios sit inside a .form-field, whose rules are written for a captioned text box: its
   `label` is a block of uppercase muted grey with a caption's margin under it, and a lone
   class does not outrank `.form-field label`. Left to it, the row stopped being a row — the
   text read as a field caption and the dot, stretched to `width: 100%` by `.form-field
   input`, drew itself centred on a line of its own above it. */
label.delivery-option {
    display: flex;
    align-items: baseline;
    gap: 0.55rem;
    cursor: pointer;
    color: var(--text-primary);
    font-size: 0.95rem;
    font-weight: 400;
    letter-spacing: normal;
    text-transform: none;
    margin-bottom: 0;

    /* A finger, not a cursor. The row is the target, and one line of text is 29 px of it —
       under the ~44 px a fingertip actually covers, which is how a tap lands between the two
       options and does nothing. min-height sets the floor rather than padding alone doing it,
       so the row that wraps to two lines on a narrow phone grows from its own height instead
       of being padded twice. */
    box-sizing: border-box;
    min-height: 2.75rem;
    padding: 0.5rem 0;
}
.delivery-option input { width: auto; accent-color: var(--accent); cursor: pointer; }

/* No top margin of its own: the row above it now ends in half a rem of padding, and the two
   together read as a gap between the option and its own explanation. */
.delivery-option + .cover-hint { margin: 0 0 0.7rem 1.55rem; }
.order-price-was {
    color: var(--text-muted);
    margin-right: 0.4rem;
}
.order-price-saved {
    margin: 0.25rem 0 0;
    text-align: right;
    color: var(--accent-soft);
    font-size: 0.9rem;
}

/* The box the client asked for: somewhere to type a code at payment. */
.discount-box {
    margin-top: 1.25rem;
    padding: 1rem;
    background: var(--bg-card);
    border: 1px dashed var(--border-strong);
    border-radius: var(--radius-md);
}
.discount-box-label {
    display: block;
    margin-bottom: 0.5rem;
    color: var(--text-secondary);
    font-size: 0.9rem;
}
.discount-box-row {
    display: flex;
    gap: 0.5rem;
    align-items: stretch;
    flex-wrap: wrap;
}
.discount-box-input {
    flex: 1 1 10rem;
    min-width: 0;
    padding: 0.7rem 0.9rem;
    background: var(--bg-primary);
    color: var(--text-primary);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    font-size: 1rem;
    /* Codes are stored upper case, so they are shown that way while being typed — it saves
       explaining that "sommer2026" and "SOMMER2026" are the same code. */
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.discount-box-input:focus {
    outline: none;
    border-color: var(--border-focus);
}
.discount-box-input::placeholder { color: var(--text-muted); letter-spacing: normal; }
.discount-box-btn { flex: 0 0 auto; }
.discount-box-ok {
    margin: 0.6rem 0 0;
    color: var(--accent-soft);
    font-size: 0.9rem;
}
.discount-box-error {
    margin: 0.6rem 0 0;
    color: var(--danger);
    font-size: 0.9rem;
}

/* ---- Newsletter sign-up ---------------------------------------------------------- */
.newsletter-box {
    margin: 2rem 0 0;
    padding: 1.25rem;
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
}
.newsletter-title {
    margin: 0 0 0.35rem;
    font-family: 'Fraunces', serif;
    font-size: 1.1rem;
    color: var(--accent);
}
.newsletter-text {
    margin: 0 0 0.9rem;
    color: var(--text-secondary);
    font-size: 0.95rem;
}
.newsletter-row {
    display: flex;
    gap: 0.5rem;
    flex-wrap: wrap;
}
.newsletter-input {
    flex: 1 1 12rem;
    min-width: 0;
    padding: 0.7rem 0.9rem;
    background: var(--bg-primary);
    color: var(--text-primary);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    font-size: 1rem;
}
.newsletter-input:focus { outline: none; border-color: var(--border-focus); }
.newsletter-input::placeholder { color: var(--text-muted); }
.newsletter-btn { flex: 0 0 auto; }
.newsletter-consent {
    margin: 0.75rem 0 0;
    color: var(--text-muted);
    font-size: 0.8rem;
    line-height: 1.5;
}

/* A consent tick-box: the label is the hit area, so the whole line is clickable on a phone. */
.auth-check {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    margin: 1rem 0;
    color: var(--text-secondary);
    font-size: 0.9rem;
    line-height: 1.5;
    cursor: pointer;
}
.auth-check input { margin-top: 0.2rem; flex: 0 0 auto; }

/* ---- Admin: discount codes and the newsletter list -------------------------------- */
.admin-price-summary {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    margin: 0.75rem 0;
}

/* The save button and the "last changed" line beside it. They wrap on a narrow screen rather
   than pushing the stamp off the edge — the owner does this from a phone as often as not. */
.admin-price-actions {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex-wrap: wrap;
    margin-top: 0.5rem;
}
.admin-code-list {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    margin-top: 1rem;
}
.admin-code {
    padding: 0.9rem 1rem;
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
}
/* A retired code is kept and dimmed rather than removed — orders still name it. */
.admin-code--off { opacity: 0.55; }
/* Wraps, so that a name too long to share the line takes the line and sends the badge to the
   next one — rather than the badge being broken, squeezed, or pushed out of the card. */
.admin-code-top {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: baseline;
    gap: 0.25rem 1rem;
}

/* The name is a discount code on /admin/rabatkoder and an e-mail address on /admin/nyhedsbrev,
   and an address is both longer and — being one unbroken token — unbreakable. On a 390 px phone
   `cabuxito1995@gmail.com` measured 225 px against 263 px of row, which left too little for the
   badge beside it: neither could shrink, so the badge hung 25 px off the right side of its own
   card. `min-width: 0` lets the name give way and `overflow-wrap` lets it break where there is
   no space to break at, which is what keeps the pair inside the card. */
.admin-code-name {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 1.05rem;
    letter-spacing: 0.06em;
    color: var(--text-primary);
    min-width: 0;
    overflow-wrap: anywhere;
}

/* The badge keeps its whole width — it is one word, and a "Tilmeldt" broken in half to save a
   character of the address beside it is the wrong trade. `margin-left: auto` is what keeps it on
   the right of its line once it has been sent to a line of its own, where space-between has
   nothing left to space it against. */
.admin-code-value {
    color: var(--accent);
    font-weight: 600;
    flex-shrink: 0;
    margin-left: auto;
}
.admin-code-desc { margin: 0.35rem 0 0; color: var(--text-secondary); font-size: 0.9rem; }
.admin-code-meta { margin: 0.35rem 0 0; color: var(--text-muted); font-size: 0.82rem; }
/* The edit form opens inside the row it belongs to, ruled off from the code's own lines so it
   reads as part of that code and not as a second form on the page. */
.admin-code-edit {
    margin-top: 0.75rem;
    padding-top: 0.75rem;
    border-top: 1px solid var(--border);
}

/* ---- The product shot on /magasinet ------------------------------------------------
   The photograph is not in the repository yet. The page checks for the file before rendering
   the figure at all, so nothing here has to cope with it being absent. */
.product-shot {
    margin: 1.5rem 0;
    text-align: center;
}
.product-shot img {
    max-width: 100%;
    height: auto;
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-card);
}
.product-shot figcaption {
    margin-top: 0.6rem;
    color: var(--text-muted);
    font-size: 0.85rem;
}
.product-price-offer {
    padding: 0.9rem 1rem;
    background: var(--bg-card);
    border-left: 3px solid var(--accent);
    border-radius: var(--radius-sm);
    color: var(--accent-soft);
}
.product-price-offer s { color: var(--text-muted); margin: 0 0.15rem; }

/* The admin discount form has the only <select> on the site, so it gets the same treatment the
   inputs beside it already have — otherwise it renders as the browser's own grey control in the
   middle of a dark card. */
.form-field select {
    width: 100%;
    background: var(--bg-card-hover);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: 0.75rem 1rem;
    font-size: 0.95rem;
    color: var(--text-primary);
    font-family: inherit;
    outline: none;
}
.form-field select:focus { border-color: var(--border-focus); }

/* ---- Admin: the mængderabat tiers ---------------------------------------------------
   One row per tier: the quantity it starts at, the percentage as an editable field, and a way
   to remove it. The quantity is text and not an input on purpose — moving a tier from 3 to 5
   is "remove this one and add another", so editing it in place is not offered. */
.admin-tier-list {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin: 1rem 0;
}
.admin-tier {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex-wrap: wrap;
    padding: 0.7rem 0.9rem;
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
}
.admin-tier-qty {
    flex: 1 1 8rem;
    color: var(--text-primary);
}
.admin-tier-edit {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    flex: 0 0 auto;
}
.admin-tier-edit input {
    width: 5rem;
    padding: 0.5rem 0.6rem;
    background: var(--bg-card-hover);
    color: var(--text-primary);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    font-family: inherit;
    font-size: 0.95rem;
    text-align: right;
    outline: none;
}
.admin-tier-edit input:focus { border-color: var(--border-focus); }
.admin-tier-unit { color: var(--text-muted); }
.admin-tier-new { margin-top: 1.25rem; }

/* ---- Soft-photo consent dialog ---------------------------------------------------------- */
/* The last thing between the customer and paying, so it is a real modal rather than a banner
   they can scroll past: the client asked for it precisely so a complaint about a blurred print
   can be answered, and that only holds if it was unmissable. */

/* The uploader's duplicate question shares the shape of the soft-photo dialog below rather than
   inventing a second one: they are the same kind of interruption, and two dialogs that look
   almost alike read as a bug. Only what differs — a list of file names instead of thumbnails —
   has rules of its own, further down. */
.soft-photo-backdrop,
.dup-photo-backdrop {
    position: fixed;
    inset: 0;
    z-index: 60;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
    background: rgba(10, 8, 6, 0.78);
    backdrop-filter: blur(3px);
}

.soft-photo-dialog,
.dup-photo-dialog {
    width: min(34rem, 100%);
    max-height: min(90vh, 44rem);
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 1rem;
    padding: 1.5rem;
    background: var(--bg-card);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-card);
}

.soft-photo-title,
.dup-photo-title {
    margin: 0;
    font-size: 1.15rem;
    color: var(--text-primary);
}

.soft-photo-text,
.dup-photo-text {
    margin: 0;
    font-size: 0.9rem;
    line-height: 1.55;
    color: var(--text-secondary);
}

/* Sized so a handful of photos are all visible at once and forty do not push the consent and
   its button off the bottom of the dialog. */
.soft-photo-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(4.5rem, 1fr));
    gap: 0.5rem;
    max-height: 14rem;
    overflow-y: auto;
    padding: 0.25rem;
}

.soft-photo-item { margin: 0; }

.soft-photo-item img,
.soft-photo-item-blank {
    display: block;
    width: 100%;
    aspect-ratio: 1;
    object-fit: cover;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border-strong);
    background: var(--bg-secondary);
}

.soft-photo-item figcaption {
    margin-top: 0.2rem;
    font-size: 0.62rem;
    text-align: center;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}

.soft-photo-consent {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    font-size: 0.85rem;
    line-height: 1.5;
    color: var(--text-primary);
    cursor: pointer;
}

.soft-photo-consent input { margin-top: 0.2rem; flex-shrink: 0; }

.soft-photo-actions,
.dup-photo-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    justify-content: flex-end;
}

/* Disabled until the box is ticked, and it has to look it — an enabled-looking button that does
   nothing reads as the page being broken. */
.soft-photo-actions .app-btn[disabled] {
    opacity: 0.45;
    cursor: not-allowed;
}

/* The duplicates are named rather than shown: the files have not been uploaded, so there is no
   thumbnail to show, and a name is what the customer recognises them by anyway. Capped at a few
   rows tall so a selection where everything is a duplicate does not push the buttons off screen. */
.dup-photo-list {
    margin: 0;
    padding: 0.5rem 0.75rem;
    max-height: 11rem;
    overflow-y: auto;
    list-style: none;
    background: var(--bg-secondary);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    font-size: 0.85rem;
    line-height: 1.7;
    color: var(--text-primary);
}

/* A long file name wraps rather than widening the dialog or hiding its own extension. */
.dup-photo-list li {
    overflow-wrap: anywhere;
}

.dup-photo-more {
    color: var(--text-muted);
    font-style: italic;
}
