/* wgf-iconbg.css - THE icon-mosaic background layer (owner mandate 2026-08-23).

   Owner's word: "We should ALWAYS be loading a full background for our users, and it
   shouldn't affect the speed of the website. I want Blizzard assets on the background of
   every page with no excuse... if I'm on the Druid page, I should see all the Druid spells
   as like a grid in the background of it."

   Resolution lives in wgf_iconbg.py; the tiles are composed offline by
   tools/build_iconbg.py. The page contributes exactly ONE attribute - data-wgf-bg="<family>"
   on <body> - and this sheet does the rest. No wrapper div, no JS, no inline style.

   WHY ::before AND NOT A DIV
   --------------------------
   assets/wgf-zonebg.css carries two documented landmines: a blanket `.wgf-zonebg-wrap > *`
   lift that silently overrode the topbar's z-index (menus sheared off), and a :not() that
   raised specificity enough to knock `position: fixed` off the backdrop and blank the art on
   every page site-wide. Both are consequences of restructuring the DOM to get a layer behind
   the content. A ::before on the body needs no restructuring at all, so it cannot repeat
   either bug, and it composes cleanly UNDER .wgf-zonebg on the pages that have both.

   THE STACKING CONTRACT (the one subtle part - read before editing)
   ----------------------------------------------------------------
   A z-index:-1 pseudo-element paints above its stacking context's own background and below
   that context's in-flow content. But <body> does not create a stacking context by default,
   so without the `isolation: isolate` below the layer would join the ROOT context and be
   painted UNDER body's opaque canvas gradient - invisible. That is precisely the trap
   wgf-zonebg.css documents ("It is NOT z-index:-1 - the SEO shell paints an opaque body
   background that would hide a negative layer"). `isolation` is used rather than
   `position: relative; z-index: 0` on purpose: it creates the stacking context WITHOUT
   creating a containing block, so every `position: fixed` descendant (topbar, dropdowns,
   modals, ad rails) keeps behaving exactly as it does today. The idiom is already in use
   here - assets/wgf-account.css:434, assets/wgf-dressing-room.css:77, wgf-spellbook.css:55.

   PALETTE LAW (docs/ui-ux/15-ithildin-reskin.md)
   ---------------------------------------------
   This sheet defines NO colour of its own beyond neutral black alpha. It reads no --wgf-*
   token and overrides none. The tile's mortar is TRANSPARENT, so the grout between the icons
   is literally the body's own Ithildin canvas - the palette is inherited, never duplicated.
   The scrim is pure rgba(0,0,0,a): dark-NEUTRAL, never tinted, exactly as the reskin spec
   requires for art overlays. The two custom properties are component-local (--ib-*), not
   global tokens (DESIGN_SYSTEM.md rule 1).

   PERFORMANCE
   -----------
   * One image per FAMILY, shared by every page of that type - so the 2nd..Nth page of a
     type costs zero bytes. Tiles are content-hashed and served 1y-immutable.
   * A CSS background on a decorative pseudo-element is fetched at LOW priority after
     render-blocking resources, so it can neither block first paint nor delay LCP.
   * position: fixed rather than background-attachment: fixed - the latter forces a repaint
     of the whole layer on every scroll frame on mobile. This one is composited.
   * The pseudo-element is out of flow and has no intrinsic size, so it can never cause CLS.
*/

body[data-wgf-bg] {
  /* See THE STACKING CONTRACT above. Scoped to the attribute so a page without a mosaic is
     behaviourally byte-identical to before this sheet existed. */
  isolation: isolate;
}

