/* ============================================================
   css/puzzle.css — thewordfind.com puzzle display
   Adapted from Us.FunAndReverence.com's puzzle_display.php.
   Same layout, same mechanics, rebranded palette/fonts.
   Uses the --gold/--cream/--ink variables already defined in
   css/style.css (Section 5 of the project plan) — this file
   assumes style.css is loaded first, per puzzle.php's <link> order.
   ============================================================ */

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

/* Scoped to html.puzzle-page (set on puzzle.php's <html> tag only) —
   NOT a bare html/body selector. puzzle.css is also loaded on index.php
   for its leaf-level classes (.cell, .q-item, .win-btn, etc.), and an
   unscoped html/body rule here was overriding index.php's entire page
   layout (forced sections into a flex row instead of stacking). */
html.puzzle-page, html.puzzle-page body {
    height: 100%;
    height: 100dvh;
    overflow: hidden;
    font-family: 'DM Sans', sans-serif;
    font-weight: 300;
    background-color: var(--ink);
    color: var(--cream);
    -webkit-tap-highlight-color: transparent;
    user-select: none;
    -webkit-user-select: none;
    margin: 0;
    padding: 0;
}

/* ── Content wrapper ── */
.page-wrap {
    position: relative;
    z-index: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    height: 100%;
    height: 100dvh;
    overflow-y: auto;
    overflow-x: hidden;
    -webkit-overflow-scrolling: touch;
    padding: 0 0 80px;
}

/* ── Sticky header ── */
.puzzle-header {
    width: 100%;
    background: rgba(28,26,22,0.7);
    backdrop-filter: blur(8px);
    border-bottom: 1px solid rgba(201,168,76,0.3);
    text-align: center;
    padding: 12px 16px 10px;
    position: sticky;
    top: 0;
    z-index: 100;
}
.header-eyebrow {
    font-size: .62rem;
    letter-spacing: .2em;
    text-transform: uppercase;
    color: var(--gold);
    margin-bottom: 3px;
}
.header-title {
    font-family: 'Playfair Display', serif;
    font-weight: 400;
    font-size: clamp(1.1rem, 4vw, 1.6rem);
    color: var(--cream);
    line-height: 1.2;
}
.header-number {
    font-size: .65rem;
    color: rgba(201,168,76,0.6);
    letter-spacing: .08em;
    margin-top: 2px;
}

/* Puzzle-view logo sizing (chat 2.8, moved here chat 2.9 continued —
   completing a move that was left half-done: only .puzzle-view-label/
   -mode/-path made it over here last round, this was missed, which is
   exactly why puzzle.php's logo still rendered at the base 1.15rem
   size with no spacing below it while category.php's correctly used
   this smaller, spaced version. Both pages need this from a file they
   both actually load. */
.puzzle-view-logo {
    justify-content: center;
    margin-bottom: .85rem;
}
.puzzle-view-logo .nav-brand-text {
    font-size: 1.05rem;
}

/* Category/subcategory eyebrow (chat 2.25 — replaces the earlier
   mode-pill + stacked-path treatment). The header now matches the
   Daily Puzzle's own header exactly: plain #puzzle-eyebrow text above
   the logo, using .header-eyebrow's shared styling directly (same
   font-size/letter-spacing/color as "DAILY CHALLENGE"), not a
   hand-copied duplicate that could drift out of sync with it.
   Daily's own eyebrow is a short fixed string and never needs
   truncation; this one holds a dynamic category+title string that
   can run long, so it keeps the same one-line/ellipsis safety net
   .puzzle-view-path used to have — just scoped to this id instead of
   a class, since only this element ever holds variable-length text.
   fitPuzzleViewPath()'s dynamic font-shrinking (puzzle.js) is retired
   along with the old markup — a plain ellipsis is enough at
   .header-eyebrow's already-small, fixed size. */
#puzzle-eyebrow {
    max-width: min(92vw, 420px);
    margin-left: auto;
    margin-right: auto;
    /* More breathing room than Daily's own 3px (.header-eyebrow's base
       rule) — deliberately scoped to this id only, not the shared
       class, so Daily's header is untouched. */
    margin-bottom: 12px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ── Progress bar ── */
.progress-wrap {
    width: 100%;
    max-width: 480px;
    padding: 8px 16px 4px;
}
.progress-bar {
    height: 5px;
    background: rgba(201,168,76,0.2);
    border-radius: 2px;
    overflow: hidden;
}
.progress-fill {
    height: 100%;
    background: rgba(111, 169, 143, 0.5);
    border-radius: 2px;
    transition: width .5s ease;
    width: 0%;
}
.progress-label {
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-size: .68rem;
    color: var(--gold-light);
    letter-spacing: .1em;
    margin-top: 4px;
}
.progress-label-right {
    display: flex;
    align-items: center;
    gap: 8px;
}
.progress-share-btn {
    background: transparent;
    border: 1px solid rgba(201,168,76,0.4);
    border-radius: 50%;
    color: var(--gold);
    width: 24px;
    height: 24px;
    line-height: 1;
    font-size: .85rem;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: background .2s, color .2s;
    padding: 0;
}
.progress-share-btn:hover { background: var(--gold); color: var(--ink); }
.progress-label-count { text-align: right; }
.progress-label-count.finished {
    cursor: pointer;
    color: var(--gold-light);
    text-decoration: underline;
    text-decoration-color: rgba(232,201,122,0.4);
    text-underline-offset: 3px;
}
/* Finished state — full-width centered pill banner instead of a plain
   underlined text link buried in the row's right-hand corner (chat
   2.9). Reads as a real call-to-action/button rather than leftover
   label text. The back-link (puzzle.php's "‹ Home") is hidden here —
   once the puzzle is solved, the win flow (Review/Categories/Share)
   already provides a way out, so it's not competing for space with the
   centered banner. Shared .progress-wrap/.progress-label classes, so
   this applies identically wherever the engine renders (puzzle.php,
   category.php, the homepage daily embed) — one fix, everywhere. */
