/* ═══════════════════════════════════════════════════════════════════════════
   page-bg.css — the page background, and what the card does about it (B17)
   ───────────────────────────────────────────────────────────────────────────
   EVERYTHING here is scoped to html.wsp-has-page-bg, which is added only when a
   page background is actually set. With no background this file is inert and
   the page is byte-for-byte what it is today — including the body gradient,
   which comes back by itself because nothing here ever removed it.

   ⛔ NEVER REACHES AN OBS OVERLAY. Two independent stops: ObsOverlay strips
   `pageBg` out of the stored settings at the source, and engine-boot.php will
   not call the setter when WSP_OBS_MODE is defined. An overlay is transparent
   on purpose; a full-bleed photo is an opaque plate in somebody's live stream.

   ⚠️ THE NUMBERS IN HERE ARE MEASURED, NOT CHOSEN. See the plate rule below.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── 1. the photo, and how far down the page it is allowed to go ───────────── */

/* ⛔ IT MUST NOT REACH THE SEO ARTICLE. Measured on the live classic-wheel at
   1440x900: the document is 8,556px tall, the tool finishes around 1,100px, and
   the article starts at 5,482px and runs for another 1,678px with no background
   of its own. That article is what earns the page its search traffic; putting a
   stranger's photograph behind it is not a background, it is a redesign.
   So the layer is a fixed height near one viewport, and it fades out. */
html.wsp-has-page-bg body {
    /* ⭐ A STACKING CONTEXT, SO THE LAYER BELOW CAN SIT AT z-index -1 AND STILL
       BE INSIDE IT. position:relative alone is not enough — with z-index:auto
       body creates no context, and a negative-z child would escape to the root
       and paint BEHIND body's own background, i.e. invisibly. */
    position: relative;
    z-index: 0;
}