body[data-wgf-bg]::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  /* Layer order: scrim on top, mosaic underneath. --ib-tile is supplied per family by the
     generated region at the bottom of this file; --ib-scrim is the single shared recipe. */
  --ib-scrim:
    radial-gradient(120% 78% at 50% 0%, rgba(0, 0, 0, 0.86), rgba(0, 0, 0, 0.42) 42%, rgba(0, 0, 0, 0.30) 68%),
    linear-gradient(180deg, rgba(0, 0, 0, 0.30) 0%, rgba(0, 0, 0, 0.18) 40%, rgba(0, 0, 0, 0.62) 100%);
  background-image: var(--ib-scrim), var(--ib-tile, none);
  background-repeat: no-repeat, repeat;
  background-position: center, center;
  background-size: cover, auto;
  /* The tiles are already desaturated + darkened at composition time (tools/build_iconbg.py);
     this is the final trim, and the SINGLE knob to turn if the owner wants the art louder or
     quieter.

     Set from a real measurement, not a guess. Headless Chrome rendered /guides/ at 1440x900
     at five opacities; the brightest actual background pixel in the canvas margins (scrollbar
     gutter excluded) and its contrast against every Ithildin text token that can land on the
     canvas:

        opacity   brightest bg     text   brand   muted   neutral(#93a098, the binding one)
          0.50     ( 27, 30, 28)  14.40   10.62    7.90   6.18
          0.65     ( 30, 32, 32)  14.02   10.34    7.69   6.02
          0.80     ( 34, 35, 35)  13.50    9.95    7.40   5.79
          0.90     ( 36, 37, 38)  13.15    9.70    7.21   5.65
          1.00     ( 39, 39, 40)  12.78    9.42    7.01   5.49

     Every step passes WCAG AA body (4.5) with room, because the ::before carries the scrim
     as well as the tile. 0.85 is chosen as the point where the icon grid is unmistakably
     readable as Blizzard art (the owner's whole ask) while still leaving the knob usable in
     both directions - roughly 5.7 on the binding token, ~27% clear of AA.

     `py -3 tools/build_iconbg.py --contrast` is the offline gate on the same question. It
     reads this exact opacity AND the weakest stop of the scrim below back out of this file,
     then composites the brightest pixel of all 55 tiles over the canvas - a worse case than
     any real page, so it always reports lower than the browser. Re-run it after touching
     this value; it currently reports 5.08 on the binding token vs the browser's ~5.7. */
  opacity: 0.85;
}

/* ── THE ONE PLACE THE MOSAIC OUTRANKS THE PHOTO: class surfaces ──────────────────────────
   Owner, 2026-08-23, verbatim: "if I'm on the Druid page, I should see all the Druid spells
   as like a grid in the background of it."

   /class/, /spec/, /talent/ and /talent-tree/ already resolve a photographic backdrop through
   wgf_zonebg's _CLASS_ZONE map (druid -> Val'sharah). That map is a curated GUESS about where
   a class hangs out; the mosaic is the class's actual talent-tree spell set straight out of
   datamine.talent_tree_cache. On these four surfaces the owner asked for the second one, so
   the photo layer and the wallpaper strip that advertises it both stand down.

   Deliberately done HERE, in this sheet, and nowhere else: class_app.py is untouched, so this
   is one selector to delete if the owner prefers the zone art back. Hiding the layer with
   `display: none` also means the browser never fetches that loading screen, so this costs
   nothing - it trades one image for another rather than stacking them.

   EVERYWHERE ELSE this engine yields: routes whose detail pages already resolve a photo are
   simply not in wgf_iconbg._ROUTES at all. */
body[data-wgf-bg^="class-"] .wgf-zonebg,
body[data-wgf-bg^="class-"] .wgf-wallpaper {
  display: none;
}

/* A reader who has asked the OS for more contrast gets the art out of the way entirely
   rather than a compromise. */
@media (prefers-contrast: more) {
  body[data-wgf-bg]::before { opacity: 0.3; }
}

/* Narrow viewports: less canvas shows around the content column, and the mosaic competes
   with the reading measure much sooner. Ease it back rather than dropping it - the owner
   wants a real background on every page, phones included. */
@media (max-width: 720px) {
  body[data-wgf-bg]::before { opacity: 0.65; }
}

@media print {
  body[data-wgf-bg]::before { display: none; }
}