.progress-wrap.finished .progress-label {
    justify-content: center;
}
.progress-wrap.finished .progress-label-back {
    display: none;
}
.progress-wrap.finished .progress-label-right {
    width: 100%;
    justify-content: center;
    gap: 8px;
    background: rgba(201,168,76,0.14);
    border: 1px solid rgba(201,168,76,0.4);
    border-radius: 50px;
    padding: 7px 16px;
}
.progress-wrap.finished .progress-label-count.finished {
    text-decoration: none;
    font-size: .74rem;
    letter-spacing: .08em;
}
.progress-label-back {
    color: var(--gold-light);
    cursor: pointer;
    letter-spacing: .12em;
    text-transform: uppercase;
    font-size: .74rem;
    font-weight: 500;
    padding: 5px 12px;
    border: 1px solid rgba(201,168,76,0.35);
    border-radius: 50px;
    text-decoration: none;
    -webkit-tap-highlight-color: transparent;
    transition: color .2s, border-color .2s, background .2s;
}
.progress-label-back:hover {
    color: var(--gold-light);
    border-color: var(--gold);
    background: rgba(201,168,76,0.1);
}
.progress-label-back:active { color: var(--cream); border-color: var(--gold); }

/* ── Grid ── */
.grid-wrapper {
    width: 100%;
    max-width: 480px;
    padding: 8px 10px;
    display: flex;
    justify-content: center;
}
#grid-container {
    position: relative;
    width: 100%;
    background: rgba(28,26,22,0.75);
    border: 1px solid rgba(201,168,76,0.3);
    border-radius: 10px;
    padding: 6px;
    touch-action: none;
    -webkit-touch-callout: none;
    box-shadow: 0 4px 32px rgba(0,0,0,0.5);
}
#grid {
    display: grid;
    position: relative;
    z-index: 1;
}
.grid-loading {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 5;
}
.grid-loading-badge {
    font-family: 'Playfair Display', serif;
    font-style: italic;
    font-size: .9rem;
    color: rgba(201,168,76,0.8);
    letter-spacing: .03em;
    padding: 10px 22px;
    border: 1px solid rgba(201,168,76,0.3);
    border-radius: 50px;
    background: rgba(28,26,22,0.5);
    animation: gridLoadingPulse 1.4s ease-in-out infinite;
}
@keyframes gridLoadingPulse {
    0%, 100% { opacity: .55; }
    50%      { opacity: 1; }
}
.cell {
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: 'DM Sans', sans-serif;
    font-weight: 400;
    cursor: pointer;
    position: relative;
    z-index: 2;
    color: rgba(245,237,216,1);
    border: 1px solid rgba(201,168,76,0.25);
    background: transparent;
    transition: color .15s;
    opacity: 0;
    transform: scale(0.6);
    animation: cellReveal 0.35s ease forwards;
}
@keyframes cellReveal {
    from { opacity: 0; transform: scale(0.6); }
    to   { opacity: 1; transform: scale(1); }
}
.cell.pulse {
    animation: cellPulse .25s ease forwards;
    opacity: 1;
    transform: scale(1);
}
@keyframes cellPulse {
    0%,100% { transform: scale(1); opacity: 1; }
    50%      { transform: scale(1.22); opacity: 1; }
}
#overlay {
    position: absolute;
    top: 6px;
    left: 6px;
    pointer-events: none;
    z-index: 10;
    overflow: visible;
}

/* ── Direction hint ── */
.direction-hint {
    font-size: .65rem;
    color: rgba(245,237,216,0.8);
    text-align: center;
    padding: 2px 16px 6px;
    letter-spacing: .04em;
}
/* Replaces four Unicode arrow characters (↔ ↕ ↘ ↗) that rendered
   inconsistently across platforms — iOS defaulted them to its
   colorful emoji-style glyphs, Android to a thin, low-contrast plain-
   text glyph, since different system fonts pick different default
   presentations for the same symbol codepoint. Real SVG guarantees
   the identical shape everywhere regardless of OS/font. stroke=
   "currentColor" (set on each <path> in puzzle.php, not here) means
   these inherit .direction-hint's own color/opacity directly — no
   color duplicated in this rule, so a future brightness change above
   reaches the icons automatically. */
.direction-hint .direction-icon {
    width: 13px;
    height: 13px;
    vertical-align: -2px;
    margin: 0 1px;
}

/* ── Questions peek bar — persistent toggle at bottom ── */
.questions-peek {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 90;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 11px 20px 14px;
    background: rgba(14,12,10,0.85);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border-top: 1px solid rgba(201,168,76,0.3);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}
.questions-peek-label {
    font-size: .62rem;
    letter-spacing: .18em;
    text-transform: uppercase;
    color: var(--gold);
    font-family: 'DM Sans', sans-serif;
    font-weight: 400;
}
.questions-peek-arrow {
    width: 14px;
    height: 14px;
    border-right: 1.5px solid var(--gold);
    border-bottom: 1.5px solid var(--gold);
    flex-shrink: 0;
    transition: transform 0.25s ease;
    transform: rotate(45deg) translate(-2px, -2px);
}
.questions-peek.showing-questions .questions-peek-arrow {
    transform: rotate(-135deg) translate(-2px, -2px);
}