html.wsp-has-page-bg body::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: min(calc(100vh + 200px), 1200px);
    background-image: var(--wsp-page-bg);
    background-size: cover;
    background-position: center top;
    background-repeat: no-repeat;
    pointer-events: none;
    z-index: -1;
    /* A hard bottom edge reads as a broken image; this dissolves it into the
       page's own ground over the last 200px. */
    -webkit-mask-image: linear-gradient(180deg, #000 0, #000 calc(100% - 200px), transparent 100%);
    mask-image: linear-gradient(180deg, #000 0, #000 calc(100% - 200px), transparent 100%);
}

/* ⛔⛔ THERE WAS A `body > *` RULE HERE AND IT BROKE FOUR THINGS.
   It set `position: relative; z-index: 1` on every direct child of body to lift
   them above the photo. The z-index was the point; the position was the means —
   and it OVERWROTE the position of everything that was already fixed. Measured
   with a page background set:

       nav.navbar    fixed z=1030 @-78  ->  relative z=1 @8500  (navbar fell to
                                             the bottom of an 8,656px document)
       div.modal x4  fixed z=1055       ->  relative z=1
       div.wsp-aw    the winner reveal  ->  relative z=1, 140px tall

   The owner saw the last one: the winner spotlight rendered as a band across the
   page instead of a full-screen reveal. Nothing lifts the siblings now — the
   layer sits at z-index -1 inside body's own stacking context instead, which
   paints it above body's background and below every child without touching any
   of them (SKILLS 1c: a rule whose purpose is stacking must not also set
   position). */

/* ⛔⛔ AND THE CONTENT WRAPPER, WHICH IS ONLY OPAQUE IN SOME THEMES.
   The owner found the photo missing in dark mode. Measured across all three
   reachable themes:

       light          photo 51.3% of the screen
       dark           photo 0%    .content-wrapper bg = rgb(18,18,18), 99% cover
       high-contrast  photo 0%    .content-wrapper bg = rgb(0,0,0),    99% cover

   In light that wrapper is transparent, so nothing was in the way and every
   check passed. themes.css gives it a solid ground in the other two, and the
   photo layer sits below body's CONTENT — which the wrapper is.

   ⚠️ Making it transparent costs nothing below the photo: body carries the same
   colour underneath, so the article keeps exactly the ground it had.
   ⛔ THE REAL LESSON IS THE PROCESS ONE. A themed component measured in one
   theme is not measured (SKILLS 115/86). wheel-image-check now runs its paint
   assertions in all three. */
/* ⛔ SPECIFICITY, NOT SOURCE ORDER — AND A TIE IS NOT A WIN.
   themes.css declares
       body[data-theme] .content-wrapper { background: var(--theme-bg) !important }
   which is (0,2,1). The first version of this rule was
       html.wsp-has-page-bg .content-wrapper
   which is ALSO (0,2,1) — a tie, decided by source order. That worked on a
   picker page and FAILED on /w/{token}, because the shared-wheel page loads
   themes.css a SECOND time AFTER page-bg.css. Measured on the share page: the
   matching rules came back themes.css, page-bg.css, themes.css, and the
   wrapper stayed rgb(18,18,18).
   Adding `body` makes it (0,2,2), so it wins on specificity and no load order
   can take it away (SKILLS 112).
   ⚠️ themes.css being linked twice is pre-existing and left alone — removing a
   duplicate link changes the cascade for everything else on that page. */
html.wsp-has-page-bg body .content-wrapper {
    background: transparent !important;
}

/* ── 2. the card gets out of the way ───────────────────────────────────────── */

/* Owner, 2026-09-01: "that white square should be transparent in this case".
   Fully transparent, not tinted — the tint moved to the text instead, which is
   what lets 93.4% of the photo show through instead of none of it. */
html.wsp-has-page-bg .wheel-card-mobile,
html.wsp-has-page-bg .main-layout-left > .card,
html.wsp-has-page-bg .spin-container > .card {
    background: transparent !important;
    border-color: transparent !important;
    box-shadow: none !important;
}

/* ── 3. the text carries its own ground ────────────────────────────────────── */

/* ⛔⛔ THIS IS THE ONLY THING KEEPING THE TEXT READABLE, AND IT IS THE TINT
   THAT DOES IT — NOT THE BLUR. A blurred black photo is still black; frosted
   glass looks like it should help legibility and contributes exactly zero.

   Measured worst case, dark ink over the darkest pixel a photo can contain:
       0% (fully transparent)   1.17:1   invisible
      30%                       2.11:1   still fails
      50%                       4.53:1   passes, by a hair
      72%                       9.03:1   <- SHIPPED. Owner picked specimen D.
      92%                      15.04:1
   AA needs 4.5:1. 72% clears it twice over and still lets the photo read
   through the words.

   ⛔ WHY width:max-content AND NOT display:inline. An inline background hugs
   the words exactly, which is what we want — but these headings are flex/grid
   children in places, and a flex child is BLOCKIFIED: display:inline computes
   to block, box-decoration-break has nothing to clone, and the plate silently
   renders as a full-width bar. That is measured, not theoretical — it happened
   in the design mock and covered 13.1% of the surface instead of 6.6%.
   width:max-content hugs the text as a block, needs no markup change, and
   cannot be blockified out of existence. max-width:100% lets it wrap normally. */
html.wsp-has-page-bg .tool-intro h1,
html.wsp-has-page-bg .tool-intro .tool-intro-sub,
html.wsp-has-page-bg .tool-intro .tool-intro-lead,
html.wsp-has-page-bg .wheel-card-mobile .card-title > .flex-grow-1 {
    background: rgba(255, 255, 255, .72);
    width: max-content;
    max-width: 100%;
    padding: .12em .5em;
    border-radius: 8px;
    /* The existing text-shadow stays underneath: it softens the plate's edge and
       it was already there. A shadow is NOT a substitute for the plate — WCAG's
       formula takes two colours and a shadow is neither, so a shadow-based page
       cannot be checked, only looked at. */
}

/* ⛔ A FLEX ITEM IGNORES width:max-content WHILE IT IS ALLOWED TO GROW.
   .card-title is display:flex and this div carries .flex-grow-1, so the plate
   stretched into a bar across the whole card — the owner photographed it.
   flex: 0 1 auto stops the growing; margin-inline: auto keeps it centred in the
   row it no longer fills. It deliberately wraps the glyph AND the title: the
   card beneath is transparent, so the icon needs the plate as much as the words. */
html.wsp-has-page-bg .wheel-card-mobile .card-title > .flex-grow-1 {
    /* ⚠️ !important IS REQUIRED HERE AND IT IS NOT MINE TO AVOID. Bootstrap's
       .flex-grow-1 utility is itself `flex-grow: 1 !important`, so a plain
       declaration loses no matter how specific — measured: the plate stayed
       704px in a 704px row with computed flex "1 1 auto" (SKILLS 121, an
       !important you did not write is part of the spec). */
    flex: 0 1 auto !important;
    margin-inline: auto;
}

/* Centred headings keep their centring once the box hugs the text. */
html.wsp-has-page-bg .tool-intro.text-center h1,
html.wsp-has-page-bg .tool-intro.text-center .tool-intro-sub,
html.wsp-has-page-bg .tool-intro.text-center .tool-intro-lead {
    margin-left: auto;
    margin-right: auto;
}

/* ⚠️ ONE INK CANNOT SERVE TWO THEMES. A white plate under white dark-theme ink
   is 1.00:1 — invisible. The plate follows the theme, and so does the ink on
   it, which is the --theme-surface-text lesson in another costume. */
html.wsp-has-page-bg body[data-theme="dark"] .tool-intro h1,
html.wsp-has-page-bg body[data-theme="dark"] .tool-intro .tool-intro-sub,
html.wsp-has-page-bg body[data-theme="dark"] .tool-intro .tool-intro-lead,
html.wsp-has-page-bg body[data-theme="dark"] .wheel-card-mobile .card-title > .flex-grow-1,
html.wsp-has-page-bg body[data-theme="high-contrast"] .tool-intro h1,
html.wsp-has-page-bg body[data-theme="high-contrast"] .tool-intro .tool-intro-sub,
html.wsp-has-page-bg body[data-theme="high-contrast"] .tool-intro .tool-intro-lead,
html.wsp-has-page-bg body[data-theme="high-contrast"] .wheel-card-mobile .card-title > .flex-grow-1 {
    background: rgba(14, 14, 18, .72);
    /* ⛔ !important, AND IT WAS MISSING. Measured with it absent: the h1's
       ink was rgb(129,140,248) and the subtitle's rgb(156,163,175) — the
       theme accents, not this value — because themes.css sets headings and
       .tool-intro-sub with !important. The plate was dark and the ink was
       mid-indigo on top of it: 2.63:1. A declaration that loses is not a
       decision, it is a comment (SKILLS 121). */
    color: #f4f5fa !important;
}

/* ── 4. fullscreen ─────────────────────────────────────────────────────────── */

/* ⛔ THE BUG THE OWNER FOUND, AND WHY IT WAS INVISIBLE TO THE CHECK.
   Measured: the photo covered 51.2% of the screen normally and 0.0% in
   fullscreen. A bisect cleared the mask and the positioning — only the stacking
   mattered — and listing everything painting over the viewport named it:

       100%  div.row.g-4   z=1000  pos=fixed  background-image: linear-gradient(...)

   In zen the stage row becomes a fixed, full-viewport layer that paints the page
   gradient so the stage has a ground of its own. The photo was sitting at
   z-index 0 underneath it the whole time. Nothing was wrong with the layer.

   ⚠️ The check never saw this because it never entered fullscreen — a check only
   covers the state it runs in (SKILLS 23). wheel-image-check now enters real
   fullscreen and measures the painted pixels. */
/* ⛔⛔ THE ZEN ROW NEEDS *BOTH* HALVES, AND I REMOVED THIS ONE TOO EARLY.
   Entering zen used to write
       background: linear-gradient(...) !important
   onto .main-layout-row as an INLINE style from _applyZenBg() in
   deferred-scripts.php. An inline important declaration outranks every author
   stylesheet at any specificity — measured: a <style> tag with this exact
   selector changed nothing (0%), the same declaration set inline worked
   (62.2%). So a JS guard there now leaves the row alone when a page background
   is set.

   ⚠️ AND THAT IS ONLY HALF. With the inline gone the row falls back to
   themes.css:
       body.zen-mode .main-layout-row { background: var(--theme-bg, #fff) !important }
   where --theme-bg is unset, so the #fff FALLBACK paints an opaque white plate
   over the photo (SKILLS 115 — a var that does not exist fails as a silent
   fallback). This rule is what beats that, and it can only win now that the
   inline is out of the way. Remove either half and the photo disappears in
   fullscreen again. */
html.wsp-has-page-bg body.zen-mode .main-layout-row {
    background: transparent !important;
}

/* And pin the layer to the fullscreen viewport rather than to the document.
   In zen the document is exactly one viewport tall and does not scroll, so the
   fade-out has nothing left to fade into — it would just clip the picture. */
html.wsp-has-page-bg body.zen-mode::before {
    position: fixed;
    inset: 0;
    height: auto;
    -webkit-mask-image: none;
    mask-image: none;
}

/* ── 5. the upload control ─────────────────────────────────────────────────── */

/* "Clear" only exists once there is something to clear — same rule the wheel
   background and the centre logo already follow. */
#clearPageBgImage[hidden] {
    display: none !important;
}