/* === GENERATED: family tiles (tools/build_iconbg.py) - do not hand-edit === */
body[data-wgf-bg="achievement"] { --ib-tile: url("/assets/bg/iconbg-achievement-c79a476d91.webp"); }
body[data-wgf-bg="armor"] { --ib-tile: url("/assets/bg/iconbg-armor-452a8b3fb4.webp"); }
body[data-wgf-bg="class-death-knight"] { --ib-tile: url("/assets/bg/iconbg-class-death-knight-a548946669.webp"); }
body[data-wgf-bg="class-demon-hunter"] { --ib-tile: url("/assets/bg/iconbg-class-demon-hunter-af48dccd5c.webp"); }
body[data-wgf-bg="class-druid"] { --ib-tile: url("/assets/bg/iconbg-class-druid-aefaa72b43.webp"); }
body[data-wgf-bg="class-evoker"] { --ib-tile: url("/assets/bg/iconbg-class-evoker-080e462164.webp"); }
body[data-wgf-bg="class-hub"] { --ib-tile: url("/assets/bg/iconbg-class-hub-9dba186653.webp"); }
body[data-wgf-bg="class-hunter"] { --ib-tile: url("/assets/bg/iconbg-class-hunter-72b787fef5.webp"); }
body[data-wgf-bg="class-mage"] { --ib-tile: url("/assets/bg/iconbg-class-mage-9d97decb97.webp"); }
body[data-wgf-bg="class-monk"] { --ib-tile: url("/assets/bg/iconbg-class-monk-a2ac80457d.webp"); }
body[data-wgf-bg="class-paladin"] { --ib-tile: url("/assets/bg/iconbg-class-paladin-a1659b1a84.webp"); }
body[data-wgf-bg="class-priest"] { --ib-tile: url("/assets/bg/iconbg-class-priest-dcb9b14282.webp"); }
body[data-wgf-bg="class-rogue"] { --ib-tile: url("/assets/bg/iconbg-class-rogue-324792300e.webp"); }
body[data-wgf-bg="class-shaman"] { --ib-tile: url("/assets/bg/iconbg-class-shaman-e1d5d35396.webp"); }
body[data-wgf-bg="class-warlock"] { --ib-tile: url("/assets/bg/iconbg-class-warlock-807d3da6fa.webp"); }
body[data-wgf-bg="class-warrior"] { --ib-tile: url("/assets/bg/iconbg-class-warrior-692324ae0e.webp"); }
body[data-wgf-bg="creature"] { --ib-tile: url("/assets/bg/iconbg-creature-eadf6c7eea.webp"); }
body[data-wgf-bg="currency"] { --ib-tile: url("/assets/bg/iconbg-currency-f6c15d526b.webp"); }
body[data-wgf-bg="default"] { --ib-tile: url("/assets/bg/iconbg-default-a29259e21e.webp"); }
body[data-wgf-bg="dungeon"] { --ib-tile: url("/assets/bg/iconbg-dungeon-6e2d81070a.webp"); }
body[data-wgf-bg="economy"] { --ib-tile: url("/assets/bg/iconbg-economy-555a145ff6.webp"); }
body[data-wgf-bg="enchant"] { --ib-tile: url("/assets/bg/iconbg-enchant-623443f613.webp"); }
body[data-wgf-bg="event"] { --ib-tile: url("/assets/bg/iconbg-event-02ec55f9ef.webp"); }
body[data-wgf-bg="expansion"] { --ib-tile: url("/assets/bg/iconbg-expansion-243cdfe1d6.webp"); }
body[data-wgf-bg="faction"] { --ib-tile: url("/assets/bg/iconbg-faction-57b563dc06.webp"); }
body[data-wgf-bg="gem"] { --ib-tile: url("/assets/bg/iconbg-gem-0a72098e0a.webp"); }
body[data-wgf-bg="guide"] { --ib-tile: url("/assets/bg/iconbg-guide-163b62bafd.webp"); }
body[data-wgf-bg="heirloom"] { --ib-tile: url("/assets/bg/iconbg-heirloom-c76bbcae87.webp"); }
body[data-wgf-bg="housing"] { --ib-tile: url("/assets/bg/iconbg-housing-3cf9f1540e.webp"); }
body[data-wgf-bg="item"] { --ib-tile: url("/assets/bg/iconbg-item-7d9ae6118d.webp"); }
body[data-wgf-bg="mount"] { --ib-tile: url("/assets/bg/iconbg-mount-bffb34f21a.webp"); }
body[data-wgf-bg="object"] { --ib-tile: url("/assets/bg/iconbg-object-691a0bc6c8.webp"); }
body[data-wgf-bg="pet"] { --ib-tile: url("/assets/bg/iconbg-pet-651bc730d2.webp"); }
body[data-wgf-bg="profession"] { --ib-tile: url("/assets/bg/iconbg-profession-fd6ef552bb.webp"); }
body[data-wgf-bg="profession-alchemy"] { --ib-tile: url("/assets/bg/iconbg-profession-alchemy-660868c508.webp"); }
body[data-wgf-bg="profession-archaeology"] { --ib-tile: url("/assets/bg/iconbg-profession-archaeology-990227bdff.webp"); }
body[data-wgf-bg="profession-blacksmithing"] { --ib-tile: url("/assets/bg/iconbg-profession-blacksmithing-363d2620b6.webp"); }
body[data-wgf-bg="profession-cooking"] { --ib-tile: url("/assets/bg/iconbg-profession-cooking-55d0661b50.webp"); }
body[data-wgf-bg="profession-enchanting"] { --ib-tile: url("/assets/bg/iconbg-profession-enchanting-e26a6523a9.webp"); }
body[data-wgf-bg="profession-engineering"] { --ib-tile: url("/assets/bg/iconbg-profession-engineering-9317adad4c.webp"); }
body[data-wgf-bg="profession-fishing"] { --ib-tile: url("/assets/bg/iconbg-profession-fishing-228ff88228.webp"); }
body[data-wgf-bg="profession-herbalism"] { --ib-tile: url("/assets/bg/iconbg-profession-herbalism-4df4204535.webp"); }
body[data-wgf-bg="profession-inscription"] { --ib-tile: url("/assets/bg/iconbg-profession-inscription-e5a17a4658.webp"); }
body[data-wgf-bg="profession-jewelcrafting"] { --ib-tile: url("/assets/bg/iconbg-profession-jewelcrafting-70b96b595d.webp"); }
body[data-wgf-bg="profession-leatherworking"] { --ib-tile: url("/assets/bg/iconbg-profession-leatherworking-c7eb31a4c7.webp"); }
body[data-wgf-bg="profession-mining"] { --ib-tile: url("/assets/bg/iconbg-profession-mining-637f6368d9.webp"); }
body[data-wgf-bg="profession-skinning"] { --ib-tile: url("/assets/bg/iconbg-profession-skinning-2ebc374366.webp"); }
body[data-wgf-bg="profession-tailoring"] { --ib-tile: url("/assets/bg/iconbg-profession-tailoring-acc2bda3c4.webp"); }
body[data-wgf-bg="quest"] { --ib-tile: url("/assets/bg/iconbg-quest-33e1e7822e.webp"); }
body[data-wgf-bg="race"] { --ib-tile: url("/assets/bg/iconbg-race-845190911b.webp"); }
body[data-wgf-bg="recipe"] { --ib-tile: url("/assets/bg/iconbg-recipe-aae2145579.webp"); }
body[data-wgf-bg="spell"] { --ib-tile: url("/assets/bg/iconbg-spell-48f26bb01d.webp"); }
body[data-wgf-bg="title"] { --ib-tile: url("/assets/bg/iconbg-title-40a6d5caa0.webp"); }
body[data-wgf-bg="toy"] { --ib-tile: url("/assets/bg/iconbg-toy-712719d306.webp"); }
body[data-wgf-bg="zone"] { --ib-tile: url("/assets/bg/iconbg-zone-f2dbe64a0a.webp"); }
/* === END GENERATED === */