/* First-visit attention nudge — see maybeShowPeekAttention() in puzzle.js.
   A soft glow pulse, not a color/motion change to the bar's own resting
   state, so it doesn't compete with the "found" toast or win celebration. */
@keyframes peekAttentionPulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(201,168,76,0); transform: translateY(0); }
    50%      { box-shadow: 0 0 22px 7px rgba(201,168,76,0.5); transform: translateY(-5px); }
}
.questions-peek.attention-pulse {
    animation: peekAttentionPulse 1.1s ease-in-out 4;
}

/* ── Questions panel ── */
.questions-panel {
    width: 100%;
    max-width: 480px;
    padding: 8px 14px 0;
}
.questions-panel-top {
    display: flex;
    justify-content: flex-end;
    margin-bottom: 14px;
}
.questions-close-x {
    background: transparent;
    border: none;
    color: rgba(245,237,216,0.55);
    font-size: 1.5rem;
    line-height: 1;
    padding: 4px 10px;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: color .2s;
    position: relative;   
    top: 3px;              
}
    
}
.questions-close-x:hover,
.questions-close-x:active { color: var(--gold); }
.q-item {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 8px 0 8px 8px;
    border-bottom: 1px solid rgba(201,168,76,0.12);
    border-left: 3px solid transparent; /* reserved on every row so a found row's colored accent doesn't shift layout */
    transition: opacity .4s, color .4s, border-left-color .4s, background .4s;
}
.q-item:last-child { border-bottom: none; }
.q-item.found {
    background: var(--found-color-bg, rgba(201,168,76,0.15));
    border-left-color: var(--found-color, var(--gold));
    border-radius: 6px;
    padding-right: 8px;
}
.q-item.found .q-num,
.q-item.found .q-text {
    opacity: 0.6;
}
.q-num {
    font-size: .68rem;
    color: var(--gold);
    flex-shrink: 0;
    margin-top: 1px;
    min-width: 18px;
    transition: opacity .4s;
}
.q-text {
    font-size: .88rem;
    color: var(--cream);
    line-height: 1.5;
    flex: 1;
    transition: opacity .4s;
}
.q-answer-reveal {
    font-size: .72rem;
    font-weight: 500;
    letter-spacing: .1em;
    color: var(--gold);
    background: rgba(201,168,76,0.15);
    border: 1px solid rgba(201,168,76,0.3);
    border-radius: 50px;
    padding: 2px 10px;
    white-space: nowrap;
    flex-shrink: 0;
    opacity: 0;
    transition: opacity .4s .2s;
}
.q-item.found .q-answer-reveal {
    opacity: 1;
    /* Per-word color palette (chat 2.8) — set via CSS custom properties
       from js/puzzle.js's assignWordColors()/foundWord(), falling back
       to plain gold if unset (e.g. Classic mode, which never marks a
       row .found through the same path — harmless either way). */
    color: var(--found-color, var(--gold));
    background: var(--found-color-bg, rgba(201,168,76,0.15));
    border-color: var(--found-color, rgba(201,168,76,0.3));
}

/* Hint — small pill button next to an unsolved trivia clue, revealing the
   answer's first letter on click. Hidden once the row is found, since
   q-answer-reveal already shows the full word at that point. */
.q-hint-btn {
    font-size: .62rem;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: rgba(201,168,76,0.7);
    background: transparent;
    border: 1px solid rgba(201,168,76,0.3);
    border-radius: 50px;
    padding: 2px 9px;
    cursor: pointer;
    flex-shrink: 0;
    font-family: 'DM Sans', sans-serif;
    transition: color .2s, border-color .2s;
    -webkit-tap-highlight-color: transparent;
}
.q-hint-btn:hover { color: var(--gold); border-color: var(--gold); }
.q-hint-badge {
    font-size: .76rem;
    font-weight: 500;
    letter-spacing: .06em;
    color: rgba(245,237,216,0.9);
    background: rgba(245,237,216,0.08);
    border: 1px solid rgba(245,237,216,0.2);
    border-radius: 50px;
    padding: 2px 9px;
    flex-shrink: 0;
    font-family: 'DM Sans', sans-serif;
    white-space: nowrap;
}
.q-item.found .q-hint-btn,
.q-item.found .q-hint-badge { display: none; }

/* Classic mode: word list items show the word itself, no clue/reveal needed */
.q-item.word-only .q-text {
    font-family: 'DM Sans', sans-serif;
    letter-spacing: .08em;
}
/* Number removed for Classic rows only, per direct request — Trivia's
   .q-num (shared, unscoped rule above) is untouched, still numbered.
   To bring the number back: delete this one rule, nothing else to
   undo. display:none also removes it from the flex layout entirely
   (no leftover gap), which is why .word-only's own left padding is
   zeroed below rather than left reserving space for a number that's
   no longer there. */
.q-item.word-only .q-num {
    display: none;
}
/* Zeroing the left padding here (Classic rows only — .q-item's own
   shared "8px 0 8px 8px" padding, unchanged for Trivia) makes the row
   symmetric left/right so the centered text below is genuinely
   centered in the row, not offset a few px right by padding still
   reserved for the now-hidden number. */
.q-item.word-only {
    padding-left: 0;
}
.q-item.word-only .q-text {
    text-align: center;
}

.puzzle-footer {
    width: 100%;
    text-align: center;
    font-size: .65rem;
    letter-spacing: .1em;
    color: rgba(245,237,216,0.3);
    padding: 20px 0 8px;
}

