/* RecipeDetail page styles — moved verbatim from the inline <style> block
   in Components/Pages/RecipeDetail.razor (large-file split, phase 1b).
   Everything is namespaced under .printable-recipe / recipe-* classes, so
   loading it globally (linked from App.razor) collides with nothing. */
    .original-text-pane {
        margin-top: 1.5rem;
    }
    /* Both collapsible sections share one "section header" look: a full-width
       light-grey bar with a centred title and a caret expand/collapse control
       at the right edge. The whole bar is the toggle button. */
    .original-text-pane .original-text-header,
    .recipe-ai-pane .recipe-ai-header,
    .recipe-photos-pane .recipe-photos-header {
        position: relative;
        padding: 0;
    }
    .original-text-toggle,
    .recipe-ai-toggle,
    .photos-toggle {
        width: 100%; box-sizing: border-box;
        position: relative;
        display: flex; align-items: center; justify-content: center;
        margin: 0.6rem 0 0;
        padding: 0.6rem 2.2rem;
        background: #ececec;
        border: 1px solid var(--border);
        border-radius: 8px;
        color: var(--text);
        font-size: 0.95rem; font-weight: 600;
        cursor: pointer; user-select: none; text-align: center;
    }
    .original-text-toggle:hover,
    .recipe-ai-toggle:hover,
    .photos-toggle:hover { background: #e2e2e2; color: var(--text); }
    /* Caret pinned right so the title stays centred in the bar. */
    .original-text-toggle .toggle-caret,
    .recipe-ai-toggle .toggle-caret,
    .photos-toggle .toggle-caret {
        position: absolute; right: 0.9rem; top: 50%; transform: translateY(-50%);
        font-size: 1.2rem; color: var(--muted);
    }
    /* The "Added ..." date sits at the left of the original-text bar; not
       interactive, so clicks fall through to the toggle underneath. */
    .original-text-pane .original-text-added {
        position: absolute; left: 0.9rem; top: 50%; transform: translateY(-50%);
        z-index: 1; pointer-events: none;
        color: var(--muted); font-size: 0.72rem; font-style: italic;
    }
    .recipe-ai-pane { margin-top: 1rem; }
    .recipe-photos-pane { margin-top: 1rem; }

    /* Collapsible Ingredients / Steps / Notes headers — same grey bar as the
       panes above, but reusable (keyed) and print-aware. */
    .recipe-collapse-toggle {
        width: 100%; box-sizing: border-box; position: relative;
        display: flex; align-items: center; justify-content: center;
        margin: 1.1rem 0 0.6rem;
        padding: 0.6rem 2.2rem;
        background: #ececec; border: 1px solid var(--border); border-radius: 8px;
        color: var(--text); font-size: 1rem; font-weight: 600;
        cursor: pointer; user-select: none; text-align: center;
    }
    .recipe-collapse-toggle:hover { background: #e2e2e2; color: var(--text); }
    .recipe-collapse-toggle .toggle-caret {
        position: absolute; right: 0.9rem; top: 50%; transform: translateY(-50%);
        font-size: 1.2rem; color: var(--muted);
    }
    .recipe-collapse-body.is-collapsed { display: none; }
    /* Paper: no interactive bar chrome and never hide content. The toggle
       becomes a plain left-aligned heading so the section label still prints. */
    @media print {
        .recipe-collapse-body.is-collapsed { display: block !important; }
        .recipe-collapse-toggle {
            background: none; border: none; border-radius: 0; padding: 0;
            margin: 0.8rem 0 0.3rem; justify-content: flex-start;
            font-size: 1.05rem; cursor: auto;
        }
        .recipe-collapse-toggle .toggle-caret { display: none; }
    }
    @media (max-width: 600px) {
        /* On a phone the centred title fills the narrow bar, so the
           absolutely-positioned "Added ..." date overlapped it. Hide the date
           on small screens (it still shows on wider screens with room to spare). */
        .original-text-pane .original-text-added { display: none; }
    }
    .recipe-ai-body { padding: 0.25rem 0 0.5rem; }
    /* Input + Ask button share one line and stretch to the same height. */
    .recipe-ai-row { display: flex; gap: 0.5rem; align-items: stretch; }
    .recipe-ai-combo { position: relative; z-index: 30; flex: 1 1 auto; min-width: 0; }
    .recipe-ai-input {
        width: 100%; box-sizing: border-box;
        font-family: inherit; font-size: 0.95rem;
        /* Taller box; fills the stretched row so it matches the button. */
        height: 100%; min-height: 2.9rem;
        padding: 0.5rem 0.6rem; border: 1px solid var(--border);
        border-radius: 8px; background: #fff; color: var(--text);
        /* Above the click-away backdrop (z20) so the input stays clickable. */
        position: relative; z-index: 32;
    }
    /* Above the click-away backdrop (which paints at the combo's z30 level),
       like the input (z32) — otherwise the first click on Ask while the history
       dropdown is open just closes the dropdown instead of submitting. */
    .recipe-ai-ask { flex: 0 0 auto; white-space: nowrap; position: relative; z-index: 32; }
    .recipe-ai-input:focus { outline: none; border-color: var(--primary, #2e7d46); }
    /* Transparent full-screen catcher so a click anywhere outside closes the
       dropdown. Sits below the combo (z-index 30) but above page content. */
    .recipe-ai-combo-backdrop {
        position: fixed; inset: 0; z-index: 20; background: transparent;
    }
    .recipe-ai-history {
        position: absolute; top: calc(100% + 2px); left: 0; right: 0; z-index: 31;
        margin: 0; padding: 0.2rem; list-style: none;
        max-height: 15rem; overflow-y: auto;
        background: #fff; border: 1px solid var(--border); border-radius: 8px;
        box-shadow: 0 6px 20px rgba(0,0,0,0.14);
    }
    .recipe-ai-history-item {
        display: flex; align-items: stretch; gap: 0.25rem; border-radius: 6px;
    }
    .recipe-ai-history-item:hover { background: var(--card, #f3efe6); }
    .recipe-ai-history-pick {
        flex: 1 1 auto; min-width: 0; text-align: left; cursor: pointer;
        background: none; border: 0; padding: 0.4rem 0.5rem;
        font-family: inherit; font-size: 0.9rem; color: var(--text);
        white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    }
    .recipe-ai-history-del {
        flex: 0 0 auto; cursor: pointer; background: none; border: 0;
        padding: 0 0.55rem; color: var(--muted); font-size: 0.85rem; border-radius: 6px;
    }
    .recipe-ai-history-del:hover { color: #b94737; background: rgba(185,71,55,0.1); }
    .recipe-ai-actions { margin-top: 0.5rem; }
    .recipe-ai-error { color: #b94737; font-size: 0.9rem; margin: 0.6rem 0 0; }
    .recipe-ai-answer {
        margin-top: 0.8rem; padding: 0.75rem 0.9rem;
        background: var(--card, #faf8f2); border: 1px solid var(--border);
        border-radius: 8px; font-size: 0.95rem; line-height: 1.5;
    }
    .recipe-ai-answer > :first-child { margin-top: 0; }
    .recipe-ai-answer > :last-child { margin-bottom: 0; }
    /* Wide tables (cost breakdowns etc.) scroll horizontally rather than
       overflowing the card on narrow screens. */
    .recipe-ai-answer { overflow-x: auto; }
    .recipe-ai-answer table {
        border-collapse: collapse; margin: 0.6rem 0; font-size: 0.9rem;
    }
    .recipe-ai-answer th, .recipe-ai-answer td {
        border: 1px solid var(--border); padding: 0.35rem 0.6rem;
        text-align: left; vertical-align: top;
    }
    .recipe-ai-answer th { background: rgba(0,0,0,0.05); font-weight: 600; }
    .edit-original-btn {
        background: none; border: 1px solid var(--border); cursor: pointer;
        color: var(--muted); font-size: 0.8rem; line-height: 1;
        padding: 0.25rem 0.55rem; border-radius: 4px;
        display: inline-flex; align-items: center; gap: 0.3rem;
        transition: color 0.15s, border-color 0.15s;
    }
    .edit-original-btn:hover { color: var(--primary); border-color: var(--primary); }
    .original-text-edit {
        width: 100%; box-sizing: border-box;
        margin-top: 0.5rem; padding: 0.8rem 1rem;
        font-family: inherit; font-size: 0.95rem; line-height: 1.5;
        border: 1px solid var(--border); border-radius: 6px;
        background: #fffdf7;
        resize: vertical;
    }
    /* Click-to-edit on the original-text block. Matches the notes-block
       cue: hover border turns primary-green. */
    .original-text-clickable {
        cursor: pointer;
        transition: border-color 0.12s;
        border: 1px solid transparent;
    }
    .original-text-clickable:hover { border-color: var(--primary); }
    .edit-meta-row {
        display: flex; flex-wrap: wrap; gap: 0.75rem;
        margin-top: 0.5rem;
    }
    .edit-meta-row label {
        display: flex; flex-direction: column; flex: 1 1 14rem; gap: 0.2rem;
    }
    .edit-meta-label {
        font-size: 0.8rem; color: var(--muted);
    }
    .edit-meta-hint {
        font-size: 0.8rem; color: var(--muted);
        margin: 0.4rem 0 0.3rem;
    }
    .scroll-nav {
        position: fixed; right: 1rem; bottom: 1rem;
        display: flex; flex-direction: column; gap: 0.4rem;
        z-index: 50;
    }
    .scroll-nav-btn {
        width: 38px; height: 38px;
        background: rgba(255,255,255,0.92); border: 1px solid var(--border);
        border-radius: 999px; cursor: pointer;
        color: var(--muted); line-height: 0;
        box-shadow: 0 2px 8px rgba(0,0,0,0.12);
        transition: color 0.15s, border-color 0.15s, opacity 0.2s, transform 0.2s;
        display: inline-flex; align-items: center; justify-content: center;
        opacity: 0; pointer-events: none; transform: translateY(8px);
    }
    .scroll-nav-btn.visible {
        opacity: 1; pointer-events: auto; transform: translateY(0);
    }
    .scroll-nav-btn:hover { color: var(--primary); border-color: var(--primary); }
    .original-text-actions {
        display: flex; align-items: center; gap: 0.5rem;
        margin-top: 0.5rem;
    }
    .reprocess-bar {
        display: flex; align-items: center; gap: 0.5rem;
        margin: 0.75rem 0;
    }
    .original-text-pane pre {
        margin: 0.5rem 0 0;
        padding: 2rem 2.5rem;
        white-space: pre-wrap;
        word-break: break-word;
        /* Caveat goes first now that it's loaded as a web font (see
           App.razor) — it has Cyrillic + Latin coverage on every
           platform. Segoe Script / Comic Sans stay as fallbacks for
           offline / network-blocked clients on Windows. The trailing
           generic cursive fallback was dropping to OS handwriting
           fonts that lacked Cyrillic on Samsung tablets, leaving
           Russian recipes rendered in some random italic script. */
        font-family: 'Caveat', 'Segoe Script', 'Comic Sans MS', cursive;
        font-size: 0.95rem;
        line-height: 1.8;
        color: #3a3520;
        max-height: 600px;
        overflow-y: auto;
        border-radius: 4px;

        /* Aged paper background */
        background:
            linear-gradient(to bottom,
                rgba(0,0,0,0) 0%,
                rgba(0,0,0,0.02) 50%,
                rgba(0,0,0,0) 100%),
            repeating-linear-gradient(
                to bottom,
                transparent 0px,
                transparent 27px,
                rgba(180, 160, 130, 0.15) 27px,
                rgba(180, 160, 130, 0.15) 28px
            ),
            linear-gradient(135deg,
                #fdf6e3 0%,
                #f5edda 25%,
                #faf3e4 50%,
                #f0e6ce 75%,
                #f8f0dc 100%);

        /* Paper edge shadow */
        box-shadow:
            2px 3px 12px rgba(0,0,0,0.08),
            0 0 0 1px rgba(180, 160, 130, 0.3),
            inset 0 0 40px rgba(180, 160, 130, 0.06);

        /* Slight paper rotation for authenticity */
        transform: rotate(-0.3deg);

        /* Faded red margin line */
        border-left: 2px solid rgba(200, 80, 80, 0.2);
    }
    .recipe-drop-zone {
        position: relative;
        border: 2px solid transparent;
        border-radius: var(--radius);
        padding: 0.5rem;
        margin: -0.5rem;
        transition: border-color 0.15s, background 0.15s;
    }
    .recipe-drop-zone.drag-over {
        border-color: var(--primary);
        border-style: dashed;
        background: #f5fbf0;
    }
    .drop-hint {
        display: none;
        text-align: center;
        color: var(--primary);
        font-size: 0.9rem;
        padding: 0.75rem;
    }
    .recipe-drop-zone.drag-over .drop-hint { display: block; }

    .recipe-notes {
        background: #faf8f0; border: 1px solid #e8e0c8; border-radius: var(--radius);
        padding: 1rem; color: #555; font-style: italic; line-height: 1.6;
        margin-bottom: 1rem; white-space: pre-wrap;
    }
    /* User-edited notes sit above the AI notes. Subtly different so the
       distinction is visible but the block still reads as one area. */
    .recipe-notes-user {
        background: #fff; border-color: #d8e3c2;
        color: #333; font-style: normal;
    }
    .recipe-notes-ai {
        /* same as .recipe-notes default — italic + muted. */
    }
    .recipe-notes-head {
        display: flex; align-items: center; gap: 0.5rem;
        margin: 1rem 0 0.5rem;
    }
    .recipe-notes-head h2, .recipe-notes-head h3 { margin: 0; }
    /* Click-to-edit on the notes panel. Hover hints at editability
       without cluttering the resting visual. */
    .recipe-notes-clickable { cursor: pointer; transition: border-color 0.12s, background 0.12s; }
    .recipe-notes-clickable:hover {
        border-color: var(--primary);
    }
    .recipe-notes-empty {
        color: var(--muted); font-style: italic; text-align: center;
        border-style: dashed;
    }
    .recipe-notes-edit .form-control {
        width: 100%; min-height: 6rem; font-family: inherit;
        box-sizing: border-box;
    }
    .recipe-notes-actions {
        display: flex; gap: 0.5rem; margin-top: 0.5rem;
    }
    .recipe-section {
        border-top: 2px solid var(--border);
        padding-top: 1rem;
        margin-top: 1.5rem;
    }
    .recipe-section:first-of-type {
        border-top: none;
        padding-top: 0;
        margin-top: 0;
    }
    .recipe-section .section-title {
        color: var(--primary);
        margin-bottom: 0.25rem;
        display: flex; align-items: baseline; gap: 0.6rem; flex-wrap: wrap;
    }
    .recipe-section .variation-badge {
        background: #fff5f3; color: #b94737;
        border: 1px solid #e0c5c1; border-radius: 999px;
        padding: 0.15rem 0.6rem;
        font-size: 0.8rem; font-weight: 500;
        white-space: nowrap;
    }
    .recipe-section .section-subtitle {
        color: var(--muted);
        font-size: 0.95rem;
        font-weight: 400;
    }
    /* Toolbar that lives above the title: back-link on the left, favorite +
       translate on the right. On a phone the title row below stays
       uncluttered so title + subtitle can fit on one line. */
    .recipe-toolbar {
        display: flex; align-items: center; gap: 0.6rem;
        margin: 0 0 0.6rem;
    }
    .recipe-toolbar .recipe-back {
        flex: 0 0 auto;
        color: var(--primary); text-decoration: none;
        font-size: 0.95rem;
    }
    .recipe-toolbar .recipe-back:hover { text-decoration: underline; }
    .recipe-toolbar .fav-btn {
        margin-left: auto;
        width: 32px; height: 32px;
        display: inline-flex; align-items: center; justify-content: center;
    }
    .recipe-toolbar .translate-btn { flex: 0 0 auto; }
    .recipe-toolbar .btn-print {
        padding: 0.25rem 0.55rem; font-size: 1rem; line-height: 1;
        background: transparent; border: 1px solid var(--border);
        border-radius: 6px; cursor: pointer;
        display: inline-flex; align-items: center; justify-content: center;
    }
    .recipe-toolbar .btn-print:hover { border-color: var(--primary); }

    /* Admin-only image-upgrade button — sits immediately to the left of the
       favourite heart. We give it the auto-margin so it (and everything
       after) gets pushed to the right edge; the heart loses its own
       margin-left:auto when the admin button is present so they stay
       glued together as a pair. */
    .recipe-toolbar .img-upgrade-btn {
        margin-left: auto;
        width: 32px; height: 32px;
        display: inline-flex; align-items: center; justify-content: center;
        background: transparent; border: 1px solid var(--border);
        border-radius: 6px; cursor: pointer;
        color: #b8860b;
    }
    .recipe-toolbar .img-upgrade-btn:hover { border-color: #b8860b; }
    /* Read-only / shared-recipe affordances. The "Recipe from {name}"
       badge is a low-key tag pill (same warm-tan palette as the rest
       of the toolbar's chrome) so it reads as context, not warning.
       The "Copy to my recipes" button mirrors the export pill — same
       border + padding language so the toolbar still looks consistent
       even though the editing controls are gone. */
    .recipe-toolbar .recipe-shared-badge {
        display: inline-flex; align-items: center; gap: 0.4rem;
        background: #f1ece4; border: 1px solid #e0d6c3;
        color: #5a4e3a;
        border-radius: 999px;
        padding: 0.25rem 0.7rem;
        font-size: 0.85rem; font-weight: 500;
        white-space: nowrap;
    }
    .recipe-toolbar .recipe-shared-badge svg { opacity: 0.7; }
    .recipe-toolbar .copy-recipe-btn {
        display: inline-flex; align-items: center; gap: 0.4rem;
        background: transparent; color: var(--primary);
        border: 1px solid var(--primary);
        border-radius: 999px;
        padding: 0.3rem 0.85rem;
        font-size: 0.85rem; font-weight: 600; line-height: 1;
        cursor: pointer;
        transition: background 0.12s, color 0.12s;
    }
    .recipe-toolbar .copy-recipe-btn:hover:not(:disabled) {
        background: var(--primary); color: #fff;
    }
    .recipe-toolbar .copy-recipe-btn:disabled { opacity: 0.6; cursor: progress; }
    .recipe-toolbar .copy-recipe-btn .translate-spin { width: 16px; height: 16px; }
    .recipe-toolbar .img-upgrade-btn:disabled,
    .recipe-toolbar .img-upgrade-btn.is-busy { cursor: progress; opacity: 0.7; }
    .recipe-toolbar .img-upgrade-btn .translate-spin { width: 18px; height: 18px; }
    .recipe-toolbar:has(.img-upgrade-btn) .fav-btn { margin-left: 0; }

    /* Add-to-shopping-list toolbar button — pinned to the right of the
       toolbar, immediately to the left of the favourite heart. Same
       sizing language as the heart so the pair reads as a unit. The
       margin-left:auto pushes it (and everything after) to the right
       edge; when both this button and the admin upgrade button are
       present, the upgrade button keeps the auto-push and this loses
       it via :has() so the trio sits glued together. */
    .recipe-toolbar .sl-toolbar-btn {
        margin-left: auto;
        width: 32px; height: 32px;
        display: inline-flex; align-items: center; justify-content: center;
        background: transparent; border: 0;
        border-radius: 6px; cursor: pointer;
        color: var(--primary);
        transition: color 120ms ease, background-color 120ms ease, transform 120ms ease;
    }
    .recipe-toolbar .sl-toolbar-btn:hover {
        background-color: rgba(127, 162, 71, 0.12);
        transform: scale(1.06);
    }
    .recipe-toolbar:has(.img-upgrade-btn) .sl-toolbar-btn { margin-left: 0; }
    .recipe-toolbar:has(.sl-toolbar-btn) .fav-btn { margin-left: 0; }
    .img-upgrade-status {
        font-size: 0.85rem; color: #555;
        white-space: nowrap;
    }
    .img-upgrade-status.is-error { color: #b1411b; }

    /* Export-format dropdown — anchored to the Export button, opens
       downward-left so it stays inside the viewport on narrow phones. */
    .export-menu-wrap { position: relative; display: inline-block; }
    .export-menu-backdrop {
        position: fixed; inset: 0; z-index: 40; background: transparent;
    }
    .export-menu {
        position: absolute; top: calc(100% + 4px); right: 0; z-index: 41;
        min-width: 160px; padding: 4px;
        background: var(--card-bg, #fff);
        border: 1px solid var(--border, #ddd); border-radius: 8px;
        box-shadow: 0 6px 20px rgba(0,0,0,0.12);
        display: flex; flex-direction: column;
    }
    .export-menu button,
    .export-menu .export-menu-link {
        display: flex; align-items: center; gap: 0.55rem;
        padding: 0.5rem 0.7rem;
        background: transparent; border: 0; border-radius: 6px;
        font-size: 0.95rem; line-height: 1.2; cursor: pointer;
        text-align: left; color: inherit; text-decoration: none;
    }
    .export-menu button:hover,
    .export-menu .export-menu-link:hover { background: rgba(0,0,0,0.05); text-decoration: none; }
    .export-menu .export-menu-icon { font-size: 1rem; line-height: 1; }
    /* Phones: drop PDF + Word from the export menu — desktop-oriented file
       downloads that are awkward on mobile; Print, Email, and Share remain. */
    @media (max-width: 600px) {
        .export-menu .export-hide-mobile { display: none; }
    }
    /* "shared" pill next to "Copy public link" when the recipe currently
       has a live share token. Visual cue so the owner remembers a public
       link is out in the wild without having to remember when they
       generated it. */
    .export-menu .export-menu-badge {
        display: inline-block;
        margin-left: 0.4rem;
        padding: 0.05rem 0.45rem;
        background: rgba(127, 162, 71, 0.18);
        color: var(--primary);
        border-radius: 999px;
        font-size: 0.72rem;
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.04em;
        vertical-align: middle;
    }
    /* Inline Yes/No confirm strip that replaces a menu item while the
       user resolves the prompt. Rust-tinted left border + neutral
       background so it reads as "halt, decide" without being as alarming
       as the photo-delete prompt's red-on-white. */
    .export-menu .export-menu-confirm {
        display: flex; align-items: center; gap: 0.45rem;
        padding: 0.45rem 0.85rem;
        background: rgba(190, 100, 30, 0.06);
        border-left: 3px solid #b76a1f;
        font-size: 0.92rem;
        color: var(--text);
    }
    .export-menu .export-menu-confirm .btn-yes,
    .export-menu .export-menu-confirm .btn-no {
        border: 0; border-radius: 6px;
        padding: 0.2rem 0.65rem;
        font-size: 0.85rem; font-weight: 600;
        cursor: pointer;
    }
    .export-menu .export-menu-confirm .btn-yes {
        background: #b76a1f; color: #fff;
    }
    .export-menu .export-menu-confirm .btn-no {
        background: transparent; color: var(--muted);
    }
    .export-menu .export-menu-confirm .btn-no:hover { color: var(--text); }

    /* Toolbar "shared" pill — sits in the top-right of the recipe page,
       immediately to the LEFT of the admin image-upgrade button (which
       in turn sits left of the heart). Quiet primary-green chip, link-
       chain glyph, "shared" label. Click → inline confirm. Visible to
       the owner only on recipes that currently have a live ShareToken. */
    .shared-pill {
        display: inline-flex; align-items: center; gap: 0.35rem;
        padding: 0.3rem 0.65rem;
        background: rgba(127, 162, 71, 0.12);
        border: 1px solid rgba(127, 162, 71, 0.35);
        color: var(--primary);
        border-radius: 999px;
        font-size: 0.82rem; font-weight: 600;
        cursor: pointer;
        transition: background 120ms ease, transform 120ms ease;
    }
    .shared-pill:hover { background: rgba(127, 162, 71, 0.22); }
    .shared-pill:active { transform: scale(0.96); }
    /* When the toolbar pill flips into confirm mode it expands inline —
       same warm-rust treatment as the export-menu confirmation so the
       user reads it as "you are about to undo something". */
    .shared-pill-confirm {
        display: inline-flex; align-items: center; gap: 0.45rem;
        padding: 0.3rem 0.65rem;
        background: rgba(190, 100, 30, 0.08);
        border: 1px solid #b76a1f;
        border-radius: 8px;
        font-size: 0.85rem;
        color: var(--text);
    }
    .shared-pill-confirm .btn-yes,
    .shared-pill-confirm .btn-no {
        border: 0; border-radius: 5px;
        padding: 0.15rem 0.55rem;
        font-size: 0.82rem; font-weight: 600;
        cursor: pointer;
    }
    .shared-pill-confirm .btn-yes { background: #b76a1f; color: #fff; }
    .shared-pill-confirm .btn-no { background: transparent; color: var(--muted); }
    .shared-pill-confirm .btn-no:hover { color: var(--text); }

    /* Pill popover — anchors under the pill, holds Copy link + Stop
       sharing. Right-aligned so it never overflows the viewport edge on
       mobile where the pill lives in the top-right toolbar. */
    .shared-pill-wrap { position: relative; display: inline-flex; }
    .shared-pill-menu {
        position: absolute; top: calc(100% + 0.4rem); right: 0;
        z-index: 40;
        display: flex; flex-direction: column;
        min-width: 11rem;
        background: var(--surface, #fff);
        border: 1px solid rgba(0, 0, 0, 0.12);
        border-radius: 10px;
        box-shadow: 0 8px 24px rgba(0, 0, 0, 0.16);
        padding: 0.3rem;
    }
    .shared-pill-item {
        display: flex; align-items: center; gap: 0.55rem;
        width: 100%;
        padding: 0.5rem 0.6rem;
        background: transparent; border: 0; border-radius: 7px;
        font-size: 0.9rem; font-weight: 600; color: var(--text);
        text-align: left; cursor: pointer;
    }
    .shared-pill-item:hover { background: rgba(127, 162, 71, 0.12); }
    .shared-pill-item svg { opacity: 0.75; flex: 0 0 auto; }
    .shared-pill-item.danger { color: #9a3b2e; }
    .shared-pill-item.danger:hover { background: rgba(154, 59, 46, 0.1); }
    .shared-pill-menu .shared-pill-confirm { display: flex; flex-wrap: wrap; }

    /* Inline "Email this recipe" form rendered inside .export-menu when
       the user picks the Email action. Keeps the menu's anchor + outside-
       click backdrop without a separate modal. */
    .export-menu .export-email-form {
        min-width: 240px; padding: 8px 6px 6px;
        display: flex; flex-direction: column; gap: 8px;
    }
    .export-menu .export-email-title {
        font-weight: 600; font-size: 0.95rem; padding: 0 4px;
    }
    .export-menu .export-email-form .form-control {
        font-size: 0.95rem; padding: 0.4rem 0.55rem;
    }
    .export-menu .export-email-actions {
        display: flex; justify-content: flex-end; gap: 6px;
    }
    .export-menu .export-email-actions .btn { padding: 0.35rem 0.7rem; font-size: 0.9rem; }
    .export-menu .export-email-status { font-size: 0.85rem; padding: 0 4px; }
    .export-menu .export-email-status.is-ok { color: #2f7a1c; }
    .export-menu .export-email-status.is-error { color: #b3261e; }
    .export-menu .export-email-note {
        font-size: 0.78rem; color: var(--muted, #777); padding: 0 4px;
    }

    /* One-second "Copied!" flash next to the Export button after the
       user picks "Copy recipe" from the menu. Absolutely positioned
       (parent .export-menu-wrap is already position:relative) so
       appearing / disappearing never reflows the toolbar — neighbouring
       icons stay put. white-space:nowrap keeps the pill on one line so
       it doesn't wrap and overlap the export button awkwardly. */
    .copy-flash {
        position: absolute;
        left: calc(100% + 0.5rem);
        top: 50%; transform: translateY(-50%);
        display: inline-flex; align-items: center; gap: 0.4rem;
        padding: 0.25rem 0.6rem;
        background: #eaf6df; border: 1px solid #b3d99a; border-radius: 999px;
        color: #2f5c1c; font-size: 0.88rem; line-height: 1.2;
        white-space: nowrap;
        z-index: 5; pointer-events: none;
        animation: copyFlashIn 120ms ease-out;
    }
    .copy-flash-icon {
        display: inline-flex; align-items: center; justify-content: center;
        width: 16px; height: 16px; border-radius: 50%;
        background: #5a9e2f; color: #fff; font-size: 0.75rem;
    }
    /* Opacity-only — don't animate transform here, the pill uses
       translateY(-50%) for vertical centering and animating transform
       fights with that, leaving the pill mis-aligned during the fade-in. */
    @keyframes copyFlashIn {
        from { opacity: 0; }
        to   { opacity: 1; }
    }

    .recipe-header {
        display: flex; align-items: flex-start; gap: 0.6rem;
        margin-bottom: 0.4rem;
    }
    .recipe-header .recipe-title {
        margin: 0;
        display: flex; flex-wrap: wrap; align-items: baseline;
        gap: 0.5rem 0.75rem;
    }
    .recipe-header .recipe-subtitle {
        color: var(--muted); font-weight: 400; font-size: 1.1rem;
    }
    .recipe-description {
        margin: 0 0 1.25rem; color: #444; line-height: 1.55;
    }
    /* Inline-editable title + subtitle — transparent borders that appear on
       hover/focus so the controls read as static text until the user
       interacts. Mirrors the shopping-list detail pattern. */
    .recipe-title-editable {
        flex: 1; min-width: 0;
        display: flex; flex-wrap: wrap; align-items: baseline;
        gap: 0.15rem 0.25rem;
    }
    /* On phones: force title + subtitle onto one line, truncate with
       ellipsis if the content is too long for the viewport. The
       toolbar row above already took the heart + translate out of
       the way, so full width is available here. */
    @media (max-width: 720px) {
        /* Title and subtitle each take their own full-width line so the
           subtitle is shown in full instead of being truncated onto the
           title's line. */
        .recipe-title-editable { flex-wrap: wrap; }
        .recipe-title-input, .recipe-subtitle-input { flex: 1 1 100%; min-width: 0; }
        .recipe-title-input { font-size: 1.35rem; }
        .recipe-subtitle-input { font-size: 0.95rem; }
    }
    .recipe-title-input, .recipe-subtitle-input {
        /* field-sizing: content lets the <input> auto-size to its text
           so title and subtitle sit right next to each other at rest.
           Hover/focus then widens it to a comfortable editing width. */
        field-sizing: content;
        min-width: 2ch;
        border: 1px solid transparent; background: transparent;
        padding: 0.15rem 0.4rem; border-radius: 6px;
        font-family: inherit;
        flex: 0 1 auto;
        transition: min-width 0.15s, background 0.12s, border-color 0.12s;
        /* These are textareas now (so long text wraps): kill the resize grip and
           the scrollbar — field-sizing:content grows the height to the text. */
        resize: none;
        overflow: hidden;
        vertical-align: top;
    }
    .recipe-title-input {
        font-size: 1.6rem; font-weight: 700; line-height: 1.2; color: inherit;
    }
    .recipe-subtitle-input {
        font-size: 1.05rem; font-weight: 400; color: var(--muted);
        font-style: italic;
    }
    /* Non-italic while the user is actively editing — italic muted text
       reads as static metadata; during edit we want it to feel like a
       regular form field. */
    .recipe-subtitle-input:focus { font-style: normal; }
    /* Hover or focus → expand to a comfortable editing width without
       reflowing the rest of the page. field-sizing still follows content
       inside that min-width floor, so the input can grow further if the
       user types more. */
    .recipe-title-input:hover, .recipe-title-input:focus {
        border-color: var(--border); background: #fff;
        outline: none; min-width: 20rem;
    }
    .recipe-subtitle-input:hover, .recipe-subtitle-input:focus {
        border-color: var(--border); background: #fff;
        outline: none; min-width: 14rem;
    }
    /* When the subtitle is empty, field-sizing:content collapses the input
       to almost nothing (just the 2ch min-width), which makes it
       impossible to discover or click into. Widen it while the placeholder
       is showing so the "+ subtitle" hint is visible at rest. */
    .recipe-subtitle-input:placeholder-shown {
        min-width: 9rem;
        border-color: var(--border);
        background: rgba(255, 255, 255, 0.6);
    }
    .recipe-title-input.readonly, .recipe-subtitle-input.readonly {
        display: inline-block;
        padding: 0.15rem 0.4rem; border: 1px solid transparent;
    }
    .recipe-title-input.readonly:hover, .recipe-subtitle-input.readonly:hover {
        background: transparent; border-color: transparent;
    }

    /* Ad-hoc translation toggle — ghost pill like the Start cooking /
       Save as separate buttons, filled when active so the state is
       obvious at a glance. Sits on the right edge of the title row. */
    .translate-btn {
        flex-shrink: 0; align-self: center;
        display: inline-flex; align-items: center; gap: 0.35rem;
        background: transparent; color: var(--primary);
        border: 1px solid var(--primary);
        padding: 0.3rem 0.7rem; border-radius: 999px;
        font-size: 0.82rem; font-weight: 600; line-height: 1;
        cursor: pointer;
        transition: background 0.12s, color 0.12s;
    }
    .translate-btn:hover:not(:disabled) { background: var(--primary); color: #fff; }
    .translate-btn.on { background: var(--primary); color: #fff; }
    .translate-btn:disabled { opacity: 0.6; cursor: default; }
    .translate-btn svg { width: 16px; height: 16px; }
    .translate-spin { animation: translate-spin 0.9s linear infinite; transform-origin: 50% 50%; }
    @keyframes translate-spin { from { transform: rotate(0); } to { transform: rotate(360deg); } }
    .recipe-source-footer {
        margin-top: 1.5rem; padding-top: 0.8rem;
        border-top: 1px dashed var(--border);
        color: var(--muted); font-size: 0.85rem;
    }
    /* Tags + Collections share one row, 50/50. Each child .recipe-tag-row /
       .recipe-collection-row keeps its own padding + dashed-pill skin and
       just claims half the band width. flex-basis: 0; flex-grow: 1 makes
       both children divide the available width equally regardless of how
       many chips each contains, so the visual stays balanced even when one
       side has a sprawling tag library and the other has two collections.
       On viewports under 720 px the band wraps so each half is full-width. */
    /* Read-only tag/collection chips for shared-link viewers (IWA-34).
       Same palettes as the owner grid's card chips: soft green tags,
       amber collections. */
    .ro-chip-row {
        display: flex; flex-wrap: wrap; gap: .35rem;
        margin: 0.2rem 0 1rem;
    }
    .ro-chip {
        display: inline-flex; align-items: center; gap: .25rem;
        padding: .18rem .6rem .18rem .45rem;
        font-size: .78rem; font-weight: 600; line-height: 1.3;
    }
    .ro-chip > span { font-size: .85rem; line-height: 1; }
    .ro-chip-tag {
        background: #eef4e3; border: 1px solid #a8c47e; color: #3c5318;
        border-radius: 999px;
    }
    .ro-chip-coll {
        background: #f4e6c2; border: 1px solid #c69d4d; color: #5a3f0a;
        border-radius: 6px;
    }

    .recipe-tagcol-band {
        display: flex; flex-wrap: wrap; gap: 0.6rem;
        align-items: stretch;
        margin: 0.2rem 0 1rem;
    }
    .recipe-tagcol-band > .recipe-tag-row,
    .recipe-tagcol-band > .recipe-collection-row {
        flex: 1 1 0;
        min-width: 280px;
        margin: 0;
        /* align-self: stretch is the flex default given the parent's
           align-items: stretch above, but spell it out so a future
           "should the tags pane be its own height?" change has to
           override this explicitly rather than tripping over the
           default. */
        align-self: stretch;
        /* Chips hug the TOP of each pane instead of centring inside the
           equalised height. Without this, when one pane is taller than
           its content (because it's stretched to match the other), its
           chips drift down to the middle, which looks like the rows
           are misaligned with each other. */
        align-content: flex-start;
    }
    .recipe-tagcol-band > .recipe-tag-row { align-items: flex-start; }
    .recipe-tagcol-band > .recipe-collection-row { align-items: flex-start; }
    .recipe-tag-row {
        display: flex; flex-wrap: wrap; gap: 0.35rem;
        align-items: center;
        margin: 0.2rem 0 1rem;
        padding: 0.5rem 0.6rem;
        background: #fbf9f3; border: 1px dashed #d8cfb9;
        border-radius: 8px;
    }
    /* Tighter right padding on the recipe-view tag chips — halved
       again from the global .tag-chip's 0.35rem to 0.175rem per
       request. Scoped to .recipe-tag-row so the MyRecipes filter bar
       chips keep their own padding. */
    .recipe-tag-row .tag-chip {
        padding-right: 0.175rem;
    }
    /* Collapsed state: hide unselected chips and the "+ add tag"
       input. The row shows only the currently-attached tags plus
       the toggle chevron — keeps the recipe header tight when the
       user has a sprawling tag catalogue, especially on mobile. */
    .recipe-tag-row.collapsed .tag-chip:not(.active),
    .recipe-tag-row.collapsed .tag-add-input {
        display: none;
    }
    .recipe-tag-row-toggle {
        display: inline-flex; align-items: center; justify-content: center;
        gap: 0.3rem;
        /* Comfortable tap target — well above Apple's 44 px /
           Google's 48 dp minimum, even with the vertical "tags"
           label tucked alongside the chevron. */
        min-width: 2.6rem;
        height: 2.1rem;
        padding: 0 0.55rem;
        background: transparent;
        color: #8a7e60;
        border: 1px dashed #c8bf9a;
        border-radius: 999px;
        cursor: pointer;
        line-height: 1;
        transition: color .12s, border-color .12s, background .12s;
    }
    .recipe-tag-row-toggle:hover {
        color: #463c25;
        border-color: var(--primary, #5a9e2f);
        background: #fff;
    }
    /* Vertical "tags" label written sideways. writing-mode rotates
       the glyphs so the word reads bottom-up; text-orientation:upright
       keeps individual letters upright instead of also rotating them,
       which makes a short word like "tags" stack legibly. The font is
       small so the button stays compact next to the chip row. */
    .recipe-tag-row-toggle-label {
        writing-mode: vertical-rl;
        text-orientation: mixed;
        transform: rotate(180deg); /* turn bottom-up into top-down */
        font-size: 0.7rem;
        letter-spacing: 0.05em;
        line-height: 1;
        font-weight: 500;
        color: inherit;
    }
    /* Chevron at ~1.4rem so the arrow itself is visibly larger
       than the label it sits next to. */
    .recipe-tag-row-toggle-chevron {
        font-size: 1.4rem;
        line-height: 1;
    }
    .thumb-badge {
        position: absolute; top: 0.3rem; left: 0.3rem;
        background: var(--primary); color: #fff; font-size: 0.65rem;
        padding: 0.1rem 0.4rem; border-radius: 3px; z-index: 1;
    }
    .photo-gallery {
        display: grid;
        grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
        gap: 0.75rem;
    }
    .photo-card {
        position: relative;
        border-radius: var(--radius);
        overflow: hidden;
        border: 1px solid var(--border);
    }
    .photo-card img {
        width: 100%;
        height: 180px;
        object-fit: cover;
        display: block;
    }

    /* Full-image lightbox. Fixed-position backdrop over the whole
       viewport; the image is capped at 95vw/95vh so it never gets
       cropped. Click anywhere on the backdrop (or the X button)
       closes; click on the image itself is no-op (stopPropagation on
       the img element in the markup). */
    .image-lightbox {
        position: fixed;
        inset: 0;
        z-index: 3000;
        background: rgba(0, 0, 0, 0.86);
        display: flex;
        align-items: center;
        justify-content: center;
        cursor: zoom-out;
        animation: image-lightbox-fade-in 0.12s ease-out;
    }
    @keyframes image-lightbox-fade-in {
        from { opacity: 0; }
        to   { opacity: 1; }
    }
    .image-lightbox-img {
        max-width: 95vw;
        max-height: 95vh;
        box-shadow: 0 8px 40px rgba(0, 0, 0, 0.55);
        border-radius: 6px;
        cursor: default;
        display: block;
    }
    .image-lightbox-close {
        position: fixed;
        top: 0.8rem;
        right: 1rem;
        width: 2.6rem;
        height: 2.6rem;
        border-radius: 50%;
        border: 0;
        background: rgba(255, 255, 255, 0.18);
        color: #fff;
        font-size: 1.8rem;
        line-height: 1;
        cursor: pointer;
        display: flex;
        align-items: center;
        justify-content: center;
        transition: background 0.12s;
    }
    .image-lightbox-close:hover {
        background: rgba(255, 255, 255, 0.34);
    }

    /* Hero frame used to be an anchor (a). It is now a button — reset the
       browser button chrome so it visually behaves like the previous
       anchor wrapper (no border, no default background, inherit
       cursor as a clickable thumbnail). */
    button.recipe-hero-frame {
        background: transparent;
        border: 0;
        padding: 0;
        cursor: zoom-in;
        font: inherit;
        color: inherit;
    }
    .photo-trash-btn {
        position: absolute; top: 0.4rem; right: 0.4rem;
        background: rgba(255,255,255,0.85); border: none; cursor: pointer;
        color: #ccc; padding: 5px; border-radius: 5px;
        transition: color 0.15s;
    }
    .photo-trash-btn:hover { color: #b94737; }
    .photo-delete-confirm {
        position: absolute; top: 0.4rem; right: 0.4rem;
        display: flex; align-items: center; gap: 0.3rem;
        background: #fff8f7; border: 1px solid #f2c3bc; border-radius: 5px;
        padding: 0.2rem 0.4rem; font-size: 0.75rem;
    }
    .photo-delete-confirm span { color: #7a2720; }
    .photo-delete-confirm .btn-yes {
        background: #b94737; color: #fff; border: none; border-radius: 3px;
        padding: 0.1rem 0.4rem; cursor: pointer; font-size: 0.7rem;
    }
    .photo-delete-confirm .btn-no {
        background: #e4e4e4; color: #333; border: none; border-radius: 3px;
        padding: 0.1rem 0.4rem; cursor: pointer; font-size: 0.7rem;
    }

    .reprocess-modal-backdrop {
        position: fixed; inset: 0; background: rgba(0,0,0,0.4);
        display: flex; align-items: center; justify-content: center;
        z-index: 1000;
    }
    .reprocess-modal {
        background: #fff; border-radius: 8px; padding: 1.25rem 1.5rem;
        max-width: 460px; width: calc(100% - 2rem);
        box-shadow: 0 10px 40px rgba(0,0,0,0.25);
    }
    .reprocess-modal h3 { margin: 0 0 0.6rem; font-size: 1.05rem; }
    .reprocess-modal p { margin: 0 0 1.1rem; color: #444; line-height: 1.45; }
    .reprocess-modal-actions {
        display: flex; gap: 0.6rem; justify-content: flex-end; flex-wrap: wrap;
    }

    /* Previously the shopping-list pill overrode cook-btn colors (tan/gold).
       The two buttons now share a common ghost-pill treatment — no override. */

    .sl-pick-list {
        list-style: none; padding: 0; margin: 0.5rem 0 1rem;
        max-height: 40vh; overflow-y: auto;
    }
    .sl-pick-list > li { margin: 0.3rem 0; }
    .sl-pick-option {
        display: block; width: 100%; text-align: left;
        background: #fef9ed; border: 1px solid #e8d9a8; border-radius: 6px;
        padding: 0.5rem 0.75rem; cursor: pointer;
        transition: border-color 0.15s;
    }
    .sl-pick-option:hover:not(:disabled) { border-color: #c99620; }
    .sl-pick-option:disabled { opacity: 0.55; cursor: default; }
    .sl-pick-option.current { background: #f3e5b8; }
    .sl-pick-name { font-weight: 600; display: block; }
    .sl-pick-meta { color: var(--muted); font-size: 0.85rem; }
    .sl-pick-current { color: #7a5e0c; font-style: italic; margin-left: 0.25rem; }

    /* Action row above the recipe body: format switch. */
    .recipe-actions {
        display: flex; gap: 0.75rem; align-items: center; flex-wrap: wrap;
        margin: 0 0 1rem 0;
    }
    /* Serves / Prep / Cook / Start-cooking all in one row. Uses flex so the
       button sits inline with the stats and the gap separates them — no
       hand-placed middots needed. */
    .recipe-meta-row {
        display: flex; flex-wrap: wrap; gap: 0 0.35rem; align-items: center;
        color: var(--muted);
    }
    /* Tightened from 0.9rem → 0.35rem so Serves / Prep / Cook /
       Start-cooking / Add-to-shopping-list all fit on one line at the
       reserved-photo-column width. The dot separator still sits at the
       right edge of each span thanks to the matching padding-right. */
    .recipe-meta-row > span { position: relative; padding-right: 0.35rem; }
    .recipe-meta-row > span:not(:last-child)::after {
        content: "·"; position: absolute; right: -0.05rem; color: var(--muted);
    }
    /* Start-cooking button label: full "Start cooking" on desktop, just
       "Start" on phones so the Prep/Cook/Start meta row stays on one line. */
    .cook-start-btn .cook-label-short { display: none; }
    @media (max-width: 600px) {
        .cook-start-btn .cook-label-full { display: none; }
        .cook-start-btn .cook-label-short { display: inline; }
    }

    /* Serves stepper — small, non-intrusive, lives inline with Prep/Cook.
       The +/- buttons are circular chips that match the overall warm
       off-white palette; the reset (↺) only appears after the user has
       actually changed something so an untouched recipe looks identical
       to before this feature landed. */
    .serves-stepper {
        display: inline-flex; align-items: center; gap: 0.25rem;
        padding-right: 0.35rem; /* matches sibling-span right-padding so the · separator still lands right */
    }
    .serves-stepper .serves-value {
        /* Override the recipe-meta-row span padding/separator treatment
           so the number sits flush between the +/- buttons. */
        position: static; padding: 0; min-width: 1.3rem; text-align: center;
        font-weight: 600; color: #333;
    }
    .serves-stepper .serves-value::after { content: none !important; }
    .serves-btn, .serves-reset {
        display: inline-flex; align-items: center; justify-content: center;
        width: 1.6rem; height: 1.6rem; border-radius: 50%;
        background: #fef9ed; border: 1px solid #e8d9a8;
        color: #7a5e0c; font-size: 1rem; line-height: 1;
        cursor: pointer; padding: 0;
        transition: border-color 0.15s, background 0.15s;
    }
    .serves-btn:hover:not(:disabled), .serves-reset:hover { border-color: #c99620; background: #f6edd0; }
    .serves-btn:disabled { opacity: 0.4; cursor: default; }
    .serves-reset { font-size: 0.9rem; }
    /* Inline asterisk next to cook-time values that may be affected by
       scaling. Orange so it stands out against the normal text, with
       help-cursor to hint that the tooltip is the payload. */
    .scale-marker {
        color: #c99620;
        font-weight: 700;
        margin-left: 0.1em;
        cursor: help;
    }
    /* Hide the live stepper on print; the static span takes over. */
    .print-only { display: none; }
    @media print {
        .no-print { display: none !important; }
        .print-only { display: inline !important; }
    }

    /* Binder-page treatment for the recipe body. Cream paper, three
       punched holes down the left edge, the classic vertical red
       margin line, and faint blue horizontal rules — reads as a sheet
       pulled from a 3-ring binder. The toolbar above .printable-recipe
       stays unstyled (it's app chrome, not part of the "page"). */
    .printable-recipe {
        position: relative;
        background-color: #fdfaf0;
        background-image:
            /* Faint horizontal rules every 28px so the page reads as
               lined paper without competing with the text. Starts ~50px
               down so it doesn't bisect the title. */
            repeating-linear-gradient(
                to bottom,
                transparent 0,
                transparent 27px,
                rgba(70, 100, 150, 0.10) 27px,
                rgba(70, 100, 150, 0.10) 28px
            ),
            /* Vertical red margin line — sits just to the right of the
               punched-hole column. */
            linear-gradient(
                to right,
                transparent 0 78px,
                rgba(190, 40, 40, 0.45) 78px 79px,
                transparent 79px
            );
        background-size: 100% auto, 100% 100%;
        background-position: 0 50px, 0 0;
        background-repeat: repeat-y, no-repeat;
        border: 1px solid #e6dcc4;
        border-radius: 4px 8px 8px 4px;
        box-shadow:
            0 2px 8px rgba(0, 0, 0, 0.08),
            0 12px 24px rgba(0, 0, 0, 0.04);
        padding: 18px 36px 50px 100px;
        margin: 0.5rem 0 1.5rem;
    }

    /* Three punched holes down the left edge. A single ::before column
       holds three radial-gradient circles — top-anchored, vertically
       centered, bottom-anchored — so the spacing stays even regardless
       of recipe length. The slightly darker rim sells "depth" — the
       page has thickness, you're looking into the hole. */
    .printable-recipe::before {
        content: "";
        position: absolute;
        left: 22px;
        top: 0;
        bottom: 0;
        width: 24px;
        background:
            radial-gradient(circle 11px at 12px 90px,
                #e0d7c0 0%, #e8e1ce 60%, #c2b89a 100%, transparent 102%),
            radial-gradient(circle 11px at 12px 50%,
                #e0d7c0 0%, #e8e1ce 60%, #c2b89a 100%, transparent 102%),
            radial-gradient(circle 11px at 12px calc(100% - 90px),
                #e0d7c0 0%, #e8e1ce 60%, #c2b89a 100%, transparent 102%);
        background-repeat: no-repeat;
        pointer-events: none;
        z-index: 1;
    }

    /* Hero thumbnail in the upper-right corner of the binder page,
       framed like the picture-frame thumbnails on the recipe cards.
       Same wood-tone outer frame + white mat + beveled shadow; just
       sized larger because the binder page is much wider than a
       sticky-note card. Anchors as a link to the photo gallery
       further down the page so a click jumps you there. */
    .printable-recipe .recipe-hero-frame {
        /* Floated (not absolute) so the title/subtitle/description text flows
           AROUND the photo — short lines beside it, full width below — instead
           of every block reserving a fixed right column (which wasted space on
           lines that sit below the photo and squeezed the meta row). Margin
           keeps text off the frame; #recipe-drop-zone clears the float so the
           ingredient/step columns stay full width. */
        float: right;
        margin: 0.25rem 0 0.5rem 1rem;
        width: 130px; height: 130px;
        box-sizing: border-box;
        /* Light-oak frame — same warm honey gradient as the card
           thumbnails but a touch thicker because the recipe-view frame
           is bigger (130px vs 56px). */
        background: linear-gradient(155deg, #e3c48f 0%, #b48a56 60%, #d2ac74 100%);
        padding: 3px 3px 4px 3px;
        border: 1px solid #8b6f3e;
        box-shadow:
            0 1px 1px rgba(255, 240, 210, 0.45) inset,
            0 -1px 1px rgba(80, 55, 25, 0.25) inset,
            0 5px 12px rgba(0, 0, 0, 0.22),
            0 1px 3px rgba(0, 0, 0, 0.16);
        text-decoration: none;
        transition: transform 0.15s ease, box-shadow 0.15s ease;
    }
    .printable-recipe .recipe-hero-thumb {
        width: 100%; height: 100%;
        object-fit: cover; display: block;
        background: #fff;
        border: 4px solid #fff;
        box-shadow: inset 0 0 0 1px #d8d0bd;
        box-sizing: border-box;
    }
    .printable-recipe .recipe-hero-frame:hover {
        transform: scale(1.06);
        box-shadow:
            0 1px 1px rgba(255, 240, 210, 0.45) inset,
            0 -1px 1px rgba(80, 55, 25, 0.25) inset,
            0 8px 18px rgba(0, 0, 0, 0.28),
            0 2px 4px rgba(0, 0, 0, 0.20);
    }
    /* Clear the floated photo so the ingredient/step columns always start on
       a full-width line below it (they must never wrap into the narrow strip
       beside the frame). Everything ABOVE the drop zone — title, subtitle,
       description, meta row, nutrition strip, tags — flows around the float. */
    .printable-recipe #recipe-drop-zone { clear: both; }
    /* Phones: shrink the frame so it doesn't dominate a narrow page. The float
       + clear handle the text flow at every width; only the size changes. */
    @media (max-width: 600px) {
        .printable-recipe .recipe-hero-frame {
            width: 84px; height: 84px;
            margin: 0.15rem 0 0.4rem 0.7rem;
            padding: 2px 2px 3px 2px;
        }
    }

    /* Print: drop the binder chrome (paper, lines, holes) so the
       printed page is clean white with the recipe content. A real
       three-ring printout would have actual punched holes. */
    @media print {
        .printable-recipe {
            background: white;
            border: none; box-shadow: none;
            padding: 0;
        }
        .printable-recipe::before { display: none; }
    }

    /* Phones: shrink the holes / margin gap so the content keeps usable
       horizontal room on a 360px viewport. */
    @media (max-width: 600px) {
        .printable-recipe {
            padding: 10px 14px 30px 50px;
            background-image:
                repeating-linear-gradient(
                    to bottom,
                    transparent 0,
                    transparent 27px,
                    rgba(70, 100, 150, 0.10) 27px,
                    rgba(70, 100, 150, 0.10) 28px
                ),
                linear-gradient(
                    to right,
                    transparent 0 36px,
                    rgba(190, 40, 40, 0.45) 36px 37px,
                    transparent 37px
                );
        }
        .printable-recipe::before {
            left: 8px; width: 18px;
            background:
                radial-gradient(circle 8px at 9px 60px,
                    #e0d7c0 0%, #e8e1ce 60%, #c2b89a 100%, transparent 102%),
                radial-gradient(circle 8px at 9px 50%,
                    #e0d7c0 0%, #e8e1ce 60%, #c2b89a 100%, transparent 102%),
                radial-gradient(circle 8px at 9px calc(100% - 60px),
                    #e0d7c0 0%, #e8e1ce 60%, #c2b89a 100%, transparent 102%);
            background-repeat: no-repeat;
        }
    }

    /* Horizontal row that holds the nutrition strip and allergen chips on a
       single line. Wraps to a second line only when the viewport genuinely
       can't fit both — on narrow mobile the allergens fall beneath the
       nutrition cleanly instead of being hard-wrapped. */
    /* Nutrition/allergens strip + the Serves/Prep/Cook row share one row
       (nutrition left) on desktop, stacking to two lines on mobile.
       flex-wrap must be WRAP, not nowrap: the cover photo is float:right, and
       a flex container avoids floats — so this box is NARROWED beside the
       photo. With nowrap the content couldn't fold into that narrowed width
       and overflowed straight under the photo. Wrapping keeps them on one line
       whenever there's room and folds instead of colliding when there isn't
       (a long meta row — Serves 80, prep, cook, several allergen chips —
       genuinely can't fit beside the photo at any sane width). */
    .recipe-head-strip {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: 0.4rem 1.2rem;
        margin: 0 0 1rem 0;
    }
    .recipe-head-strip > .recipe-metastrip,
    .recipe-head-strip > .recipe-meta-row {
        margin: 0;
        /* Shrinkable (0 1 auto + min-width:0), NOT 0 0 auto. Both children are
           themselves wrapping flex rows; pinning them to max-content stops
           their own wrap from ever engaging, so a row too wide for the space
           beside the photo overflows under it instead of folding. */
        flex: 0 1 auto;
        min-width: 0;
    }
    @media (max-width: 600px) {
        .recipe-head-strip { row-gap: 0.5rem; }
        /* 100% basis forces one per line rather than leaving it to chance. */
        .recipe-head-strip > .recipe-metastrip,
        .recipe-head-strip > .recipe-meta-row { flex: 1 0 100%; }
    }

    .recipe-metastrip {
        display: flex; flex-wrap: wrap; align-items: center;
        gap: 0.4rem 0.9rem;
        margin: 0 0 1rem 0;
    }
    .recipe-metastrip > .recipe-nutrition,
    .recipe-metastrip > .recipe-allergens {
        margin: 0;
    }

    /* Per-serving nutrition strip. Low-key styling (small, muted) so it
       reads as a reference chip, not a primary stat. */
    .recipe-nutrition {
        display: flex; flex-wrap: nowrap; align-items: baseline;
        gap: 0.15rem 0.75rem;
        color: #7a7a7a; font-size: 0.82rem;
        background: #fafaf3; border: 1px dashed #e6e0cf;
        border-radius: 6px;
        padding: 0.35rem 0.7rem;
        margin: 0 0 1rem 0; max-width: max-content;
    }
    .recipe-nutrition-flag {
        color: var(--primary); font-weight: 700;
    }
    /* Keep each cell's value + label together — never let "45" and "kcal"
       split onto two lines if the strip is a touch narrow. */
    .recipe-nutrition-cell { white-space: nowrap; }
    .recipe-nutrition-cell strong { color: #333; font-weight: 700; }
    .recipe-nutrition-per { color: #aaa; font-size: 0.75rem; }

    /* Allergen warning chips. Rendered directly under the nutrition strip
       so a user scanning a recipe sees "what's in it" next to "can I
       eat it". Amber warning treatment — not alarming red, but obvious
       enough to catch on a quick skim. */
    .recipe-allergens {
        display: flex; flex-wrap: wrap; align-items: center;
        gap: 0.4rem;
        margin: 0 0 1rem 0;
    }
    .recipe-allergens-label {
        font-size: 0.78rem; color: #8a6d1b; font-weight: 600;
        letter-spacing: 0.02em; text-transform: uppercase;
        margin-right: 0.15rem;
    }
    .recipe-allergen-chip {
        display: inline-flex; align-items: center; gap: 0.3rem;
        background: #fff8e1; border: 1px solid #f2d98a;
        color: #6b5112;
        border-radius: 999px;
        padding: 0.18rem 0.6rem 0.18rem 0.5rem;
        font-size: 0.8rem; line-height: 1.2;
        white-space: nowrap;
    }
    .recipe-allergen-icon {
        font-size: 0.95rem; line-height: 1;
    }
    /* Attention-words chip: lives in the same row as the allergen chips
       but uses a redder fill so the user's personal flagged terms stand
       out from the pre-canned allergen list. The text is rendered in
       the recipe's source language verbatim — see USER ATTENTION WORDS
       in RecipeProcessor.txt. */
    .recipe-attention-chip {
        display: inline-flex; align-items: center;
        background: #fde8e6; border: 1px solid #e8a59c;
        color: #7a2720;
        border-radius: 999px;
        padding: 0.18rem 0.7rem;
        font-size: 0.8rem; line-height: 1.2;
        font-weight: 600;
        white-space: nowrap;
    }
    /* Print: keep the chips but drop the fill so they don't eat toner. */
    @media print {
        .recipe-allergen-chip,
        .recipe-attention-chip {
            background: transparent;
            border-color: #999;
            color: #333;
        }
    }
    /* Ghost pill: white with a green border, fills green on hover. Used for
       Start cooking and Add to shopping list, matching the split-section
       button's treatment. */
    .cook-btn {
        display: inline-flex; align-items: center; gap: 0.45rem;
        background: transparent; color: var(--primary);
        border: 1px solid var(--primary);
        padding: 0.4rem 0.9rem; border-radius: 999px;
        font-size: 0.9rem; cursor: pointer;
        transition: background 0.12s, color 0.12s;
    }
    .cook-btn:hover { background: var(--primary); color: #fff; }
    .cook-btn svg { flex-shrink: 0; }
    .cook-btn-inline {
        padding: 0.25rem 0.55rem; font-size: 0.82rem; line-height: 1;
        margin-left: 0.1rem;
        gap: 0.3rem;
    }

    /* "Save as separate recipe" — subtler than the green cook button; sits
       on the same section meta row and clones the section into its own flat
       recipe on click. */
    .split-section-btn {
        display: inline-flex; align-items: center; gap: 0.35rem;
        background: transparent; color: var(--primary);
        border: 1px solid var(--primary);
        border-radius: 999px;
        padding: 0.2rem 0.7rem; font-size: 0.8rem; line-height: 1;
        cursor: pointer;
        transition: background 0.12s, color 0.12s;
    }
    .split-section-btn:hover:not(:disabled) {
        background: var(--primary); color: #fff;
    }
    .split-section-btn:disabled { opacity: 0.5; cursor: default; }
    .split-section-btn svg { flex-shrink: 0; }

    /* Recipe-level "Split into N recipes" chip (IWA-29). Lives inline
       at the start of the .recipe-title-editable flex row so it sits
       on the same baseline as the recipe name — the IWA-29 spec calls
       for the chip to appear before the title, not stacked above it.
       Same primary-outline pill language as the old per-section
       buttons so the affordance reads familiar. align-self:center
       overrides the parent's align-items:baseline (a button has no
       text baseline; center looks correct next to the input). */
    .split-all-btn {
        display: inline-flex; align-items: center; gap: 0.4rem;
        align-self: center;
        background: transparent; color: var(--primary);
        border: 1px solid var(--primary);
        border-radius: 999px;
        padding: 0.3rem 0.85rem; font-size: 0.85rem; line-height: 1;
        cursor: pointer;
        transition: background 0.12s, color 0.12s;
        flex-shrink: 0;
    }
    .split-all-btn:hover:not(:disabled) {
        background: var(--primary); color: #fff;
    }
    .split-all-btn:disabled { opacity: 0.5; cursor: default; }
    .split-all-btn svg { flex-shrink: 0; }

    /* Cooking overlay: full-screen dimmed focus view. */
    .cook-overlay {
        position: fixed; inset: 0; z-index: 10000;
        background: rgba(10, 10, 10, 0.96); color: #f6f6f6;
        display: flex; flex-direction: column;
        padding: 1.5rem;
        outline: none;
    }
    .cook-top {
        display: flex; justify-content: space-between; align-items: center;
        font-size: 1rem; color: #ccc;
    }
    .cook-counter { font-variant-numeric: tabular-nums; }
    .cook-section { margin-left: 0.4rem; color: #9ac; }
    .cook-close {
        background: transparent; border: none; color: #ccc;
        font-size: 2.4rem; line-height: 1; cursor: pointer; padding: 0 0.4rem;
    }
    .cook-close:hover { color: #fff; }
    .cook-end-top {
        display: inline-flex; align-items: center; gap: 0.45rem;
        padding: 0.35rem 0.8rem; font-size: 0.9rem;
    }
    .cook-end-top .cook-end-x {
        font-size: 1.25rem; line-height: 0.85; margin-left: 0.15rem;
    }

    .cook-body {
        flex: 1;
        display: flex; align-items: center; justify-content: center;
        padding: 1rem;
        max-width: 1100px; margin: 0 auto; width: 100%;
        overflow-y: auto;
        cursor: pointer;
    }
    .cook-body.cook-body-sxs {
        align-items: start;
        padding-top: 3rem;
        display: grid;
        grid-template-columns: minmax(14rem, 1fr) 2fr;
        gap: 2rem;
    }
    .cook-body:not(.cook-body-sxs) { flex-direction: column; gap: 1.5rem; }
    .cook-step-text {
        /* Inter for cooking steps. Sized to be readable from a step or
           two back from the screen — a cook glances over from the
           cutting board with messy hands, no time to lean in. The
           clamp's lower bound keeps it usable on a phone, the upper
           bound stops it from filling the whole screen on a 27" iMac. */
        font-family: "Inter", "Atkinson Hyperlegible", system-ui, -apple-system, sans-serif;
        font-size: clamp(2rem, 5.5vw, 3.6rem);
        line-height: 1.35;
        letter-spacing: -0.005em;
        text-align: left;
    }
    .cook-ingredients {
        list-style: disc; padding-left: 1.3rem;
        /* Bumped to roughly track the cook-step-text upgrade — the
           ingredients list sits next to a much larger step text now,
           and the previous 1-1.3rem made it look incidental. */
        font-size: clamp(1.2rem, 2.6vw, 1.7rem);
        color: #e0e0e0;
        line-height: 1.5;
    }
    .cook-ingredients li { margin: 0.25rem 0; }

    /* Live voice-input line under the step text. Italic so it reads as
       "transcript / system feedback" rather than recipe content. The
       status pill swaps colour by .cook-heard-status[class] so the cook
       can tell at a glance whether their command was accepted, not yet
       recognised, or still being heard (interim). */
    .cook-heard {
        margin-top: 1rem;
        font-style: italic;
        font-size: clamp(0.95rem, 1.8vw, 1.2rem);
        color: #c0c0c0;
        display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem;
    }
    .cook-heard-quote { color: #d8d8d8; }
    .cook-heard-status {
        font-style: normal; font-weight: 600;
        padding: 0.1rem 0.55rem;
        border-radius: 999px;
        font-size: 0.85em;
        white-space: nowrap;
    }
    .cook-heard-status.accepted { background: rgba(127, 162, 71, 0.25); color: #b9d488; }
    .cook-heard-status.unknown  { background: rgba(255, 107, 107, 0.20); color: #ff9b9b; }
    .cook-heard-status.interim  { background: rgba(255, 211, 77, 0.18); color: #ffd34d; }

    /* Speaker button — sits at the top centre of the active step row.
       Pulled out of the normal flex stacking with align-self so it always
       hugs the top of the body regardless of how tall the step text is.
       The button itself is a transparent disc with a soft glow when
       speaking; the SVG inside swaps from grey to white on hover for
       legibility. */
    .cook-speak {
        position: absolute;
        top: 0.6rem;
        left: 50%;
        transform: translateX(-50%);
        background: transparent;
        border: 0;
        padding: 0.4rem;
        border-radius: 50%;
        color: #aaa;
        cursor: pointer;
        z-index: 5;
        transition: color 120ms ease, background-color 120ms ease, transform 120ms ease;
    }
    /* Make the SVG and its paths transparent to pointer events so a click
       always hits the button, not an inner path — without this the click
       target becomes the path, which Blazor onclick on the button doesn't
       always catch reliably across browsers. */
    .cook-speak .cook-speak-svg,
    .cook-speak .cook-speak-svg * { pointer-events: none; }

    /* Voice-listen mic toggle — sits to the LEFT of the speaker. Same
       absolute-positioning language but offset to the left so the two
       buttons sit as a hands-free pair. When listening, a red dot at
       the bottom-right and a soft red halo pulse make it obvious that
       the mic is hot. */
    .cook-listen {
        position: absolute;
        top: 0.6rem;
        /* Sits well to the left of the centre so the speaker (centred at
           50%) and the mic read as two separate controls rather than one
           huddled pair. */
        left: calc(50% - 96px);
        background: transparent;
        border: 0;
        padding: 0.4rem;
        border-radius: 50%;
        color: #aaa;
        cursor: pointer;
        z-index: 5;
        transition: color 120ms ease, background-color 120ms ease, transform 120ms ease;
    }
    .cook-listen:hover { color: #fff; background-color: rgba(255,255,255,0.06); }
    .cook-listen:active { transform: scale(0.94); }
    .cook-listen.is-listening {
        color: #ff6b6b;
        background-color: rgba(255, 107, 107, 0.10);
        box-shadow: 0 0 0 0 rgba(255, 107, 107, 0.55);
        animation: cookListenPulse 1.2s ease-in-out infinite;
    }
    .cook-listen .cook-listen-svg,
    .cook-listen .cook-listen-svg * { pointer-events: none; }

    /* Voice-locale chip — sits just to the right of the speaker icon so
       the cook can see which language the recognizer is set to. Useful
       when SourceLanguage was misdetected at parse time and "next" is
       being matched against, say, German vocab instead of English. */
    .cook-voice-lang {
        position: absolute;
        top: 0.85rem;
        left: calc(50% + 36px);
        padding: 0.18rem 0.55rem;
        font-size: 0.78rem;
        font-weight: 600;
        letter-spacing: 0.02em;
        color: #d8d8d8;
        background: rgba(255, 255, 255, 0.08);
        border: 1px solid rgba(255, 255, 255, 0.18);
        border-radius: 999px;
        pointer-events: none;
        white-space: nowrap;
        z-index: 5;
    }
    @keyframes cookListenPulse {
        0%   { box-shadow: 0 0 0 0   rgba(255, 107, 107, 0.55); }
        70%  { box-shadow: 0 0 0 14px rgba(255, 107, 107, 0); }
        100% { box-shadow: 0 0 0 0   rgba(255, 107, 107, 0); }
    }
    @media (prefers-reduced-motion: reduce) {
        .cook-listen.is-listening { animation: none; }
    }
    .cook-speak:hover { color: #fff; background-color: rgba(255,255,255,0.06); }
    .cook-speak:active { transform: translateX(-50%) scale(0.94); }
    .cook-speak.is-speaking {
        color: #ffd34d;
        background-color: rgba(255, 211, 77, 0.10);
        box-shadow: 0 0 0 0 rgba(255, 211, 77, 0.45);
        animation: cookSpeakPulse 1.4s ease-in-out infinite;
    }
    /* Cone-of-sound waves — when idle they're static. When speaking each
       wave rides its own delayed opacity-pulse so they feel like real
       sound waves bouncing outward. */
    .cook-speak .cook-speak-wave { opacity: 1; }
    .cook-speak.is-speaking .cook-speak-wave {
        animation: cookSpeakWave 1.2s ease-in-out infinite;
    }
    .cook-speak.is-speaking .cook-speak-wave.w1 { animation-delay: 0s; }
    .cook-speak.is-speaking .cook-speak-wave.w2 { animation-delay: 0.18s; }
    .cook-speak.is-speaking .cook-speak-wave.w3 { animation-delay: 0.36s; }
    @keyframes cookSpeakPulse {
        0%   { box-shadow: 0 0 0 0   rgba(255, 211, 77, 0.45); }
        70%  { box-shadow: 0 0 0 14px rgba(255, 211, 77, 0); }
        100% { box-shadow: 0 0 0 0   rgba(255, 211, 77, 0); }
    }
    @keyframes cookSpeakWave {
        0%   { opacity: 0.2; transform: scale(0.92); transform-origin: 11px 12px; }
        50%  { opacity: 1;   transform: scale(1.05); transform-origin: 11px 12px; }
        100% { opacity: 0.2; transform: scale(0.92); transform-origin: 11px 12px; }
    }
    @media (prefers-reduced-motion: reduce) {
        .cook-speak.is-speaking { animation: none; }
        .cook-speak.is-speaking .cook-speak-wave { animation: none; opacity: 1; }
    }
    /* The cook-body needs to be the positioned ancestor so the absolute
       speaker button measures from it — already a flex container, just
       add position:relative. */
    .cook-body { position: relative; }
    /* Keep the step text from sliding under the speaker button on the
       narrow stacked layout by reserving headroom. */
    .cook-body:not(.cook-body-sxs) { padding-top: 3.2rem; }

    .cook-nav {
        display: flex; justify-content: space-between; align-items: center;
        gap: 1rem; padding-top: 0.8rem;
    }
    .cook-arrow {
        background: rgba(255,255,255,0.08); color: #fff; border: 1px solid rgba(255,255,255,0.18);
        width: 4rem; height: 4rem; border-radius: 50%;
        font-size: 2.2rem; line-height: 1; cursor: pointer;
    }
    .cook-arrow:hover:not(:disabled) { background: rgba(255,255,255,0.18); }
    .cook-arrow:disabled { opacity: 0.25; cursor: default; }
    .cook-end {
        background: transparent; color: #ccc;
        border: 1px solid rgba(255,255,255,0.25);
        padding: 0.5rem 1.1rem; border-radius: 6px;
        font-size: 0.9rem; cursor: pointer;
    }
    .cook-end:hover { color: #fff; border-color: #fff; }

    /* Step progress strip at the very bottom. One dot per step; clickable
       to jump. Past steps are filled muted, current step is filled primary
       and pulses, future steps are outlined only. Lines between dots
       visualise completion. */
    .cook-progress {
        display: flex; align-items: center; justify-content: center;
        gap: 0; padding: 0.6rem 0 0.2rem;
        flex-wrap: wrap;
    }
    .cook-dot {
        width: 0.7rem; height: 0.7rem; border-radius: 50%;
        border: 1.5px solid rgba(255,255,255,0.35);
        background: transparent;
        padding: 0; margin: 0.25rem;
        cursor: pointer;
        transition: background 0.15s, border-color 0.15s, transform 0.15s;
    }
    .cook-dot:hover { border-color: #fff; transform: scale(1.15); }
    .cook-dot.done {
        background: rgba(255,255,255,0.55);
        border-color: rgba(255,255,255,0.55);
    }
    .cook-dot.current {
        background: var(--primary, #5a9e2f);
        border-color: var(--primary, #5a9e2f);
        transform: scale(1.4);
        box-shadow: 0 0 0 3px rgba(90,158,47,0.25);
    }
    @media (max-width: 640px) {
        .cook-dot { width: 0.55rem; height: 0.55rem; margin: 0.18rem; }
    }

    @media (max-width: 640px) {
        .cook-body.cook-body-sxs {
            grid-template-columns: 1fr;
            padding-top: 1rem;
            gap: 1rem;
        }
        .cook-arrow { width: 3rem; height: 3rem; font-size: 1.6rem; }
    }

    /* Three-way layout switch shown above the recipe content. The active
       button gets a primary-coloured fill; choice persists to localStorage. */
    .format-switch {
        display: inline-flex; gap: 0;
        border: 1px solid var(--border); border-radius: 6px; overflow: hidden;
    }
    .format-switch button {
        background: #fff; border: none; border-right: 1px solid var(--border);
        padding: 0.35rem 0.8rem; cursor: pointer; font-size: 0.85rem;
        color: var(--muted); line-height: 1;
    }
    .format-switch button:last-child { border-right: none; }
    .format-switch button:hover:not(.active) { background: #f5f5f5; color: var(--text); }
    .format-switch button.active {
        background: var(--primary); color: #fff;
    }

    /* Mise-en-place layouts for RecipeDetail. Toggle above the content
       picks one of three modes; the choice is read per-render in
       RenderIngredientsAndSteps(). */
    .mep-steps { padding-left: 0; list-style-position: outside; }
    .mep-steps > li {
        margin: 1rem 0; padding: 0.8rem 1rem; border-left: 3px solid var(--border);
        background: #fdfbf6;
    }
    .mep-steps.steps-stacked > li { display: block; }
    .mep-steps.steps-stacked .mep-ingredients {
        margin: 0 0 0.6rem 0; padding-left: 1.2rem;
        color: #555; font-size: 0.9rem;
    }
    /* Inter is scoped tightly to the cooking-relevant content blocks:
       the Standard / Stacked / Side-by-side step lists, the recipe
       ingredient list, and the full-screen Start-cooking overlay's
       step + ingredient text. Title, meta row, allergens, tags, and
       all other page chrome stay on the system font (set on body in
       app.css) — the previous broader rule bled Inter into those
       areas, which the user didn't want. */
    .printable-recipe .mep-steps,
    .printable-recipe .mep-steps li,
    .printable-recipe .mep-step-text,
    .printable-recipe .mep-ingredients,
    .printable-recipe .mep-ingredients li,
    .printable-recipe .recipe-ingredients,
    .printable-recipe .recipe-ingredients li,
    .printable-recipe ol.recipe-step-list,
    .printable-recipe ol.recipe-step-list li {
        /* Lora is a humanist serif optimised for body reading on screen;
           clearly distinct from the surrounding system sans so the recipe
           content has a visible "this is the part you're cooking from"
           character. font-size bumped a touch because serifs at small
           sizes lose detail on low-DPI screens.

           NOTE: .cook-step-text / .cook-ingredients used to be in this
           selector but were taken out — the cooking overlay needs its own
           much-larger Inter sizing (clamp(2rem, 5.5vw, 3.6rem)) and pulling
           it back into Lora at 1.04em was overriding that, leaving the
           cook-mode text the same size as on-page step text. */
        font-family: var(--recipe-font, "Lora", Georgia, "Times New Roman", serif);
        /* --rf-body-* are the "Recipe text" row of the formatting settings;
           the font itself stays on --recipe-font (a profile setting). */
        font-size: var(--rf-body-size, 1.04em);
        color: var(--rf-body-fg, inherit);
        background: var(--rf-body-bg, transparent);
        font-weight: var(--rf-body-weight, inherit);
        font-style: var(--rf-body-style, normal);
        text-decoration: var(--rf-body-deco, none);
        line-height: 1.6;
    }
    .mep-steps.steps-stacked .mep-step-text { line-height: 1.5; }

    /* Cook-along step checkoff — tap a step to strike it through. The cursor +
       tap-highlight suppression make it read as an interactive checklist item
       without adding a checkbox. Strike is a transient cooking marker, so it's
       reset for print (a struck step shouldn't look crossed-out on paper). */
    .step-checkable {
        cursor: pointer;
        -webkit-tap-highlight-color: transparent;
        /* manipulation kills the 300ms double-tap-zoom delay so a tap registers
           immediately; user-select:none stops a tap being read as the start of a
           text selection — both were making taps feel unreliable on iOS. */
        touch-action: manipulation;
        -webkit-user-select: none;
        user-select: none;
    }
    .step-checkable.step-done {
        text-decoration: line-through;
        text-decoration-color: var(--muted);
        opacity: 0.5;
        transition: opacity 0.12s ease;
    }
    @media print {
        .step-checkable.step-done { text-decoration: none; opacity: 1; }
    }

    .mep-steps.steps-sxs > li {
        display: grid; grid-template-columns: minmax(12rem, 1fr) 2fr; gap: 1.2rem;
        align-items: start;
    }
    .mep-steps.steps-sxs .mep-ingredients {
        margin: 0; padding-left: 1.2rem;
        color: #555; font-size: 0.9rem;
    }
    .mep-steps.steps-sxs .mep-step-text { line-height: 1.55; }

    /* Inline edit-quantity. Hover an ingredient line for ~1.5 s and the
       leading quantity swaps in-place for a text input — type a new
       amount, hit Enter or click away, and the whole ingredient list
       (plus the step text, since it scales the same way) re-renders
       at the new implied scale. .ing-display carries the verbatim
       formatted line so the resting state is identical to the
       non-editable rendering; .ing-edit is the structured "<input>
       unit name" pair that takes over once the row has the
       .show-edit class. The dwell-delay (1.5 s in, 0 s out) is owned
       by /js/ing-edit-delay.js — the swap below is purely a CSS
       reaction to the class it adds. Pure CSS :hover here was too
       eager: brushing the cursor down the list flashed inputs in/out
       on every row the pointer touched. Note: :focus-within is
       intentionally NOT used — the cook asked for the input to
       revert to text the moment the mouse leaves the row, even if
       focus is still inside (e.g. after a spinner click). The
       @onchange handler still fires on blur, so the typed value
       commits regardless. Print + PDF hide the input entirely so
       exports look the same as the static rendering. */
    .printable-recipe .recipe-ingredients > li.ing-row,
    .printable-recipe .mep-ingredients > li.ing-row {
        position: relative;
        cursor: pointer; /* click the row to strike it, the chip to edit it */
        /* manipulation kills the 300ms tap delay iOS holds open waiting for a
           double-tap-to-zoom, so a tap registers immediately. */
        touch-action: manipulation;
        /* Suppress iOS's "Copy / Look Up" long-press callout AND the
           default touch text-selection on the whole row, so a tap to
           open the quantity editor doesn't also select the line's text.
           The qty input inside .ing-edit is its own element and stays
           selectable — these properties don't propagate to nested
           inputs. */
        -webkit-touch-callout: none;
        -webkit-user-select: none;
        user-select: none;
    }

    /* Numeric quantity token — a round CHIP, 1.5× the line's font. A single
       click / tap on it opens the edit input (see /js/ing-edit-delay.js);
       clicking anywhere outside closes it. line-height 1.1 keeps the bigger
       digits from blowing the row height out too far. */
    /* The --rf-* custom properties are the per-user recipe-formatting
       overrides (Account → UI preferences → Recipe formatting). They're set
       on the .printable-recipe root's style attribute from
       AppUser.RecipeFormatJson; the var() FALLBACKS below are the product
       defaults, so a user with no custom formatting renders exactly this
       stylesheet. See Data/RecipeFormatting.cs. */
    .printable-recipe .ing-num {
        display: inline-block;
        /* rem, not em, so every chip is the SAME size regardless of where it
           sits — a normal amount, a range, a recalculated value, and the
           source chip nested inside a converted line's muted 0.9em parens all
           render identically. (.printable-recipe inherits the root font, so
           1.5rem == the 1.5em these used to be, and it still tracks the user's
           font-scale.) The edit input below matches this exactly. */
        font-size: var(--rf-qty-size, 1.5rem);
        color: var(--rf-qty-fg, inherit);
        font-family: var(--rf-qty-family, inherit);
        font-weight: var(--rf-qty-weight, inherit);
        font-style: var(--rf-qty-style, normal);
        text-decoration: var(--rf-qty-deco, none);
        line-height: 1.1;
        padding: 0.02em 0.5em;
        margin: 0 0.1em;
        border: 1px solid color-mix(in oklab, var(--primary, #5a9e2f) 35%, transparent);
        border-radius: 999px;
        background: var(--rf-qty-bg, color-mix(in oklab, var(--primary, #5a9e2f) 8%, transparent));
        vertical-align: baseline;
        cursor: pointer;
        -webkit-tap-highlight-color: transparent;
        transition: color .12s, border-color .12s, background-color .12s;
    }
    /* Ingredient NAMES — the **bold** spans the formatter marks in ingredient
       lines (incl. converted-line results and their source text). Scoped to
       the ingredient lists so step operations (also <strong>) are untouched. */
    .printable-recipe .recipe-ingredients strong,
    .printable-recipe .mep-ingredients strong {
        font-size: var(--rf-name-size, 1em);
        color: var(--rf-name-fg, inherit);
        background: var(--rf-name-bg, transparent);
        font-family: var(--rf-name-family, inherit);
        font-weight: var(--rf-name-weight, 700);
        font-style: var(--rf-name-style, normal);
        text-decoration: var(--rf-name-deco, none);
    }
    /* Cooking TIMES and TEMPERATURES — the ***bold-italic*** spans in steps.
       BoldMarkup classifies each span by its unit and emits rf-time / rf-temp
       so the two are styleable independently even though the stored text
       marks both the same way. Italic rides on the inner <em>. */
    .printable-recipe .rf-time {
        font-size: var(--rf-time-size, 1em);
        color: var(--rf-time-fg, inherit);
        background: var(--rf-time-bg, transparent);
        font-family: var(--rf-time-family, inherit);
        font-weight: var(--rf-time-weight, 700);
        text-decoration: var(--rf-time-deco, none);
    }
    .printable-recipe .rf-time em { font-style: var(--rf-time-style, italic); }
    .printable-recipe .rf-temp {
        font-size: var(--rf-temp-size, 1em);
        color: var(--rf-temp-fg, inherit);
        background: var(--rf-temp-bg, transparent);
        font-family: var(--rf-temp-family, inherit);
        font-weight: var(--rf-temp-weight, 700);
        text-decoration: var(--rf-temp-deco, none);
    }
    .printable-recipe .rf-temp em { font-style: var(--rf-temp-style, italic); }
    @media (hover: hover) {
        .printable-recipe .ing-row:hover .ing-num {
            color: var(--primary, #5a9e2f);
            border-color: var(--primary, #5a9e2f);
            background-color: color-mix(in oklab, var(--primary, #5a9e2f) 15%, transparent);
        }
    }
    /* Ingredient lines strike out on click / tap — same local "got it" marker
       as recipe steps (.step-done in step-check.js). Class is JS-toggled;
       user-select:none + touch-action make the tap register reliably instead
       of starting a text selection (steps do the same). */
    .printable-recipe .recipe-ingredients > li,
    .printable-recipe .mep-ingredients > li {
        -webkit-tap-highlight-color: transparent;
        touch-action: manipulation;
        -webkit-user-select: none;
        user-select: none;
    }
    /* A touch more air between ingredient lines than line-height alone gives.
       Bottom-only so the gap between two lines is exactly this much (adjacent
       margins don't stack) and the list doesn't gain a leading indent. Scoped
       to the main list — the per-step .mep-ingredients sublist stays compact. */
    .printable-recipe .recipe-ingredients > li {
        margin-bottom: 2px;
    }
    .printable-recipe .recipe-ingredients > li.ing-struck,
    .printable-recipe .mep-ingredients > li.ing-struck {
        text-decoration: line-through;
        text-decoration-color: var(--muted);
        opacity: 0.5;
        transition: opacity 0.12s ease;
    }
    @media print {
        /* Paper gets the plain amount (no pill) and no strike-outs — those are
           transient cook-along marks, same treatment as .step-done. */
        .printable-recipe .ing-num {
            font-size: 1em;
            padding: 0;
            margin: 0;
            border: none;
            background: none;
        }
        .printable-recipe .recipe-ingredients > li.ing-struck,
        .printable-recipe .mep-ingredients > li.ing-struck {
            text-decoration: none;
            opacity: 1;
        }
    }
    .printable-recipe .recipe-ingredients > li.ing-row .ing-edit,
    .printable-recipe .mep-ingredients > li.ing-row .ing-edit {
        display: none;
    }
    .printable-recipe .recipe-ingredients > li.ing-row.show-edit .ing-display,
    .printable-recipe .mep-ingredients > li.ing-row.show-edit .ing-display {
        display: none;
    }
    .printable-recipe .recipe-ingredients > li.ing-row.show-edit .ing-edit,
    .printable-recipe .mep-ingredients > li.ing-row.show-edit .ing-edit {
        display: inline;
    }
    .printable-recipe .ing-qty-input {
        width: 5.5rem;
        padding: 0.1rem 0.3rem 0.1rem 0.5rem;
        font: inherit;
        font-family: var(--recipe-font, "Lora", Georgia, "Times New Roman", serif);
        border: 1px solid var(--border, #d4d0c4);
        border-radius: 4px;
        text-align: right;
        background: #fff;
        color: inherit;
    }
    /* Chromium puts the up/down spinner at the right edge of the content
       box, immediately next to the digits. Pad the pseudo-element from
       the left to give the requested gap between the number and the
       arrows. opacity:1 keeps the spinner visible even before the input
       is focused (it's only there on hover anyway). */
    .printable-recipe .ing-qty-input::-webkit-inner-spin-button,
    .printable-recipe .ing-qty-input::-webkit-outer-spin-button {
        margin-left: 0.5rem;
        opacity: 1;
    }
    .printable-recipe .ing-qty-input:focus {
        outline: 2px solid var(--primary, #0a8a3f);
        outline-offset: 1px;
        border-color: transparent;
    }
    /* The edit input matches the chip exactly (same 1.5rem), so opening the
       editor doesn't resize the amount — only the pill turns into a box. The
       unit/name text beside it (.ing-rest) stays at the normal line size.
       Width in em tracks the input's own font so longer values don't clip;
       vertical-align middle keeps it centred against the 1em text. */
    .printable-recipe .ing-row.show-edit .ing-qty-input {
        /* Tracks the chip's size var so opening the editor never resizes the
           amount, custom formatting included. */
        font-size: var(--rf-qty-size, 1.5rem);
        width: 5em;
        vertical-align: middle;
    }
    .printable-recipe .ing-rest { margin-left: 0.25rem; }
    /* A curing-salt line rewritten to the viewer's preferred salt type. The
       little ⇄ mark (with a "converted from …" tooltip) flags that the amount
       was derived, not taken verbatim from the recipe. */
    .printable-recipe .salt-conv-mark {
        margin-left: 0.35rem;
        color: var(--primary, #5a9e2f);
        font-size: 0.85em;
        cursor: help;
    }
    /* The recipe's original wording, shown in parens right after a converted
       amount so the reader sees both "what to use" and "what it said". Muted
       so the converted value stays primary. */
    .printable-recipe .salt-conv-orig {
        margin-left: 0.35rem;
        color: var(--muted, #8a8a8a);
        font-size: 0.9em;
    }
    /* Practical metric equivalent for an awkward scaled spoon/cup amount
       ("18 ст.л." → "(≈ 270 ml)"). Muted so it reads as a helper, not the
       primary amount; dropped from print/PDF. */
    .printable-recipe .ing-practical {
        margin-left: 0.4rem;
        color: var(--muted, #8a8a8a);
        font-size: 0.85em;
        white-space: nowrap;
    }
    @media print {
        .printable-recipe .ing-edit { display: none !important; }
        .printable-recipe .ing-display { display: inline !important; }
    }

    /* "Reset quantities" chip, shown inline beside the Ingredients
       heading only when the user has adjusted (and we've saved) this
       recipe's amounts. Clicking it restores the original quantities and
       clears the saved override. Hidden in print/PDF exports. */
    .printable-recipe .reset-qty-chip {
        margin-left: 0.6rem;
        vertical-align: middle;
        font-family: inherit; font-size: 0.7rem; font-weight: 600;
        color: var(--primary, #5a9e2f);
        background: color-mix(in oklab, var(--primary, #5a9e2f) 10%, transparent);
        border: 1px solid color-mix(in oklab, var(--primary, #5a9e2f) 35%, transparent);
        border-radius: 999px;
        padding: 0.15rem 0.6rem;
        cursor: pointer;
        line-height: 1.4;
        white-space: nowrap;
        transition: background .12s, border-color .12s;
    }
    .printable-recipe .reset-qty-chip:hover {
        background: color-mix(in oklab, var(--primary, #5a9e2f) 18%, transparent);
        border-color: var(--primary, #5a9e2f);
    }

    @media (max-width: 640px) {
        .mep-steps.steps-sxs > li {
            grid-template-columns: 1fr;
            gap: 0.4rem;
        }
    }

    /* Share-time copyright attestation modal — shown the first time a
       user clicks "Copy public link" on any recipe. Plain CSS so it
       works during SSR pre-render and in the no-JS fallback. */
    .share-attest-backdrop {
        position: fixed; inset: 0; background: rgba(0,0,0,0.45);
        display: flex; align-items: center; justify-content: center;
        z-index: 9000; padding: 1rem;
    }
    .share-attest-modal {
        background: #fff; border-radius: 12px; padding: 1.4rem 1.5rem;
        max-width: 480px; width: 100%; box-shadow: 0 12px 40px rgba(0,0,0,0.18);
    }
    .share-attest-modal h2 {
        margin: 0 0 0.6rem; font-size: 1.15rem; color: var(--primary);
    }
    .share-attest-modal p { margin: 0.4rem 0; line-height: 1.5; }
    .share-attest-checkrow {
        display: flex; align-items: flex-start; gap: 0.6rem;
        background: #f6f6f3; border-radius: 8px; padding: 0.7rem 0.9rem;
        margin: 1rem 0;
    }
    .share-attest-checkrow input[type="checkbox"] {
        margin-top: 0.2rem; flex-shrink: 0;
    }
    .share-attest-checkrow label {
        font-size: 0.92rem; line-height: 1.45; cursor: pointer;
    }
    .share-attest-actions {
        display: flex; justify-content: flex-end; gap: 0.6rem; margin-top: 1rem;
    }
    .share-attest-actions .btn-cancel,
    .share-attest-actions .btn-accept {
        padding: 0.5rem 1.1rem; border-radius: 6px; font-weight: 500;
        border: none; cursor: pointer;
    }
    .share-attest-actions .btn-cancel {
        background: #ececec; color: #333;
    }
    .share-attest-actions .btn-accept {
        background: var(--primary); color: #fff;
    }
    .share-attest-actions .btn-accept:disabled {
        opacity: 0.4; cursor: not-allowed;
    }