/* ── THE BAND BEHIND THE TITLE (owner, 2026-09-02) ────────────────────────── */
/* ⛔ NOT THE PLATE — a separate defect, and the one his screenshots actually
   showed. Walking up from the h1 with a page background set:

     light   nothing above the h1 paints - the photo shows through
     dark    section.tool-intro.tool-intro-head  rgb(18,18,18)  961x88
             section.tool-intro (the lead)       rgb(18,18,18)  820x156

   The runtime named the cause rather than a grep (SKILLS 94):

       body[data-theme] section { background: var(--theme-bg) !important }

   - a BARE ELEMENT selector with !important, hitting every <section> on every
   themed page. Light escapes it only because light REMOVES the data-theme
   attribute, which is exactly why light looked right and dark did not.

   ⚠️ SAME BUG AS B18b, one element along: there .content-wrapper painted over
   the photo in dark and high-contrast while light was transparent. `section`
   is a common word, not a namespace (SKILLS 110).

   ⚠️ BOTH themed renderings, not just dark. The band is a bug wherever it
   appears - it hides the photo the user uploaded - and it is the same rule in
   both. The PLATE stays per-theme; only this band is shared.
   ⚠️ SCOPED TO .tool-intro, NOT all sections: the photo covers the TOOL AREA
   only and the SEO article below keeps the page ground by design.
   ⚠️ (0,3,1) + !important, to out-specify an !important rule. */
html.wsp-has-page-bg body[data-theme] section.tool-intro,
html.wsp-has-page-bg body[data-theme] section.tool-intro-head {
    background: transparent !important;
}