/* ── Toast ── */
.found-toast {
    position: fixed;
    pointer-events: none;
    z-index: 500;
    font-family: 'Playfair Display', serif;
    font-weight: 400;
    font-size: 1.1rem;
    color: var(--ink);
    background: var(--gold);
    padding: 5px 16px;
    border-radius: 50px;
    white-space: nowrap;
    transform: translate(-50%, -50%);
    animation: toastUp .95s ease forwards;
}
@keyframes toastUp {
    0%   { opacity: 1; transform: translate(-50%,-50%) scale(1); }
    60%  { opacity: 1; transform: translate(-50%,-80px) scale(1.06); }
    100% { opacity: 0; transform: translate(-50%,-110px) scale(.9); }
}

/* ── Celebration message (sparkles removed per direction) ── */
.celebration-msg {
    position: fixed;
    z-index: 1050;
    left: 50%;
    top: 50%;
    font-family: 'Playfair Display', serif;
    font-style: italic;
    font-size: clamp(2rem, 8vw, 3rem);
    font-weight: 400;
    color: var(--cream);
    text-align: center;
    background: rgba(14,12,10,0.92);
    border: 1px solid rgba(201,168,76,0.3);
    border-radius: 16px;
    padding: 28px 36px;
    max-width: 85vw;
    line-height: 1.5;
    pointer-events: none;
    box-shadow: 0 8px 48px rgba(0,0,0,0.5);
    animation: msgFloat 2.5s ease forwards;
}
@keyframes msgFloat {
    0%   { opacity:0; transform:translate(-50%,-40%) scale(.8); }
    20%  { opacity:1; transform:translate(-50%,-50%) scale(1.03); }
    70%  { opacity:1; transform:translate(-50%,-50%) scale(1); }
    100% { opacity:0; transform:translate(-50%,-55%) scale(.96); }
}

/* ── Win banner ── */
.win-banner {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.75);
    z-index: 999;
    align-items: center;
    justify-content: center;
}
.win-banner.show { display: flex; }
.win-card {
    background: rgba(14,12,10,0.97);
    border: 1px solid rgba(201,168,76,0.3);
    border-radius: 20px;
    padding: 36px 28px;
    text-align: center;
    max-width: 300px;
    width: 90%;
    box-shadow: 0 20px 60px rgba(0,0,0,.5);
    animation: pop .35s cubic-bezier(.34,1.56,.64,1);
}
@keyframes pop {
    from { transform:scale(.7); opacity:0; }
    to   { transform:scale(1); opacity:1; }
}
.win-checkmark {
    width: 44px;
    height: 44px;
    border-radius: 50%;
    border: 1.5px solid var(--gold);
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 0 auto 14px;
    color: var(--gold);
    font-size: 1.1rem;
}
.win-title {
    font-family: 'Playfair Display', serif;
    font-weight: 400;
    font-size: 1.8rem;
    color: var(--gold);
    margin-bottom: 6px;
}
.win-timer {
    font-size: .8rem;
    color: rgba(245,237,216,0.55);
    margin-bottom: 4px;
}
.win-hints {
    font-size: .76rem;
    color: rgba(245,237,216,0.45);
    margin-bottom: 4px;
}
.win-return-note {
    font-size: .76rem;
    color: rgba(245,237,216,0.6);
    line-height: 1.5;
    margin: 10px 0 2px;
}
.win-btn {
    display: block;
    width: 100%;
    padding: 12px 32px;
    background: transparent;
    color: var(--gold);
    border: 1px solid rgba(201,168,76,0.3);
    border-radius: 50px;
    font-family: 'DM Sans', sans-serif;
    font-size: .82rem;
    font-weight: 400;
    letter-spacing: .1em;
    text-transform: uppercase;
    text-align: center;
    text-decoration: none;
    cursor: pointer;
    transition: background .2s, color .2s;
}
.win-btn:hover { background: var(--gold); color: var(--ink); }
.win-actions {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-top: 16px;
}
/* Share + Dashboard/Home paired side by side (chat 2.14) — direct
   feedback that a card with this many buttons stacked vertically felt
   heavy. Each child gets flex:1 1 0, so two buttons split the row
   50/50; on puzzle.php's non-owner win card (Share only, no
   Dashboard/Home — see $isNonOwnerView) the single remaining child
   fills the full row on its own by the same flex math, with no
   separate fallback rule needed. */
.win-actions-row {
    display: flex;
    gap: 8px;
}
.win-actions-row .win-btn {
    /* min-width:0 is the actual fix, not just a nice-to-have: flex
       items default to min-width:auto, which stops them shrinking
       below their own content's natural width — even with
       flex:1 1 0. "DASHBOARD" (9 letters + .win-btn's base 32px
       padding each side) needed more room than its fair half of the
       row, so it grew past 50% and pushed Share smaller, while still
       barely fitting itself. This override is what makes flex:1 1 0
       actually produce two equal-width buttons. */
    flex: 1 1 0;
    min-width: 0;
    /* .win-btn's base padding/font-size (12px 32px, .82rem) were
       sized for a full-width single button — the win-card interior is
       only ~244px (300px max-width minus 28px padding each side), so
       each half-row button has ~118px to work with. Scaled both down
       so the longer label ("Dashboard") fits with real breathing
       room, not just barely. */
    padding: 12px 8px;
    font-size: .72rem;
    letter-spacing: .06em;
}
/* .win-btn-again is kept as a class purely as puzzle.js's Daily Puzzle
   show/hide hook (initPuzzle() hides it when PUZZLE.is_daily) — it no
   longer carries its own fill styling. Option B (chat 2.9): every
   action reads as equal weight, no single button arbitrarily filled
   gold with no signal for why. */
