/* ═════════════════════════════════════════════════════════════════════════
   app-mode.css — picker pages rendered as APP SCREENS.

   Every rule in this file is scoped to `.wsp-app`, which is set (pre-paint, in
   partials/common/theme-bootstrap.php) only when the page is running inside
   the installed PWA / Play Store wrapper, or when ?app=1 was used for review.

   ⛔ THE MARKETING SITE IS UNTOUCHED BY CONSTRUCTION. Without that class not a
   single selector here matches, so a normal browser visitor — including
   Googlebot, which never runs in display-mode: standalone — gets exactly the
   page it got before this file existed. That is the point: mobile-first
   indexing reads the mobile web page, so the SEO copy must stay there. It is
   hidden for the app, not deleted from the site.

   ⚠️ SCOPED ON `html.wsp-app`, NOT `body.wsp-app`. theme-bootstrap sets the
   class on documentElement unconditionally but on body only `if (document.body)`
   — it happens to run inside <body> today (header.php:39), but a view that
   included it from <head> would silently get no body class and every rule here
   would stop matching, with nothing looking wrong until someone opened the app.
   documentElement always exists. site.css:5176 hedges the same way for
   `.wsp-hide-in-app`.

   ⛔ THIS IS NOT ZEN MODE AND MUST NOT BECOME IT. The hide list below is
   adapted from zen (themes.css:1077), but zen ALSO does
   `.main-layout-row { position: fixed; inset: 0 }` — a full-screen overlay.
   An app screen is an ordinary scrollable document: you still need to reach
   the settings, the presets and the entries list. Nothing here changes
   position or overflow. The `zen-mode` CLASS is never used here either —
   40+ engines branch on it, and borrowing it would fire all of them.
   ═════════════════════════════════════════════════════════════════════════ */

/* ── 1. The marketing chrome and the SEO tail ────────────────────────────
   What goes: the 27 KB mega-menu navbar, the footer, the long-form content
   below the tool, related-tools, breadcrumbs and the sticky ad rails.
   What stays: the tool card, the settings drawer, presets, the winner bar,
   the fair receipt, the tool action row (fullscreen/share/import) — and the
   cookie-consent chrome, which is a legal requirement and does not stop
   applying because the page is an app screen. */
/* ⚠️ THE HEADER IS EMPTIED, NOT HIDDEN. The app topbar is rendered from
   header.php and therefore sits INSIDE <header class="wrapper">. A plain
   `header { display:none }` hid the topbar along with the navbar — measured:
   the topbar's own computed display was `flex` while its parent's was `none`,
   so it was correct and invisible at the same time. Hiding every child EXCEPT
   the topbar keeps <header> alive as the topbar's container and still removes
   the mega-menu, the offcanvas drawer and everything else in there. */
/* ⛔⛔ `body:not(.wsp-app-home)` IS NOT OPTIONAL — WITHOUT IT THE APP HOME LOSES
   ITS OWN CONTROLS. This rule empties the site's marketing header on picker
   pages. But /app builds its bar as `<header class="wsp-app-bar">` too, so in
   APP MODE it matched there and hid the brand, the search box, the Sort-by
   toggle and the theme button — leaving an 18px empty strip. Measured
   2026-08-27: bar 166px in a browser, **18px in the installed app**, with
   `search: zero box` and `seg: display:none`.

   ⚠️ It reached the owner because every check loaded /app WITHOUT the app-mode
   flag — the one state /app actually runs in. A page can be perfect in the
   state you test and broken in the only state that ships. */
html.wsp-app body:not(.wsp-app-home) header > *:not(.wsp-app-topbar),
html.wsp-app footer,
html.wsp-app .navbar-clone,
html.wsp-app .page-title-section,
html.wsp-app .tool-intro:not(.tool-intro-head),
html.wsp-app .content-guide,
html.wsp-app section.wrapper,
html.wsp-app main > section,
html.wsp-app .content-section,
html.wsp-app .seo-content-section,
html.wsp-app .related-tools,
html.wsp-app .breadcrumb,
html.wsp-app [id*="sticky-ad"],
html.wsp-app .bottom-sticky-ad,
/* ⚠️ The floating "Buy Us a Coffee" tab (a direct child of <body>). It is a
   website ask — a donation pitch that sits over the tool — and the owner ruled
   it out of the app on 2026-08-27. It stays on the public site untouched. */