.win-group-divider {
    border: none;
    border-top: 1px solid rgba(201,168,76,0.15);
    margin: 6px 0 2px;
}

/* Puzzle menu mode (chat 2.9 continued) — the same #win-banner overlay
   now serves two honest, distinct moments: the actual solved
   celebration (default state) and a mid-play menu opened via the
   puzzle-view's × (openPuzzleMenu(), this file). .solved-only content
   (checkmark, "Solved.", timer, hints, Review Puzzle, the divider,
   Share, Exit) never shows in menu-mode — nothing's finished yet, so
   none of it would be true. .menu-only content (Resume Playing, Home,
   Sign In/Sign Out) only shows in menu-mode — irrelevant clutter on an
   actual win. Everything else in .win-actions (Save, New Puzzle,
   Change Category) is valid in both states and carries neither class. */
.win-banner:not(.menu-mode) .menu-only { display: none; }
.win-banner.menu-mode .solved-only { display: none; }

/* Puzzle menu trigger (chat 2.9 continued) — opens the menu above
   (openPuzzleMenu(), puzzle.js) instead of a plain instant close, so
   it earns more visual weight than a bare fade-on-hover ×.

   button.puzzle-menu-trigger, not .puzzle-menu-trigger (chat 2.9
   continued, real fix): the plain class selector had EQUAL
   specificity to .daily-embed-close (daily-embed.css) — both single
   classes — so which one actually won was decided purely by
   stylesheet load order, not intent. On category.php, daily-embed.css
   loads AFTER this file, so .daily-embed-close silently won every
   overlapping property (border, background, font-size, padding,
   color), reverting this button back to a plain fading × there —
   while on puzzle.php (which never loads daily-embed.css at all) the
   circular badge applied cleanly, backwards from either page having
   been the one actually broken. The element-type qualifier here gives
   this genuinely higher specificity (0,1,1 vs .daily-embed-close's
   0,1,0), so it wins on both pages regardless of what order any
   stylesheet happens to load in — not a load-order-dependent patch.

   Self-contained otherwise (including its own positioning) rather
   than relying on .daily-embed-close for that at all — puzzle.php
   doesn't load daily-embed.css, and this needs to work on both pages
   from this one shared file. Circular, thin gold border — same
   restrained badge language as .win-checkmark, not a new ornament
   invented just for this. .puzzle-header already has position:sticky
   (this file, above), which is enough of a positioning context for
   this to anchor to top-right the same way .daily-embed-close anchors
   to .category-puzzle-embed on category.php. */
button.puzzle-menu-trigger {
    position: absolute;
    top: 14px;
    right: 18px;
    z-index: 5;
    width: 34px;
    height: 34px;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid rgba(201,168,76,0.35);
    border-radius: 50%;
    background: rgba(28,26,22,0.4);
    font-size: 1.2rem;
    line-height: 1;
    color: var(--gold);
    cursor: pointer;
    transition: all .2s;
}
button.puzzle-menu-trigger:hover {
    border-color: var(--gold);
    background: rgba(201,168,76,0.12);
    color: var(--gold-light);
}

/* ── Share card fallback modal (js/share-card.js) ── */
.share-preview-modal {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.8);
    z-index: 1000;
    align-items: center;
    justify-content: center;
    padding: 20px;
}
.share-preview-modal.show { display: flex; }
.share-preview-inner {
    position: relative;
    background: rgba(14,12,10,0.97);
    border: 1px solid rgba(201,168,76,0.3);
    border-radius: 20px;
    padding: 20px;
    max-width: 380px;
    width: 100%;
    text-align: center;
    box-shadow: 0 20px 60px rgba(0,0,0,.5);
}
.share-preview-close {
    position: absolute;
    top: 10px;
    right: 14px;
    background: transparent;
    border: none;
    color: rgba(245,237,216,0.55);
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
    transition: color .2s;
    padding: 4px 8px;
}
.share-preview-close:hover { color: var(--gold); }
.share-preview-inner img {
    width: 100%;
    border-radius: 10px;
    margin-bottom: 16px;
    display: block;
}
.share-preview-actions {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

/* ── Desktop: contain in a centered card instead of full-bleed ── */
/* Scoped to html.puzzle-page — see the note on the base html/body rule
   above for why this can't be a bare html/body selector. */
@media (min-width: 640px) {
    /* Desktop Daily Puzzle scrollbar fix (chat 2.9): .page-wrap below
       already contains its own scroll (max-height:88vh, overflow-y:
       auto) — it was always meant to be the one scrollable region.
       The outer html/body `overflow: auto` here was a safety net that
       backfired: any content pushing even slightly past the viewport
       (padding, box-shadow rounding, etc.) triggered the browser's
       own page-level scrollbar alongside .page-wrap's, which is what
       showed up as an ugly, unnecessary bar next to the peek bar on a
       live screenshot. `.page-wrap` doesn't need that outer safety
       net — it already clips and scrolls itself — so html/body stay
       `overflow: hidden` here too, same as their mobile-first default
       just above, just with `height: auto` still applied so body's
       flex-centering isn't clipped at exactly one viewport height. */
    html.puzzle-page, html.puzzle-page body { height: auto; overflow: hidden; }
    html.puzzle-page body {
        display: flex;
        align-items: center;
        justify-content: center;
        min-height: 100vh;
        padding: 40px 20px;
    }
    .page-wrap {
        width: 100%;
        max-width: 620px;
        height: auto;
        max-height: 88vh;
        margin: 0 auto;
        border: 1px solid rgba(201,168,76,0.25);
        border-radius: 18px;
        overflow-y: auto;
        background: rgba(28,26,22,0.5);
        box-shadow: 0 24px 70px rgba(0,0,0,0.55);
        padding-bottom: 24px;
        /* On-brand scrollbar for .page-wrap's own internal scroll —
           thin, low-contrast gold-on-transparent rather than the
           browser's default grey/white bar, matching the same
           treatment agreed for the builder's oversized-grid scroll
           (DECISIONS.md #113). Scoped to this desktop rule only —
           mobile relies on the peek bar affordance, not a visible
           scrollbar, so it's left untouched. */
        scrollbar-width: thin;
        scrollbar-color: rgba(201,168,76,0.25) transparent;
    }
    .page-wrap::-webkit-scrollbar { width: 6px; }
    .page-wrap::-webkit-scrollbar-track { background: transparent; }
    .page-wrap::-webkit-scrollbar-thumb { background: rgba(201,168,76,0.25); border-radius: 3px; }
    .page-wrap::-webkit-scrollbar-thumb:hover { background: rgba(201,168,76,0.4); }
    .puzzle-header { border-radius: 18px 18px 0 0; }
    .questions-peek {
        max-width: 620px;
        left: 50%;
        right: auto;
        transform: translateX(-50%);
        border-radius: 18px 18px 0 0;
        border-left: 1px solid rgba(201,168,76,0.25);
        border-right: 1px solid rgba(201,168,76,0.25);
    }
}

/* ============================================================
   Two-column desktop card, shared by category.php's puzzle-view AND
   puzzle.php's non-Daily branch (chat 2.9 continued). Moved here
   from category-picker.css — puzzle.php never loaded that file, so
   a saved puzzle viewed via Dashboard → Play was silently rendering
   the OLD single-column, mobile-style layout even on a full desktop
   screen (a real, reported gap, not a login-state bug as it first
   appeared — it only ever surfaced via this one path). Everything
   below is scoped under .category-puzzle-embed, so none of it can
   ever affect the Daily Puzzle's branch, which never uses that class
   — direct instruction: the Daily Puzzle's rendered output must stay
   exactly what it already is, not touched by this change at all.

   Two rules below could NOT simply be copied as-is — both used a
   bare #game-screen or #questions-peek ID selector, assuming (safely,
   when only category.php used this file) that nothing else on the
   page needed different treatment for the same id. puzzle.php's Daily
   branch uses that same #game-screen id with its own, already-correct
   .page-wrap-based handling — a bare ID-selector copy would have
   silently overridden that, exactly the same class of bug already
   found twice this session (DECISIONS.md #131). Both are rewritten
   here using :has(.category-puzzle-embed), which only matches when
   the two-column card actually exists in the page — Daily's branch
   never contains it, so these rules simply don't apply there,
   regardless of the shared id. */

.category-puzzle-embed {
    position: relative;
    width: 100%;
    max-width: 980px;
    margin: 0 auto 2rem;
    background: rgba(28,26,22,0.55);
    border: 1px solid rgba(201,168,76,0.25);
    border-radius: 18px;
    padding: 1.5rem 2rem 2rem;
    box-shadow: 0 20px 60px rgba(0,0,0,0.4);
    text-align: left;
    overflow: hidden;
    box-sizing: border-box;
}

/* Matches .puzzle-header's real banner treatment (the true Daily
   Puzzle page, above) exactly — same background/blur/border values —
   scoped to .category-puzzle-embed specifically so it does NOT also
   affect index.php's separate inline daily-embed widget, which reuses
   the same bare .daily-embed-header class but was never meant to pick
   this up (that panel's own .daily-embed-inner already has its own,
   different card treatment). Negative margins bleed it edge-to-edge
   against the card's own padding, so it reads as a real banner strip
   rather than a rounded box floating inside another rounded box. */
.category-puzzle-embed .daily-embed-header {
    background: rgba(28,26,22,0.7);
    backdrop-filter: blur(8px);
    border-bottom: 1px solid rgba(201,168,76,0.3);
    margin: -1.5rem -2rem 0;
    padding: 12px 2rem 10px;
    border-radius: 18px 18px 0 0;
}
/* .puzzle-header itself (base rule, top of this file) is now unused
   anywhere in the live markup — Daily's header was switched to reuse
   this same .daily-embed-header rule directly (puzzle.php) instead of
   a separate .puzzle-header path, after that separate path's bleed
   never rendered correctly despite identical margin/padding values
   (confirmed via DevTools) across several rounds of patching. Left as
   flagged dead code rather than deleted automatically — safe to
   remove .puzzle-header's base rule (and its desktop border-radius
   companion below) whenever convenient. */
/* button.puzzle-menu-trigger's shared top:14px (below) was set before
   #puzzle-eyebrow existed above the logo in this header. Now that the
   eyebrow line sits on top (12px header padding + the eyebrow's own
   text + its 12px margin-bottom, all ahead of the logo), the button's
   old 14px offset lands it squarely on top of that eyebrow text
   instead of the logo it was originally positioned against. Scoped
   here rather than changed on the shared rule itself, since Daily's
   header has no equivalent button at all — its own top-right exit
   button (.puzzle-exit-btn) was removed by direct decision, "‹ Home"
   in the progress row already covering the same job — so this rule
   never needs to apply there regardless. */
.category-puzzle-embed button.puzzle-menu-trigger {
    top: 40px;
}
@media (max-width: 639px) {
    /* Mobile-only: drop the card's semi-opaque fill entirely so the
       category/subcategory background photo shows through unimpeded,
       matching puzzle.php's Daily Puzzle container (.page-wrap),
       which has never had a fill of its own. The rgba(28,26,22,0.55)
       fill on the base .category-puzzle-embed rule above was there
       for desktop's "busier composite background" legibility reason
       (see the .q-text comment below) — that reasoning doesn't apply
       here now that the photo itself already carries its own
       configurable darkness/fade overlay (admin/seed.php's per-
       category and per-subcategory sliders), so this second, fixed
       layer of dimming on top was just redundant on mobile specifically,
       not an intentional design choice being reversed.
       Long-hand `background-color` rather than shorthand `background`
       — same "don't clobber background-image with the shorthand"
       lesson already documented elsewhere in this file — since
       category-picker.js sets `background-image` on body (not this
       element) but this element's own background-image is `none` by
       default, so the shorthand would be equally safe here; using the
       long-hand anyway keeps this rule obviously scoped to only the
       fill color, matching intent at a glance. */
    .category-puzzle-embed { background-color: transparent; border-radius: 0; margin-bottom: 0; }
    .category-puzzle-embed .daily-embed-header { border-radius: 0; }
    /* Breathing room between the header's bottom border and the
       progress bar — the shared .progress-wrap's own padding-top
       (8px) plus the header's padding-bottom (10px) left only an 18px
       gap, reading as crowded directly under the divider line. #game-
       screen is a fixed-height (100dvh) scroll container on mobile
       (see the block below), so a short puzzle (e.g. 6 words, a
       compact grid) already leaves real leftover blank space at the
       BOTTOM of that box rather than needing to scroll — this simply
       spends some of that same, already-existing slack at the top
       instead, rather than adding new overall height. As a direct
       consequence, this also pushes .questions-panel (and its close-X,
       which otherwise peeks up above the fixed peek bar on a short
       puzzle — see .questions-panel-top below) further down toward or
       past the fold, closer to only appearing once genuinely scrolled/
       peeked to, matching how it reads on a longer puzzle. This value
       (confirmed correct for category.php/saved puzzles at 42px) also
       reaches puzzle.php's Daily branch, since chat 2.24's restructure
       put Daily on this same .category-puzzle-embed wrapper too — see
       the separate, higher-specificity override directly below for
       tuning Daily's own spacing independently of this number. */
    .category-puzzle-embed .progress-wrap {
        padding-top: 38px;
    }
    /* Daily-only override — .is-daily is a plain marker class added
       directly to Daily's outer wrapper (puzzle.php), replacing the
       earlier :has(.puzzle-header) detection now that Daily's header
       no longer uses a separate class at all. category.php's and a
       saved puzzle's wrapper never carry .is-daily, so they can never
       match this rule. Started at the same 42px as the shared rule
       above; tuned to 52px directly by the user to further tuck the
       peek bar's close-X under the fold. Change THIS number, not the
       shared one above, to adjust Daily's spacing without moving
       category's. */
    .category-puzzle-embed.is-daily .progress-wrap {
        padding-top: 52px;
    }
    .category-puzzle-embed .daily-embed-columns {
        flex-direction: column;
        flex-wrap: wrap;
        gap: 24px;
        /* Matches .page-wrap's own proven mobile-centering recipe
           (display:flex; flex-direction:column; align-items:center) —
           chat 2.14. Never explicitly set here before: daily-embed.css's
           base rule sets align-items:flex-start, correct for its own
           desktop-only side-by-side layout, but that value was still
           silently active once this override switched to a column
           direction — pinning the stacked grid/questions panel to one
           edge instead of centering them. */
        align-items: center;
    }
    /* Compound selector (.category-puzzle-embed.daily-embed — both
       classes on the SAME element, not a descendant chain), chat 2.14.
       This card carries both classes together (see category.php's
       markup), and daily-embed.css independently sets conflicting
       width/max-width on these same two descendants (.daily-embed
       .grid-wrapper, .daily-embed .questions-panel) at IDENTICAL
       specificity to the plain .category-puzzle-embed version this
       replaced — a silent, load-order-dependent tie, the exact class
       of bug already documented several times in this codebase (see
       MASTER.md §6). This compound form has genuinely higher
       specificity (0,3,0 vs 0,2,0 either way), so it wins outright
       regardless of which stylesheet happens to load second, rather
       than depending on it. */
    .category-puzzle-embed.daily-embed .grid-wrapper,
    .category-puzzle-embed.daily-embed .questions-panel {
        max-width: none;
        width: 100%;
    }
}

/* Short mobile viewports (chat 2.21) — in-app browsers opened from a
   Messenger/text link, or a browser with its toolbars showing, leave
   less visible height, and the Daily's 52px top spacing above pushed
   the bottom of the grid under the fixed peek bar. Reclaim it only when
   height is tight; taller screens keep the tuned 52px. Tune 740 (what
   counts as "short") and 16px (the reduced spacing) on-device. */
@media (max-width: 639px) and (max-height: 740px) {
    .category-puzzle-embed.is-daily .progress-wrap {
        padding-top: 16px;
    }
}

/* Question/answer brightness — this card sits over a busier,
   composite background (category.php: library photo + gradient
   overlay + the card's own semi-transparent fill; a saved puzzle on
   puzzle.php: the same card treatment, flat --ink behind it) than the
   Daily Puzzle's plain background, so the same color values read
   dimmer here even though identical. Scoped to .category-puzzle-embed
   specifically, never touching puzzle.css's own shared .q-text rule
   the Daily Puzzle already uses and which already looks right there. */
.category-puzzle-embed .q-text {
    color: #FFFFFF;
}

@media (min-width: 640px) {
    /* Rewritten from a bare #questions-peek selector (category-picker
       .css originally) — see the block comment above for why. Only
       needed on mobile in the first place: at desktop widths the
       side-by-side .daily-embed-columns layout already shows grid and
       questions together, so the peek bar would be a redundant,
       floating extra control. Daily's own desktop peek-bar treatment
       (repositioned, not hidden — see .questions-peek rules elsewhere
       in this file) is completely unaffected, since :has() only
       matches when .category-puzzle-embed is actually present. */
    body:has(.category-puzzle-embed) #questions-peek { display: none; }

    .category-puzzle-embed .questions-panel-top { display: none; }

    /* Desktop scroll containment — two layers, matching this same
       file's own .page-wrap pattern for the Daily Puzzle:
       1. The questions column scrolls independently — the common
          case, keeps the grid and header in view while a long word
          list (up to 12 questions) scrolls beside it.
       2. The whole card also gets its own max-height/overflow-y as a
          fallback safety net, in case header + progress bar + a tall
          grid ever add up to more than one viewport even with #1 in
          place. Overrides just the y-axis of .category-puzzle-embed's
          existing overflow:hidden via the longhand overflow-y — same
          "prefer longhand over the shorthand" lesson documented
          elsewhere in this file for background-image.
       94vh, not 88vh — body already reserves its own 40px top/bottom
       padding around this card (see this file's own desktop body
       rule), so ordinary content doesn't need to trip this fallback;
       it's only for something genuinely tall (a Difficult/14x19 grid).
       Both containers share the same on-brand thin gold-on-transparent
       scrollbar already established for .page-wrap, instead of the
       browser's default. */
    .category-puzzle-embed {
        max-height: 94vh;
        overflow-y: auto;
        scrollbar-width: thin;
        scrollbar-color: rgba(201,168,76,0.25) transparent;
    }
    .category-puzzle-embed::-webkit-scrollbar { width: 6px; }
    .category-puzzle-embed::-webkit-scrollbar-track { background: transparent; }
    .category-puzzle-embed::-webkit-scrollbar-thumb { background: rgba(201,168,76,0.25); border-radius: 3px; }
    .category-puzzle-embed::-webkit-scrollbar-thumb:hover { background: rgba(201,168,76,0.4); }

    .category-puzzle-embed .questions-panel {
        max-height: 65vh;
        overflow-y: auto;
        scrollbar-width: thin;
        scrollbar-color: rgba(201,168,76,0.25) transparent;
    }
    .category-puzzle-embed .questions-panel::-webkit-scrollbar { width: 6px; }
    .category-puzzle-embed .questions-panel::-webkit-scrollbar-track { background: transparent; }
    .category-puzzle-embed .questions-panel::-webkit-scrollbar-thumb { background: rgba(201,168,76,0.25); border-radius: 3px; }
    .category-puzzle-embed .questions-panel::-webkit-scrollbar-thumb:hover { background: rgba(201,168,76,0.4); }
}

@media (max-width: 639px) {
    /* Rewritten from a bare #game-screen selector — see the block
       comment above. Daily's #game-screen (.page-wrap) already has
       its own correct mobile height/overflow/padding elsewhere in
       this file; this only ever applies when .category-puzzle-embed
       is present, i.e. category.php's puzzle-view or puzzle.php's
       non-Daily branch — never Daily. */
    body:has(.category-puzzle-embed) #game-screen {
        height: 100dvh;
        overflow-y: auto;
        -webkit-overflow-scrolling: touch;
        padding-bottom: 80px;
    }
}