html.wsp-app #wspSupportFloat {
    display: none !important;
}

/* ⛔ THE WINNER-POPUP SETTING IS REMOVED FROM THE APP'S SETTINGS.
   In app mode the winner ALWAYS arrives as the bottom sheet, so the toggle no
   longer chooses between two reveals — measured by the owner: with it on, "the
   only thing it does is remove the Save card / Share link buttons". A control
   whose only remaining effect is to take away two buttons is a trap, not a
   setting (SKILLS 68: never ship a dead switch).

   ⚠️ Matched by the INPUT ID SUFFIX, not by a per-page id. winner-result.js
   addresses this checkbox as `byId(prefix + 'Popup')`, so the id differs on
   every page (corePopup, lrPopup, m8Popup…) but the suffix is the contract the
   engine itself relies on. `:has()` is safe here: app mode only ever runs in a
   Chromium/WebKit shell new enough to support it, and if it ever did not the
   row would simply remain — a visible extra setting, not a broken page. */
html.wsp-app .wsp-set:has(input[id$="Popup"]) {
    display: none !important;
}

/* ⛔ THE SEO-TAIL SELECTORS ARE MEASURED OVER ALL 151 PICKERS, NOT INFERRED.
   `writable/ui-tools/seo-tail-inventory.php` fetches every routed picker and
   counts each marker. Run it again after adding pages; the numbers below are
   2026-08-27:

       .content-guide     141 pages   ← the real workhorse
       .content-section    12
       section.wrapper     11
       (no marker)          4         planet-picker, team-generator,
                                      random-number-generator, comment-picker

   ⚠️ `.content-guide` WAS MISSING FROM THE FIRST VERSION OF THIS FILE, and
   124 pages carry it as their ONLY marker — so app mode was shipping the full
   SEO tail on four fifths of the site while the four pages I happened to spot-
   check looked perfect. The plan predicted two shapes; there are four.

   ⛔ `section.wrapper` IS UNSCOPED ON PURPOSE. Zen's list (themes.css:1077)
   says `main > section.wrapper`, which matches ZERO elements here: on
   classic-wheel the sections are under <main> but not as direct children, and
   on magic-8-ball they are parented to **BODY**. Any narrowing re-breaks one
   of those two.

   ⚠️ `main > section` COVERS THE 4 THAT CARRY NO CLASS AT ALL. planet-picker,
   team-generator, random-number-generator and comment-picker put their prose in
   bare <section> children of <main>. It is a broad selector, so it was only
   added after `main-section-safety.php` parsed ALL 151 rendered pages and
   proved that **none of the 211 such sections on 137 pages contains the tool**
   (checked for main-layout-row / main-layout-left / wheel-card-mobile /
   spin-container / wsp-result). Re-run that tool before widening this further —
   on a page where the tool lived inside one of these, this rule would hide the
   tool itself and nothing would look wrong until someone opened that page.

   ⚠️ `.tool-intro:not(.tool-intro-head)` — the H1 lives in
   `section.tool-intro.tool-intro-head`, so hiding bare `.tool-intro` took the
   page's heading with it. The marketing prose below the tool is the plain
   `.tool-intro`; the head keeps its H1 and loses only its sub-line (below). */

/* ── 2. The app top bar ─────────────────────────────────────────────────
   Replaces the navbar with one row: back to the list, and the page's name.
   Hidden by default so it costs a normal visitor nothing but the markup. */
.wsp-app-topbar { display: none; }

html.wsp-app .wsp-app-topbar {
    display: flex;
    align-items: center;
    gap: .6rem;
    position: sticky;
    top: 0;
    z-index: 30;
    padding: .5rem .7rem;
    background: var(--theme-surface, #fff);
    border-bottom: 1px solid var(--theme-border, #e4e7ec);
}

.wsp-app-topbar-back {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* 40px: a back button that is hard to hit is the one control nobody can
       work around. R4's lesson — give it real area, not an icon's worth. */
    width: 40px;
    height: 40px;
    flex: 0 0 auto;
    border-radius: 10px;
    font-size: 1.05rem;
    color: var(--theme-accent-ink, #4f46e5);
    background: var(--theme-bg, #f2f3f7);
    border: 1px solid var(--theme-border, #e4e7ec);
    text-decoration: none;
}

.wsp-app-topbar-title {
    flex: 1 1 auto;
    min-width: 0;
    font-size: .95rem;
    font-weight: 700;
    line-height: 1.2;
    /* The bar is painted --theme-surface, so the ink is --theme-surface-text.
       With --theme-text this read 1:1, white on white, in high-contrast. */
    color: var(--theme-surface-text, #1f2430);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ── 2a. THE THEME CONTROL — SHARED BY BOTH APP BARS ─────────────────────
   Moved here from app-home.css on 2026-08-28, when the owner asked for the
   theme button on every app screen rather than only on /app. The markup is
   partials/app/theme-menu.php; app-shell.js wires it.

   ⛔ THESE RULES ARE DELIBERATELY UNSCOPED — no `html.wsp-app` prefix — and
   that is safe for exactly one reason: `.wsp-app-theme` exists in only two
   places, /app's own bar (which has no wsp-app class when someone opens the URL
   in a normal browser, and still needs a working control) and the app topbar
   (which is `display:none` off app mode, so nothing inside it can be seen or
   reached). `.wsp-app-topbar { display: none }` a few rules up is unscoped for
   the same reason. A public page renders this markup and paints none of it.

   ⚠️ commoncss.php loads THIS sheet on /app too — app-home.php includes it —
   which is why one copy can serve both bars. app-home.css is the /app-only one.

   ⛔ THIS COMMENT'S CLOSING MARKER IS LOAD-BEARING. Written without it, the
   comment ran on and swallowed the whole .wsp-app-bar-btn rule below, closing
   instead at the inner comment's marker inside that rule. The button rendered
   with the browser's default button chrome (2px outset black on #f0f0f0) on
   every app screen, and css-comment-balance.php still reported "balanced"
   because the PAIRS balance -- the comment simply ends in the wrong place.
   SKILLS 109, third time. */

.wsp-app-bar-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* 40px, so the tap target clears the 44px guideline with the bar padding
       around it. R4's lesson: a control that is visually fine can still be
       unclickable, so give it real area rather than an icon's worth. */
    width: 40px;
    height: 40px;
    border-radius: 10px;
    font-size: 1.15rem;
    color: var(--theme-accent-ink, #4f46e5);
    background: var(--theme-bg, #f2f3f7);
    border: 1px solid var(--theme-border, #e4e7ec);
    text-decoration: none;
}

.wsp-app-bar-btn:hover,
.wsp-app-bar-btn:focus-visible { background: var(--theme-border, #e9ebf0); }

/* ── the theme menu ──────────────────────────────────────────────────────
   The one screen with no navbar and no settings drawer still needs a way OUT
   of a theme; without this, choosing dark on /app was a one-way door. */
/* margin-left:auto pins it to the right end of whichever bar it is in. The
   .4rem of margin-right it used to carry was clearance for the classic-wheel
   button that sat after it on /app; that button was removed on 2026-08-28, so
   the gap now just holds the control off the bar's own padding. */
.wsp-app-theme { position: relative; margin-left: auto; }

/* ⛔ THE MENU RULES ARE GONE, WITH THE MENU. The app bars used a four-option
   dropdown for one day; the owner asked for the site's single-click cycler
   instead (2026-08-28), so `.wsp-app-theme-menu` no longer exists in any
   markup. Dead rules for markup nobody renders are how a stylesheet starts
   lying about what the page contains. */



/* In the app topbar the button sits beside an ellipsised title that is
   `flex: 1 1 auto`. Without this the title's overflow would compete for the
   40px the control needs and shrink it on a long tool name. */
.wsp-app-topbar .wsp-app-theme { flex: 0 0 auto; }

/* ── 2a-ii. THE PRESET CROSS-SELL IS NOT APP FURNITURE ───────────────────
   Owner, 2026-08-28: *"on Combo Spin page, don't show any presets there"*.

   ⚠️ WHAT THIS CARD ACTUALLY IS, measured rather than assumed: a "Popular
   Presets / Browse all 150" block that is IDENTICAL on every engine — on
   combo-spin it advertises Wheel of Names, NBA Team Picker and eighteen other
   classic-wheel pages. It is a cross-sell for the website, and inside an app it
   is a list of other apps sitting under the tool you opened.

   ⛔⛔ AND THE SELECTOR HAS TO BE NARROWER THAN THE CLASS. Measured: the SAME
   class hosts two different things.

       dice-roll   "Quick Presets"    #presetTiles -> 7 real per-picker presets
       coin-toss   "Quick Presets"    #presetTiles -> 8 real per-picker presets
       combo-spin  "Popular Presets"  no tiles, the 150-page cross-sell

   Hiding `.quick-presets-mobile` outright — which is what this rule did for one
   round — also hid the REAL presets the owner wants surfaced. The cross-sell is
   the variant with NO #presetTiles inside it, so that is what :not(:has()) keys
   on. ⚠️ The tiles are injected by SCRIPT, so the raw HTML has zero .preset-card
   and a server-side check cannot tell the two apart.

   ⛔ HIDDEN, NOT DELETED, and only under html.wsp-app — the website keeps it. */
html.wsp-app .quick-presets-mobile:not(:has(#presetTiles)) { display: none !important; }

/* ── 2a-iii. THE SETTINGS GEAR SITS WITH THE OTHER TOOLS ─────────────────
   app-shell.js moves #off-sett-btn into .wsp-tool-actions in app mode; these
   rules place it once it arrives. Owner, 2026-08-28: the gear was overlapping
   the stage's own mode switch, and moving it up "will also create more space so
   the stage area will be a little bit more up".

   ⚠️ RIGHT-ALIGNED, NOT IN THE CENTRE ROW — he asked for exactly that. The row
   centres its three actions with justify-content:center, so an auto left margin
   would fight it; absolute positioning takes the gear out of the flow instead
   and leaves the other three centred on the true middle of the row. */
html.wsp-app .wsp-tool-actions.wsp-app-has-gear { position: relative; }

html.wsp-app .wsp-tool-actions .wsp-app-gear {
    position: absolute;
    right: 0;
    top: 50%;
    transform: translateY(-50%);
    margin: 0;
}

/* ⛔ AND THE CARD MUST GIVE THE SPACE BACK. The gear was the only thing holding
   the card title's row open on a phone; with it gone the heading keeps its old
   bottom margin and the stage does not actually move up, which was the point of
   the change. */
html.wsp-app .card-title:has(> .wsp-app-gear) { margin-bottom: 0; }

/* ── 2a-iv. ONE TREATMENT FOR EVERY PIECE OF APP CHROME ──────────────────
   Owner, 2026-08-28: *"the back button color should be same for all the pickers,
   based on theme... and also notice the button colors of fullscreen share etc.
   icons at the top.. not visible properly... again should be proper and same"*.

   ⛔ MEASURED ON date-ideas-picker, WHICH IS WHY IT LOOKED SO WRONG THERE:

       topbar BACK    ink rgb(240,216,240) on rgb(242,243,247)   1.20:1
       tool actions   ink rgb(33,37,41)    on rgb(12,4,16)       1.31:1

   Neither control owned its colour. The back link is an <a>, so it inherited
   this picker's PINK page accent; the Save/Embed/Fullscreen row inherited
   bootstrap's near-black body ink and happens to sit on a stage wrapper that is
   dark purple in EVERY theme. Both were invisible in light mode, and both would
   look different again on the next picker — which is exactly the inconsistency
   he is describing.

   ⭐ THE FIX IS TO STOP INHERITING. In app mode every chrome control is painted
   as the same chip: the theme's own surface, the ink that belongs to that
   surface, and the theme's border. It then reads identically on all 151 pickers
   regardless of what the page's palette or the stage's backdrop happen to be.

   ⚠️ SCOPED TO html.wsp-app. He noted the same defect exists on the desktop
   site; that is a separate, visible change to 151 public pages and is NOT made
   here. */
/* ⚠️ DESCENDANT, NOT DIRECT CHILD. The share button is wrapped in a
   `.wsp-ta-slot` span, so a `>` selector styled the download, fullscreen and
   gear and left share sitting in accent indigo — three matching chips and one
   odd one out, which is the same complaint in miniature. */
/* ⭐ ONLY THE BACK BUTTON IS LISTED NOW. The tool-action row was fixed at its
   SOURCE in classic-wheel-styles.css on 2026-08-28 (the owner approved the
   desktop fix), so app mode inherits it and a second copy here would be two
   "equivalent" stylesheets waiting to drift (SKILLS 38). The back button stays
   because it exists only in app mode. */
html.wsp-app .wsp-app-topbar-back {
    background: var(--theme-surface, #fff) !important;
    color: var(--theme-surface-text, #1f2430) !important;
    border: 1px solid var(--theme-border, #e4e7ec) !important;
    border-radius: 10px;
    text-decoration: none;
}

/* the glyph inside must follow the chip, not themes.css's site-wide .bi rule
   (themes.css:269 paints EVERY bootstrap glyph with an !important colour —
   SKILLS 121, and the reason this needs both important and specificity) */
html.wsp-app .wsp-app-topbar-back .bi {
    color: inherit !important;
    opacity: 1 !important;
}

/* ── 2b. THE WINNER ANNOUNCEMENT ─────────────────────────────────────────
   In the app a result arrives as a compact sheet from the bottom, not as the
   desktop winner CARD in the page and not as the full-screen Bootstrap modal.
   Driven by app-winner.js, which wraps wspWinnerResult.show().

   ⚠️ THE BAR IS HIDDEN WITH `display:none`, NOT WITH ITS `hidden` ATTRIBUTE,
   and that distinction is load-bearing. `wspWinnerResult.visible()` reads the
   attribute (`!bar.hidden`), and engines branch on it; setting the attribute
   here would tell every one of them the winner is not showing. CSS hides the
   pixels and leaves the contract alone. The bar also stays measurable, which
   measureActions() needs.

   ⚠️ The 15 pickers whose STAGE is the reveal have no bar at all, so nothing
   here matches and nothing about them changes — they keep announcing on their
   own stage, which is what they were built to do. */
html.wsp-app .wsp-result {
    display: none !important;
}

/* The sheet itself. Fixed to the viewport, above everything including the
   settings drawer, but below nothing that matters — the consent bar is the
   only other fixed element and it sits lower. */
.wsp-aw {
    position: fixed;
    inset: 0;
    z-index: 2000;
    display: flex;
    align-items: flex-end;
    justify-content: center;
}

.wsp-aw[hidden] { display: none; }

.wsp-aw-scrim {
    position: absolute;
    inset: 0;
    background: rgba(8, 11, 20, .55);
    opacity: 0;
    transition: opacity .2s ease;
}

.wsp-aw.is-open .wsp-aw-scrim { opacity: 1; }

.wsp-aw-card {
    position: relative;
    width: 100%;
    max-width: 460px;
    /* Rounded at the top only — it is a sheet attached to the bottom edge,
       not a floating dialog. That single detail is most of what makes it read
       as native rather than as a web modal. */
    border-radius: 20px 20px 0 0;
    background: var(--theme-surface, #fff);
    /* ⛔ `--theme-surface-text`, NEVER `--theme-text`, on a surface.
       In high-contrast BOTH `--theme-surface` and `--theme-text` are #ffffff —
       the theme paints white text on a BLACK page background, and its surfaces
       are white with BLACK ink. Using --theme-text here measured the winner's
       name at **1:1, white on white**: perfectly invisible, and only in the one
       theme chosen by people who need contrast most. `--theme-surface-text` is
       the token that exists for exactly this (#000 here, #e5e7eb in dark). */
    color: var(--theme-surface-text, #1f2430);
    padding: 1.4rem 1.15rem calc(1.15rem + env(safe-area-inset-bottom, 0px));
    text-align: center;
    box-shadow: 0 -8px 40px rgba(8, 11, 20, .28);
    transform: translateY(100%);
    transition: transform .24s cubic-bezier(.22, .68, .3, 1.02);
}

.wsp-aw.is-open .wsp-aw-card { transform: translateY(0); }

/* On a tablet or desktop the same sheet centres and floats, so /app opened on
   a big screen does not get a full-width band stuck to the bottom edge. */
@media (min-width: 620px) {
    .wsp-aw { align-items: center; }
    .wsp-aw-card { border-radius: 20px; }
}

@media (prefers-reduced-motion: reduce) {
    .wsp-aw-card, .wsp-aw-scrim { transition: none; }
}

.wsp-aw-x {
    position: absolute;
    top: .55rem;
    right: .55rem;
    width: 36px;
    height: 36px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: var(--theme-muted, #6b7280);
    font-size: .85rem;
}

.wsp-aw-art {
    width: 72px;
    height: 72px;
    margin: 0 auto .6rem;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 16px;
    overflow: hidden;
    background: var(--theme-bg, #f2f3f7);
    font-size: 2rem;
    line-height: 1;
}

.wsp-aw-art img { width: 100%; height: 100%; object-fit: cover; }
.wsp-aw-art svg  { max-width: 100%; max-height: 100%; }

.wsp-aw-kicker {
    margin: 0 0 .15rem;
    font-size: .72rem;
    font-weight: 700;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--theme-muted, #6b7280);
}

/* The winner's name is the whole point of the sheet, so it gets the size.
   ⚠️ It WRAPS rather than truncating — a name is not a label you can cut in
   half, and the entries are user-supplied so they can be long. */
.wsp-aw-name {
    margin: 0;
    font-size: 1.65rem;
    font-weight: 800;
    line-height: 1.2;
    letter-spacing: -.02em;
    overflow-wrap: anywhere;
}

.wsp-aw-verdict {
    margin: .4rem 0 0;
    font-size: .92rem;
    line-height: 1.35;
    color: var(--theme-muted, #6b7280);
}

.wsp-aw-actions {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
    margin-top: 1.1rem;
}

.wsp-aw-btn {
    flex: 1 1 40%;
    min-height: 46px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: .4rem;
    padding: .5rem .8rem;
    border-radius: 12px;
    border: 1px solid var(--theme-border, #e4e7ec);
    background: var(--theme-bg, #f2f3f7);
    color: var(--theme-text, #1f2430);
    font-size: .92rem;
    font-weight: 600;
}

/* ⚠️ SOLID, NOT A GRADIENT. The winner card's "Again" pill is a long-running
   contrast defect precisely because it is a wide gradient: white ink passes at
   one stop and fails at the other, and repainting the text cannot fix a range
   (CARRYOVER C7). A flat fill has ONE contrast to check.

   ⛔ AND IT IS A FIXED INDIGO, NOT `--theme-accent-ink`. That token is an INK
   FOR TEXT — B13 chose the dark theme's value (#818cf8) by measuring it AS TEXT
   on --theme-surface. Used as a BACKGROUND under white it measured 2.98:1 in
   dark and 2.56:1 in high-contrast, both well under AA. Because both this fill
   and its ink are absolute colours, the pair measures the same 6.29:1 in every
   theme — there is no per-theme value that can drift out of range. */
.wsp-aw-btn--primary {
    flex: 1 1 100%;
    background: #4f46e5;
    border-color: #4f46e5;
    color: #fff;
}

/* High contrast speaks in pure black and white and its own cyan; an indigo
   button reads as foreign there. Its accent under BLACK ink is the pairing that
   both matches the theme and clears AA — measured below, not assumed. */
:root[data-theme="high-contrast"] .wsp-aw-btn--primary,
body[data-theme="high-contrast"] .wsp-aw-btn--primary {
    background: var(--theme-accent, #00aaff);
    border-color: var(--theme-accent, #00aaff);
    color: #000;
}

.wsp-aw-btn:active { transform: translateY(1px); }

/* ── 2c. THE PROVABLY-FAIR RECEIPT BECOMES A MODAL ───────────────────────
   On the website the shield expands a panel below the card. In the app that
   pushes the tool up and leaves the reader scrolling to get back (owner,
   2026-08-27: "the shield click should be a modal popup, not the card under the
   shield.. so user can view and close anytime.. to avoid scroll").

   ⚠️ NO NEW MARKUP AND NO NEW TOGGLE. The panel is the page's own
   `#wspFairPanel`, still opened and closed by `wspToggleFairPanel()`; only its
   POSITIONING changes here, and app-shell.js adds the scrim and the outside-tap
   close. The receipt's contents, its Copy button and every id are untouched, so
   fair-panel.js keeps working exactly as it does on the website — which matters,
   because this panel is the site's trust feature. */
html.wsp-app #wspFairPanel:not([hidden]) {
    position: fixed;
    z-index: 2100;                 /* above the winner sheet's scrim (2000) */
    left: 50%;
    bottom: 0;
    transform: translateX(-50%);
    width: 100%;
    max-width: 560px;
    /* Tall enough to read a full receipt, short enough to never fill the
       screen — it scrolls INSIDE itself rather than scrolling the page, which
       is the whole complaint being fixed. */
    max-height: 82vh;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    margin: 0;
    border-radius: 18px 18px 0 0;
    box-shadow: 0 -8px 40px rgba(8, 11, 20, .32);
    padding-bottom: calc(1rem + env(safe-area-inset-bottom, 0px));
}

@media (min-width: 620px) {
    html.wsp-app #wspFairPanel:not([hidden]) {
        bottom: auto;
        top: 50%;
        transform: translate(-50%, -50%);
        border-radius: 18px;
    }
}

/* The scrim. A real element (added by app-shell.js) rather than a pseudo, so it
   can take the tap that closes the panel. */
.wsp-fair-scrim {
    position: fixed;
    inset: 0;
    z-index: 2090;
    background: rgba(8, 11, 20, .55);
    border: 0;
    padding: 0;
}

.wsp-fair-scrim[hidden] { display: none; }

/* The close button sits over the panel's own header row, which ends with the
   "Sealed before the spin" state text — measured overlapping it. Reserve the
   corner rather than moving the button somewhere less expected. */
html.wsp-app #wspFairPanel:not([hidden]) .fairp-head {
    padding-right: 2.6rem;
}

/* A close control, appended to the panel in app mode only. */
.wsp-fair-x {
    position: absolute;
    top: .5rem;
    right: .5rem;
    width: 36px;
    height: 36px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: 50%;
    background: rgba(127, 127, 127, .16);
    color: inherit;
    font-size: .85rem;
    z-index: 1;
}

/* ── 3. Reclaim the space the chrome was holding ─────────────────────────
   With the navbar and intro gone, the containers' top padding leaves the tool
   floating well below the bar. Trimmed, not zeroed — the card still needs to
   breathe. */
html.wsp-app main,
html.wsp-app main > .container,
html.wsp-app .container.pb-4,
html.wsp-app .container.pb-md-5 {
    padding-top: .5rem !important;
}

html.wsp-app .main-layout-row { margin-top: 0 !important; }

/* ── 4. ONE NAME, NOT THREE ──────────────────────────────────────────────
   Owner, 2026-08-27: the tool's name appeared THREE times on one phone screen —
   in the app top bar, again as the H1 with its marketing sub-line, and again as
   the wheel card's own heading. In an app that is two too many.

   What survives is the WHEEL CARD'S name, shown once, in the top bar — because
   that is the tool's real editable name ("Classic Wheel Spin"), while the H1 is
   an SEO heading ("Classic Spinning Wheel – Free Online Wheel Spinner…").
   app-shell.js copies it up; this hides the two originals.

   ⚠️ The H1 block goes ENTIRELY — heading and sub-line. The tool action row
   (import / share / embed / fullscreen) is a SIBLING inside `.wsp-tool-head`,
   not a child of `.tool-intro-head`, so it is untouched. Verified: the action
   row is still present and clickable with this rule applied. */
html.wsp-app .tool-intro-head {
    display: none !important;
}

/* ⛔ HIDE THE TITLE, NOT THE HEADER ROW — THE GEAR LIVES IN IT.
   Both page shapes put the title in a `.flex-grow-1` child of the card title,
   with the settings / Advanced button as its SIBLING:

     A. wheel-display-card.php   <h5 class="card-title …">
                                   <div class="flex-grow-1 …"><span id="wheelTitle">
                                   <button = the gear>
     B. panel-header.php         <h2 class="card-title … wsp-panel-head">
                                   <span class="flex-grow-1 …">
                                   <button id="wspPanelAdvancedBtn">

   So hiding `.card-title > .flex-grow-1` removes exactly the name and leaves
   the control. Hiding the `.card-title` itself would take the only way into
   settings with it — which on a phone is the only way to edit the entries.

   ⚠️ SCOPED TO `.main-layout-left`: the same markup builds the SETTINGS drawer
   header, and hiding its title there would leave an anonymous panel. */
html.wsp-app .main-layout-left .card-title > .flex-grow-1 {
    display: none !important;
}

/* With the name gone the header row is just the gear, so it no longer needs to
   reserve a heading's worth of space below itself.

   ⚠️ AND IT MUST BE RE-JUSTIFIED. The row is `justify-content: space-between`,
   which with two children put the title left and the gear right — but with ONE
   child left it pushes the gear to the START. The settings control jumped to
   the top-LEFT of the card, which is not where anyone reaches for it and not
   where it sits on the public site. flex-end keeps it exactly where it was. */
html.wsp-app .main-layout-left .card-title {
    justify-content: flex-end !important;
    margin-bottom: .5rem !important;
    min-height: 0;
}