/* ============================================================
   Save button styles — shared by the mid-play spot (below the
   questions list) and the win-card instance on both pages. Moved
   here from category-picker.css for the same reason as the block
   above: puzzle.php needs these now too, and they belong in one
   place rather than two. See DECISIONS.md #127 for the spacing/style
   reasoning (muted secondary treatment, real separation from the
   peek bar, star emoji removed as a site-convention violation). */
.save-puzzle-row {
    margin-top: 32px;
    padding: 22px 0 40px;
    border-top: 1px solid rgba(245,237,216,0.1);
    text-align: center;
}

/* :not(.win-btn) added chat 2.14 — this rule's own equal specificity
   with .win-btn, combined with loading later in this file, was
   silently overriding .win-btn's look on the win-card instance
   (class="win-btn save-puzzle-btn"), contradicting DECISIONS.md #127
   ("the win-card instance...intentionally kept normal .win-btn
   weight, matching its siblings"). Scoping this rule to elements
   WITHOUT .win-btn means the win-card instance no longer matches it
   at all — it just gets .win-btn's own base styling untouched, rather
   than fighting an override that was never supposed to apply there.
   The mid-play instance (no .win-btn class, see puzzle.php/
   category.php) keeps this exact muted treatment unchanged. */
.save-puzzle-btn:not(.win-btn) {
    display: inline-block;
    font-family: 'DM Sans', sans-serif;
    font-size: .68rem;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--ink-soft);
    background: transparent;
    border: 1px solid rgba(245,237,216,0.18);
    border-radius: 4px;
    padding: .6rem 1.3rem;
    cursor: pointer;
    transition: all .2s;
}
.save-puzzle-btn:not(.win-btn):hover:not(:disabled) {
    color: var(--gold);
    border-color: rgba(201,168,76,0.4);
}
.save-puzzle-btn:disabled {
    cursor: default;
    opacity: .75;
}

.save-puzzle-error {
    display: block;
    margin-top: 8px;
    font-size: .72rem;
    color: var(--error);
}
