*{box-sizing:border-box;margin:0;padding:0}
/* Do NOT put overflow-x:hidden on html/body at desktop — it breaks position:sticky
   on .deck-viewport and the PC depth-deck sections disappear into empty scroll space.
   Horizontal clip is applied only in the phone media query below. */
html{scroll-behavior:smooth}
body{
  background:
    radial-gradient(ellipse 70% 55% at 15% 25%, rgba(107,95,239,.22) 0%, transparent 60%),
    radial-gradient(ellipse 60% 50% at 85% 35%, rgba(0,180,216,.16) 0%, transparent 60%),
    radial-gradient(ellipse 55% 50% at 50% 85%, rgba(224,85,155,.14) 0%, transparent 60%),
    #06070F;
  background-attachment:fixed;
  font-family:'Open Sans',Helvetica,Arial,sans-serif;color:#EEEEFF;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}
a{color:inherit;text-decoration:none}
:focus-visible{outline:2px solid var(--accent-2);outline-offset:3px;border-radius:6px;box-shadow:0 0 0 5px rgba(155,106,255,.25)}
/* follows the mouse via transform (rAF in JS) rather than left/top, so it never triggers layout.
   Native cursor stays visible — this is a halo riding alongside it, not a replacement (hiding
   the system cursor on an e-commerce page hurts usability). Desktop mouse users only. */
/* (custom cursor effect removed 2026-07 by request — the system cursor is back on duty) */

:root{
  --max:clamp(1280px, 92vw, 1800px);
  --ne-orange:#FF8500;
  /* NE design-system CTA tier (primary = NE Yellow, per NewggWebUI V2) */
  --ne-yellow:#FA9D28;
  --ne-yellow-hover:#FFB25D;
  --ne-yellow-active:#FF8500;
  --ne-ink:#212121;
  --border:#2D2D58;
  --border-2:#484880;
  --text:#EEEEFF;
  --accent:#6B5FEF;
  --accent-2:#9B6AFF;
  /* FAQ open/hover question text: #9B6AFF is 4.44:1 on the panel's brightest pixel — 0.06 short of AA */
  --accent-2-hi:#B48CFF;
  --c-shop:#FF8500;
  --c-build:#6B5FEF;
  --c-comm:#0070CC;
  --font-h:'Montserrat','Russo One',sans-serif; /* headings — matches Newegg's NewggWebUI V2 design system */
  --font-b:'Open Sans','Chakra Petch',sans-serif; /* body/UI — matches Newegg's NewggWebUI V2 design system */
  --font-ui:'Open Sans',Helvetica,Arial,sans-serif;
}

/* ===== GLOBAL INTERACTIVE CURSOR ===== */
button,a,[role=button],.hub-sc,.dotnav-item,.pc-prev,.pc-next,.pc-dot{cursor:pointer}
/* removes the default gray flash Android/iOS paint on tap for our custom-styled controls —
   :active below supplies real touch feedback instead, so touch users still get a response. */
button,a,[role=button]{-webkit-tap-highlight-color:transparent}
button:active,a:active,[role=button]:active{opacity:.8}

/* ===== NEWEGG CHROME-KIT SHELL =====
   Real header/footer come from injectheaderfooter.js (#app_header / #app_footer).
   Neutralize the site template's container constraints so our sections run full-bleed. */
#newProductPageContent,
#newProductPageContent .page-section,
#newProductPageContent .page-content-inner,
#newProductPageContent .page-content-root{max-width:none;width:100%;margin:0;padding:0;background:transparent}
#newProductPageContent .page-content-root{position:relative}

/* ===== BI TRAFFIC TABLE — aligned 1:1 with AI Hardware Hub bi-traffic-kit/bi-traffic-layout.css
   (body[data-bi-traffic-page] scoping; production AI HW uses body.ai-hardware-hub-page — same rules).
   Inlined so a missed CDN upload can't 404 the layout styles. Requires BiTrafficLayout.init(). */
#newProductPageContent.page-content{min-height:0;padding-top:0}
#main-content{scroll-margin-top:120px}
#newProductPageContent>.page-section>.page-content-inner{max-width:none}
#app_header.b2c-remote,#app_footer.b2c-remote{position:relative;z-index:100}
/* Restore the site's base font size for the INJECTED chrome only.
   Newegg pages load the full RemoteTemplate-<hash>.css, which sets
   html{font-size:62.5%} (=10px) and body,button,input,select,textarea{font-size:14px}.
   We load the SCOPED build instead (RemoteTemplateScoped + RemoteTemplateGlobal), and
   "scoped" means exactly this: it deliberately never touches the host page's html/body.
   So every header/footer element that has no font-size of its own inherits the browser
   default 16px instead of 14px -- 14.3% larger. Measured on 2026-08-26: 6 of the 27 text
   elements in the header were off (Skip to main content / Select address /
   Sign In / Register / & Orders / Cart / Menu), which widened Cart 83.8->88.1px and the
   account block 177x44->180x46. The bar's own height was identical (124.6px both), so it
   only ever LOOKED zoomed -- it was the text.
   Fixing this on html/body instead would drag our own typography down with it (measured:
   FAQ body 16->14px, hero title 32->28px), and nothing of ours needs it -- this file uses
   zero rem. So the declaration is scoped to the two injected roots. */
#app_header,#app_footer{font-size:14px}
/* QA: OneTrust's cookie-settings link ships color:#212121 (built for white pages). The footer's
   disclaimer bar has no background of its own, so OUR near-black body shows through it and the
   link measured 1.25:1 — effectively invisible. Scoped to that one injected element. */
#app_footer #ot-sdk-btn.ot-sdk-show-settings{color:#EEEEFF !important}
/* QA A-5 (mobile header): the injected subnav reserves a fixed 100px for the absolutely-
   positioned AI Mode chip, but the chip itself measures 108px+ (more when device font metrics
   run larger) — its tail printed over "Shell Shocker". Reserve real clearance, and let the link
   strip paint over any remaining tail (solid row-colored bg + z) so glyphs can never intermix.
   Scoped to the injected header at phone widths only. */
@media(max-width:799px){
  #app_header .header2021-inner.has-mode{padding-left:124px !important}
  #app_header .header2021-inner.has-mode .section-left{position:relative;z-index:1;background:rgb(238,245,255)}
}
body[data-bi-traffic-page] #app_header.b2c-remote{min-height:0 !important;height:auto !important;overflow:visible}
body[data-bi-traffic-page] #app_header.b2c-remote .biExtension{display:none !important;height:0 !important;min-height:0 !important;margin:0 !important;padding:0 !important;overflow:hidden !important;visibility:hidden !important;pointer-events:none !important}
/* Gaming Hub PC depth-deck uses .deck{z-index:1} + sticky 100vh — that stacking context
   paints over earlier siblings and steals clicks from Show (visible but not clickable).
   Mobile flowMode drops sticky, so clicks work there. Raise BI above the deck; keep
   pointer-events so the plugin button stays hittable. */
body[data-bi-traffic-page] #newProductPageContent>.biExtension{
  display:block;
  position:relative;
  z-index:120;
  width:100%;
  min-height:0;
  margin:0;
  pointer-events:auto;
}
body[data-bi-traffic-page] #newProductPageContent>.biExtension.biController,
body[data-bi-traffic-page] #newProductPageContent>.biExtension.biControllo{
  z-index:130;
  pointer-events:auto !important;
}
body[data-bi-traffic-page] #newProductPageContent>.page-section{
  position:relative;
  z-index:1;
}
body[data-bi-traffic-page] #newProductPageContent .biExtension.biController>div>span,
body[data-bi-traffic-page] #newProductPageContent .biExtension.biControllo>div>span,
body[data-bi-traffic-page] #newProductPageContent .biExtension.biController .biz_show_api_button,
body[data-bi-traffic-page] #newProductPageContent .biExtension.biControllo .biz_show_api_button{color:#fff !important}
/* Empty shells — exclude Show/Hide (.biController). Plugin often puts allpagetrafficContainer
   on the controller too; without :not(.biController) CSS alone hides Show after first paint. */
body[data-bi-traffic-page] #newProductPageContent>.biExtension.allpagetrafficContainer.storePagePerformance:not(.biController):not(.biControllo):not(:has(.grid-container > *)),
body[data-bi-traffic-page] #newProductPageContent>.biExtension.allpagetrafficContainer:not(.storePagePerformance):not(.biController):not(.biControllo):not(:has(.grid-container > *)),
body[data-bi-traffic-page] #newProductPageContent>.biExtension[data-ai-bi-collapsed="true"]:not(.biController):not(.biControllo){display:none !important;height:0 !important;min-height:0 !important;margin:0 !important;padding:0 !important;overflow:hidden !important}
body[data-bi-traffic-page] #newProductPageContent .storePagePerformance .grid-container:empty,
body[data-bi-traffic-page] #newProductPageContent .storePagePerformance .grid-container>:empty,
body[data-bi-traffic-page] #newProductPageContent .storePagePerformance .grid-col:empty{display:none !important;height:0 !important;min-height:0 !important;width:0 !important;min-width:0 !important;padding:0 !important;margin:0 !important;border:0 !important;overflow:hidden !important}
#ai-remote-header-spacer{display:none !important;height:0 !important}

/* ===== BREADCRUMB — match AI Hardware Hub (in-flow text, no separate nav bar) ===== */
.breadcrumb{
  position:relative;
  display:flex;
  align-items:center;
  gap:8px;
  padding:12px 48px;
  background:transparent;
  z-index:2;
  font-family:var(--font-ui);
}
.breadcrumb-item{
  font-size:15px;
  font-weight:500;
  line-height:24px;
  color:rgba(209,209,209,.92);
  text-decoration:none;
  white-space:nowrap;
  transition:color .18s;
}
.breadcrumb-item:hover{color:#fff}
.breadcrumb-active{color:#fff;font-weight:600;cursor:default}
.breadcrumb-sep{color:rgba(209,209,209,.55);font-size:14px;line-height:1;user-select:none}

/* ===== SCROLL PROGRESS BAR ===== */
.scroll-progress{position:fixed;top:0;left:0;height:3px;width:0;background:linear-gradient(90deg,var(--ne-orange),var(--accent-2));z-index:200;box-shadow:0 0 14px rgba(255,133,0,.55),0 0 28px rgba(107,95,239,.3)}

/* ===== HERO (cinematic, video-like) ===== */
.hero{position:relative;height:100vh;display:flex;align-items:stretch;overflow:hidden}
/* HP Opening video is the hero background; .hero-bg above it is now a pure legibility scrim
   (darker at the edges + bottom, lighter in the middle so the footage stays visible). If the
   video ever fails to load, the scrim over the page's dark base still reads fine. */
.hero-video{position:absolute;top:0;left:0;right:0;bottom:0;inset:0;z-index:0;width:100%;height:100%;object-fit:cover;object-position:center}
/* NO-FRAME FALLBACK (app A-8): when a webview refuses to paint video frames, the raw element is
   a black rectangle. .video-fallback (JS, state-driven) hides the element and paints a styled
   scene in the section's palette instead. Append ?nv=1 to the URL to review it anywhere. */
.hero.video-fallback .hero-video{display:none}
.hero.video-fallback{background:
  radial-gradient(ellipse 70% 60% at 24% 30%, rgba(107,95,239,.32), transparent 62%),
  radial-gradient(ellipse 60% 55% at 78% 24%, rgba(255,133,0,.20), transparent 60%),
  radial-gradient(ellipse 75% 70% at 55% 88%, rgba(0,112,204,.22), transparent 65%),
  linear-gradient(180deg,#0A0C1E 0%,#06070F 58%,#04050C 100%)}
.deck-video-layer.video-fallback video{display:none}
.deck-video-layer.video-fallback{background:
  radial-gradient(ellipse 80% 60% at 30% 20%, rgba(107,95,239,.25), transparent 60%),
  radial-gradient(ellipse 70% 55% at 75% 75%, rgba(255,133,0,.16), transparent 60%),
  linear-gradient(180deg,#080A18,#05060E)}
.hero-bg{position:absolute;top:0;left:0;right:0;bottom:0;inset:0;z-index:1;
  background:
    linear-gradient(180deg, rgba(6,7,15,.55) 0%, rgba(6,7,15,.30) 42%, rgba(6,7,15,.88) 100%),
    radial-gradient(ellipse 90% 80% at 50% 46%, transparent 40%, rgba(6,7,15,.45) 100%)}
/* animated moving grid — RETIRED 2026-07 (grid texture over the video removed by request;
   the .hero-grid div is gone from index.html — restore both to re-enable) */
@keyframes gridfloat{0%{background-position:0 0}100%{background-position:54px 54px}}
/* Cinema layout v2: title cluster (eyebrow + flip-board title + subtagline + callout) is
   bottom-anchored into the lower ~35% of the hero; the two glass carousel cards float at the
   upper sides. Everything is absolutely positioned over the video — the flex on .hero-inner
   is only a fallback shell. z: cards 4 < text 5, so at very short viewports the title wins. */
.hero-inner{position:relative;z-index:3;display:flex;align-items:center;justify-content:center;width:100%;height:100%;max-width:none;margin:0 auto;padding:0 48px}
/* title cluster: viewport-centered, lifted into the upper-middle. The card (below) is pinned
   to the SAME vertical band so it reads as related to the text, not stuck up in the corner. */
.hero-left{position:absolute;left:50%;top:42%;transform:translate(-50%,-50%);z-index:5;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:0}
/* soft gradient shadow behind the text cluster — anchors it off the video, adds depth */
.hero-left::before{content:"";position:absolute;z-index:-1;inset:-9% -15%;pointer-events:none;
  background:
    radial-gradient(ellipse 60% 56% at 50% 52%, rgba(4,5,14,.74), rgba(4,5,14,.36) 54%, transparent 78%),
    radial-gradient(ellipse 42% 48% at 50% 62%, rgba(107,95,239,.18), transparent 72%);
  filter:blur(8px)}
.hero-ambient{position:absolute;top:12%;bottom:12%;left:18%;right:18%;z-index:2;pointer-events:none;border-radius:50%;filter:blur(160px);opacity:.30;background:rgba(107,95,239,.38)}
/* terminal/screen texture — separate layer from .hero-grid (which already animates its own
   background-position for the gridfloat drift) so this can run its own independent scroll
   without dragging the grid along with it. Sits above the ambient glow, below hero-inner. */
.hero-scanlines{position:absolute;inset:0;z-index:2;pointer-events:none;opacity:.08;background-image:repeating-linear-gradient(rgba(255,255,255,.9) 0px,rgba(255,255,255,.9) 1px,transparent 1px,transparent 3px);background-size:100% 3px;animation:scanmove 6s linear infinite}
@keyframes scanmove{0%{background-position:0 0}100%{background-position:0 120px}}
@media(prefers-reduced-motion:reduce){.hero-scanlines{animation:none}}
/* HUD corner brackets — viewfinder-style depth cue in the hero's four corners */
.hero-brackets{position:absolute;inset:28px;z-index:4;pointer-events:none}
.hero-brackets i{position:absolute;width:22px;height:22px;border:1.5px solid rgba(255,255,255,.35);opacity:.8}
.hero-brackets i:nth-child(1){top:0;left:0;border-width:1.5px 0 0 1.5px}
.hero-brackets i:nth-child(2){top:0;right:0;border-width:1.5px 1.5px 0 0}
.hero-brackets i:nth-child(3){bottom:0;left:0;border-width:0 0 1.5px 1.5px}
.hero-brackets i:nth-child(4){bottom:0;right:0;border-width:0 1.5px 1.5px 0}
/* vertical est. mark, echoes the corner brackets */
.hero-rail{position:absolute;right:12px;top:50%;transform:translateY(-50%);writing-mode:vertical-rl;z-index:4;font-family:var(--font-b);font-size:10px;letter-spacing:.32em;color:rgba(255,255,255,.38);pointer-events:none}
/* boot flash — deliberately does NOT block the hero photo (which carries preload+fetchpriority
   for LCP) behind it; it's a small corner overlay that plays once per session and fades, not a
   full-screen splash. JS toggles .done to fade it out and skips it entirely on repeat visits
   (sessionStorage) or prefers-reduced-motion. */
.hud-boot{position:absolute;top:28px;left:28px;z-index:8;display:flex;align-items:center;gap:10px;font-family:var(--font-b);font-size:11px;font-weight:600;letter-spacing:.18em;color:var(--accent-2);text-transform:uppercase;opacity:0;animation:hudBootIn .3s ease-out forwards;pointer-events:none}
/* .done is added ~800ms after hudBootIn has already settled at opacity:1 and is just holding —
   giving .done a *different* animation (rather than reusing hudBootIn) would restart from that
   animation's own 0% keyframe and pop back to opacity:1 for a frame before fading, since adding
   the .done class fully replaces the applied `animation` (higher-specificity selector). Using
   the same 1->0 shape here, continuous with wherever hudBootIn left off, avoids that pop. */
.hud-boot.done{animation:hudBootOut .35s ease-in forwards}
.hud-boot-line{width:26px;height:1.5px;background:var(--accent-2);box-shadow:0 0 8px var(--accent-2)}
@keyframes hudBootIn{0%{opacity:0}100%{opacity:1}}
@keyframes hudBootOut{0%{opacity:1}100%{opacity:0}}
@media(prefers-reduced-motion:reduce){.hud-boot{display:none}}
/* The carousel hangs at the upper right as a hologram projection — small, floating on a slow
   bob. The title's width formula keeps the centered text out of this column at every width. */
@keyframes holoFloat{0%,100%{transform:translateY(0)}50%{transform:translateY(-11px)}}
/* card vertically centered on the text band (top:42% − ~half the card height) so it aligns
   with the tagline; right offset scales but stays off the extreme edge. */
.hero-card{position:absolute;top:calc(42% - 172px);right:clamp(36px,4vw,88px);z-index:4;width:clamp(248px,18vw,336px);height:clamp(310px,42vh,400px);overflow:visible;
  animation:holoFloat 7s ease-in-out infinite;will-change:transform}
@media(prefers-reduced-motion:reduce){.hero-card{animation:none}}
/* very short desktop windows: shrink the card, lift both a touch so nothing clips */
@media(min-width:1200px) and (max-height:768px){
  .hero-card{height:min(40vh,330px);top:calc(40% - 150px)}
  .hero-left{top:40%}
}
.hero-eyebrow{align-self:center;display:inline-flex;align-items:center;gap:9px;font-family:var(--font-b);font-size:12.5px;font-weight:600;letter-spacing:.2em;text-transform:uppercase;color:var(--accent-2);border:1px solid rgba(155,106,255,.35);background:rgba(107,95,239,.12);padding:9px 20px;border-radius:999px;margin-bottom:20px;backdrop-filter:blur(12px);box-shadow:0 0 24px rgba(107,95,239,.15),inset 0 1px 0 rgba(255,255,255,.07)}
@keyframes livepulse{0%,100%{box-shadow:0 0 6px var(--ne-orange),0 0 12px rgba(255,133,0,.4)}50%{box-shadow:0 0 10px var(--ne-orange),0 0 24px rgba(255,133,0,.7)}}
.hero-eyebrow .live{width:7px;height:7px;border-radius:50%;background:var(--ne-orange);animation:livepulse 2s ease-in-out infinite}
/* The h1 is a split-flap board with proportional cells, stacked three lines:
   EVERYTHING / GAMING. / ONE HUB. Longest line "EVERYTHING" ≈ 6.8em uppercase Montserrat 900;
   the calc() budgets that against the top-right hologram column so the title can run larger. */
/* Selector is the CLASS, not h1, on purpose: the split-flap board lives on a sibling <div>
   and the real <h1> is sr-only. aria-hidden hides the 100 tile letters from the a11y tree but
   NOT from DOM text — Google renders JS, so while the board was inside the <h1> the crawler
   read a 148-char heading ('Everything Gaming. One Hub.' + 96 chars of repeated tile letters)
   instead of the intended 27. Moving the board out is what fixes that; this class carries the
   visual styling across. Keep all four breakpoint overrides on .hero-title too. */
.hero .hero-title{font-family:var(--font-h);font-weight:900;font-size:clamp(44px,6.9vw,120px);line-height:1;letter-spacing:0;color:#fff;margin-bottom:22px}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}

/* ===== TEXT FLIPPING BOARD (split-flap glyphs — built by text-flipping-board.js) =====
   Chromeless variant: no tile card backgrounds/seams — just the characters themselves
   split-flipping, so at rest the title reads as plain type. Cells stay fixed-width (that's
   what makes the halves align through the flip); glyphs are sized to never clip at the cell
   edge since there's no dark tile to hide overflow anymore. */
/* The board is built by JS after document.fonts.ready, so its height jumped from ~0 to 3 rows
   once Montserrat landed — that pushed the whole hero column down and was the largest single
   contributor to CLS (measured 0.118 overall). Reserving 3 rows' worth of em height up front
   keeps the layout still; the tiles just fill space that was already there. */
.flip-board{display:flex;flex-direction:column;gap:.01em;align-items:center;line-height:1;min-height:3.4em}
.flip-line{display:flex;gap:0;justify-content:center}
/* "ONE HUB." (third line) steps up — em-based tiles scale with the line's font-size */
.flip-line:nth-child(3){font-size:1.32em;margin-top:-.04em}
/* cell width is set per character by the JS (proportional mode: each cell = its glyph's
   natural advance), so the line tracks like normal type; .95em is only the pre-JS fallback */
.flip-tile{position:relative;width:.95em;height:1.1em;perspective:3.2em;transform-style:preserve-3d}
.flip-tile .fh{position:absolute;left:0;width:100%;height:50%;overflow:hidden;background:transparent;backface-visibility:hidden}
.flip-tile .fh.ft{top:0}
.flip-tile .fh.fb{bottom:0}
.flip-tile .fh i{position:absolute;left:0;width:100%;height:200%;font-style:normal;
  font-family:var(--font-h);font-weight:900;font-size:1em;
  display:flex;align-items:center;justify-content:center;
  color:var(--tile-ink,#fff);text-shadow:0 .04em .34em rgba(0,0,0,.5)}
.flip-tile .fh.ft i{top:0}
.flip-tile .fh.fb i{top:-100%}
/* flaps sit above the static under-faces; bottom flap rests folded away (edge-on) */
.flip-tile .flap-t{z-index:2;transform-origin:50% 100%}
.flip-tile .flap-b{z-index:2;transform-origin:50% 0%;transform:rotateX(90deg)}
.flip-tile.go .flap-t{animation:flapDown var(--flap-ms,130ms) ease-in both}
.flip-tile.go .flap-b{animation:flapUp var(--flap-ms,130ms) ease-out both;
  animation-delay:calc(var(--flap-ms,130ms)*.55)}
@keyframes flapDown{from{transform:rotateX(0);filter:brightness(1)}to{transform:rotateX(-90deg);filter:brightness(.45)}}
@keyframes flapUp{from{transform:rotateX(90deg);filter:brightness(.45)}to{transform:rotateX(0);filter:brightness(1)}}
@media(prefers-reduced-motion:reduce){
  .flip-tile.go .flap-t,.flip-tile.go .flap-b{animation:none}
}
.hero .hero-title .grad{background:linear-gradient(120deg,#FF8500 0%,#9B6AFF 50%,#F43F5E 100%);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;filter:drop-shadow(0 0 28px rgba(155,106,255,.4))}
.hero p{font-family:var(--font-b);font-size:clamp(17px,1.6vw,20px);color:rgba(224,224,246,.96);max-width:min(880px,72vw);margin:0 auto 14px;line-height:1.6;font-weight:400;text-shadow:0 2px 18px rgba(0,0,0,.6)}
.hero-ctas{display:flex;gap:14px;justify-content:flex-start;flex-wrap:wrap;margin-bottom:28px}
/* rotates real static selling points (same copy pool as .ticker-lane) — deliberately not a
   "live visitor count" or any other fabricated real-time metric. */
.hero-callout{display:flex;align-items:center;justify-content:center;gap:10px;font-family:var(--font-b);font-size:13px;font-weight:600;letter-spacing:.03em;color:rgba(216,216,240,.85);margin-bottom:0;min-height:18px}
.hero-callout-dot{width:6px;height:6px;border-radius:50%;background:var(--ne-orange);box-shadow:0 0 8px var(--ne-orange);flex-shrink:0;animation:livepulse 2s ease-in-out infinite}
.hero-callout-text{transition:opacity .3s ease}
.hero-callout-text.swap{opacity:0}
@keyframes ctapulse{0%,100%{box-shadow:0 6px 28px rgba(107,95,239,.5),0 0 0 0 rgba(155,106,255,.0)}60%{box-shadow:0 12px 48px rgba(107,95,239,.95),0 0 80px rgba(107,95,239,.25)}}
.btn-primary{height:54px;padding:0 32px;background:linear-gradient(130deg,#5A4FD9,#9B6AFF,#7B5EEF);background-size:200% 100%;border:none;border-radius:999px;font-family:var(--font-b);font-size:15px;font-weight:700;letter-spacing:.04em;color:#fff;cursor:pointer;display:flex;align-items:center;gap:9px;box-shadow:0 6px 28px rgba(107,95,239,.5),inset 0 1px 0 rgba(255,255,255,.2);transition:transform .2s cubic-bezier(.2,.7,.2,1),box-shadow .2s,background-position .4s;animation:ctapulse 3.5s ease-in-out infinite}
.btn-primary:hover{transform:translateY(-3px) scale(1.02);box-shadow:0 16px 48px rgba(107,95,239,.9),0 0 80px rgba(107,95,239,.3);animation:none;background-position:100% 0}
.btn-primary:active{transform:translateY(-1px) scale(0.99);transition-duration:.08s}
.btn-ghost{height:54px;padding:0 28px;background:rgba(14,14,36,.6);border:1.5px solid rgba(107,95,239,.35);border-radius:999px;font-family:var(--font-b);font-size:15px;font-weight:600;letter-spacing:.03em;color:rgba(255,255,255,.85);cursor:pointer;display:flex;align-items:center;gap:9px;backdrop-filter:blur(12px);transition:all .2s cubic-bezier(.2,.7,.2,1)}
.btn-ghost:hover{border-color:var(--accent-2);background:rgba(107,95,239,.2);color:#fff;transform:translateY(-2px);box-shadow:0 8px 28px rgba(107,95,239,.25)}
/* scroll cue */
/* horizontally centered on the viewport. Above ~1400px the left column's content sits far
   enough left that page-center is clear of it, so the cue coexists with the hero content.
   Below that the left column reaches page-center and would overlap this cue, so it's hidden
   at those widths (see media query below) — it's a decorative hint, safe to drop there. */
/* hidden since the 2026-07 bottom-anchored title redesign — the cue's bottom-center spot now
   belongs to the title cluster. Rule kept for easy re-enable. */
.scroll-cue{display:none;position:absolute;bottom:56px;left:50%;transform:translateX(-50%);z-index:6;flex-direction:column;align-items:center;gap:10px;color:rgba(214,206,255,.95);font-family:var(--font-b);font-size:10.5px;letter-spacing:.22em;text-transform:uppercase;font-weight:600;text-shadow:0 1px 10px rgba(155,106,255,.5)}
@media(max-width:1400px){.scroll-cue{display:none}}
.scroll-mouse{width:25px;height:40px;border:1.5px solid rgba(155,106,255,.85);border-radius:13px;position:relative;box-shadow:0 0 15px rgba(155,106,255,.45),inset 0 8px rgba(155,106,255,.2)}
.scroll-mouse::after{content:"";position:absolute;top:7px;left:50%;transform:translateX(-50%);width:3.5px;height:7px;border-radius:2px;background:var(--accent-2);box-shadow:0 0 8px var(--accent-2);animation:scrolldot 1.8s ease-in-out infinite}
@keyframes scrolldot{0%{opacity:0;top:7px}40%{opacity:.9}80%{opacity:0;top:19px}100%{opacity:0}}

/* ===== SCROLL-REVEAL MODULES ===== */
/* symmetric padding reserves the same clearance on both sides as the fixed .dotnav needs on
   the right (right:36px + label ~190px total) — keeping it symmetric is what makes
   .module-inner's margin:auto centering land on the true viewport centre instead of drifting
   left, which is what an asymmetric 32/190 split did. */
.module{position:relative;min-height:92vh;display:flex;align-items:center;padding:80px 190px;overflow:hidden}
.module-glow{position:absolute;width:620px;height:620px;border-radius:50%;filter:blur(120px);opacity:.32;z-index:0}
.module.shop .module-glow{background:var(--c-shop);top:-10%;right:-8%}
.module.build .module-glow{background:var(--c-build);top:10%;left:-10%}
.module.community .module-glow{background:var(--c-comm);bottom:-10%;right:-6%}
.module-inner{position:relative;z-index:2;max-width:var(--max);margin:0 auto;width:100%;display:flex;align-items:center;justify-content:center;gap:48px}
.module-text{max-width:820px;flex:0 1 58%}
.module.alt .module-inner{flex-direction:row-reverse}
.module-text{background:linear-gradient(145deg,rgba(14,14,36,.82),rgba(8,9,22,.45));padding:32px 34px 34px;border-radius:24px;border:1px solid rgba(255,255,255,.09);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);border-top-width:2px;box-shadow:0 24px 64px rgba(0,0,0,.35),inset 0 1px 0 rgba(255,255,255,.06)}
.module.alt .module-text{background:linear-gradient(225deg,rgba(14,14,36,.82),rgba(8,9,22,.45))}
.module.shop .module-text{border-top-color:rgba(255,133,0,.7);box-shadow:0 24px 64px rgba(0,0,0,.35),0 0 40px rgba(255,133,0,.06),inset 0 1px 0 rgba(255,255,255,.06)}
.module.build .module-text{border-top-color:rgba(107,95,239,.8);box-shadow:0 24px 64px rgba(0,0,0,.35),0 0 40px rgba(107,95,239,.08),inset 0 1px 0 rgba(255,255,255,.06)}
.module.community .module-text{border-top-color:rgba(0,112,204,.7);box-shadow:0 24px 64px rgba(0,0,0,.35),0 0 40px rgba(0,112,204,.06),inset 0 1px 0 rgba(255,255,255,.06)}
/* text side */
.module-index{font-family:var(--font-b);font-weight:700;font-size:14.5px;letter-spacing:.18em;margin-bottom:22px;display:flex;align-items:center;gap:12px;text-transform:uppercase}
.module-index .bar{width:36px;height:2px;border-radius:1px;opacity:.8}
.module.shop .module-index{color:var(--c-shop)} .module.shop .bar{background:var(--c-shop)}
.module.build .module-index{color:var(--accent-2)} .module.build .bar{background:var(--accent-2)}
.module.community .module-index{color:var(--c-comm)} .module.community .bar{background:var(--c-comm)}
.module-icon{width:56px;height:56px;border-radius:16px;display:grid;place-items:center;margin-bottom:24px;border:1px solid rgba(255,255,255,.12);background:rgba(20,20,50,.7);backdrop-filter:blur(8px);box-shadow:0 4px 16px rgba(0,0,0,.3),inset 0 1px 0 rgba(255,255,255,.08)}
.module.shop .module-icon{box-shadow:0 4px 16px rgba(255,133,0,.2),inset 0 1px 0 rgba(255,255,255,.08)}
.module.build .module-icon{box-shadow:0 4px 16px rgba(107,95,239,.25),inset 0 1px 0 rgba(255,255,255,.08)}
.module.community .module-icon{box-shadow:0 4px 16px rgba(0,112,204,.2),inset 0 1px 0 rgba(255,255,255,.08)}
.module h2,.module h3{font-family:var(--font-h);font-weight:800;font-size:clamp(32px,4vw,50px);line-height:1.08;letter-spacing:-.02em;color:#fff;margin-bottom:16px;text-shadow:0 2px 20px rgba(0,0,0,.4);text-wrap:balance}
.module .desc{font-size:17.5px;color:rgba(212,212,235,.93);line-height:1.65;margin-bottom:24px;max-width:480px;font-weight:400}
.module-feats{display:flex;flex-direction:column;gap:12px;margin-bottom:32px}
.feat{display:flex;align-items:center;gap:11px;font-family:var(--font-b);font-size:17px;color:rgba(232,232,250,.96);font-weight:500;line-height:1.45}
.feat .dot{width:6px;height:6px;border-radius:50%;flex-shrink:0;opacity:.9}
.module.shop .feat .dot{background:var(--c-shop)} .module.build .feat .dot{background:var(--accent-2)}
.module.community .feat .dot{background:var(--c-comm)}
/* cut-corner CTAs: clip-path clips the element's own box-shadow along with it, so the outer
   glow has to live on an unclipped wrapper instead (filter:drop-shadow isn't clipped). Inset
   highlights stay on the button itself — being clipped to the new corner shape is what we want
   there. The wrapper's :hover mirrors the button's :hover 1:1 (hovering the button always
   hovers its wrapper, since the wrapper has no extra padding around it), so hover-intensified
   glow doesn't need JS or :has(). */
/* position+z-index (not just display) are required here: filter (used below for the glow)
   creates a stacking context on this element, but z-index only actually applies to positioned
   elements — without position:relative, the wrapper silently paints in normal-flow order and
   loses to any explicitly z-indexed sibling (e.g. .ph.is-entry::after's darkening overlay,
   z-index:1, ends up covering the button instead of sitting behind it). */
/* flex-shrink:0 — this wrapper is a flex item inside the column-flex .ph tool cards, and Chrome
   only leaves it at content height because min-height:auto protects it. Firefox resolves that
   protection differently for a nested flex container and squeezed the CTA to a sliver (QA saw
   buttons "rendering differently from Chrome"). Pinning shrink is a no-op in Chrome. */
.cut-wrap{display:inline-flex;position:relative;z-index:2;flex-shrink:0}
/* .pc-cta no longer shares the cut-corner treatment — hero CTAs follow the Newegg design
   system's 999px pill (see the pc-cta rules in the hero carousel section). */
/* Section CTAs follow the hero's Newegg design-system primary pill (NE Yellow #FA9D28,
   #212121 ink, 999px radius, fa-caret-right) — the cut-corner treatment is fully retired. */
.module-cta,.ph-entry-cta{border-radius:999px;position:relative;background:var(--ne-yellow);color:var(--ne-ink);border:none}
.module-cta{height:48px;padding:0 32px;font-family:var(--font-ui);font-size:15px;font-weight:700;letter-spacing:0;cursor:pointer;display:inline-flex;align-items:center;gap:9px;transition:background .18s ease,transform .22s cubic-bezier(.2,.7,.2,1);flex:0 0 auto;min-width:max-content}
/* ===== CARET — was Font Awesome's fa-caret-right, now a clip-path triangle =====
   FA cost 3.9KB of render-blocking CSS + an 81.8KB .woff for ONE glyph used 26 times, and the
   late-arriving font was the top desktop CLS source (0.0649 of 0.065, attributed to
   `DIV.page-content-root | I | I`). The injected site header/footer used zero FA icons, so
   dropping the <link> was safe.
   Geometry is measured from the real glyph at 4x deviceScaleFactor, not guessed: the element box
   was 0.3575em x 1em and the painted triangle 0.3125em x 0.5625em, offset 0.0068em / 0.1719em
   inside it. The polygon below reproduces exactly that, as percentages of the same box — so every
   call site keeps its own font-size (16/15/14/13px) and its own inherited color via currentColor.
   Keep `width` in em: four different sizes ride on font-size alone. */
.caret{flex:0 0 auto;width:.3575em;height:1em;background:currentColor;
  clip-path:polygon(2% 20%, 93.7% 48.9%, 2% 77.8%)}
.module-cta .caret,.ph-entry-cta .caret{font-size:15px;line-height:1}
.cut-wrap:has(.module-cta),.cut-wrap:has(.ph-entry-cta){filter:drop-shadow(0 6px 18px rgba(0,0,0,.35))}
.module-cta:hover{background:var(--ne-yellow-hover);transform:translateY(-2px)}
.module-cta:active{background:var(--ne-yellow-active);transform:translateY(0) scale(.98);transition-duration:.08s}
.module-cta:focus-visible,.ph-entry-cta:focus-visible{outline:none}
.module-cta:focus-visible::after,.ph-entry-cta:focus-visible::after{content:"";position:absolute;inset:-4px;border-radius:999px;border:2px solid #fff;box-shadow:0 0 0 4px rgba(16,97,205,.45);pointer-events:none}
/* entry cards already carry their own CTA on desktop — the module-level CTA is the
   tablet/phone fallback (shown below 1200). */
@media(min-width:1200px){.module-cta{display:none}}

/* ===== MODULE VISUAL — product image placeholders (differentiated per section) ===== */
.module-visual{flex:0 1 48%;max-width:760px;display:flex;flex-direction:column;align-items:center;justify-content:center}
/* generic placeholder tile */
.ph{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:9px;text-align:center;padding:14px;
  background:linear-gradient(135deg,rgba(20,20,50,.55),rgba(8,9,22,.30));
  border:1.5px dashed rgba(255,255,255,.22);border-radius:16px;color:rgba(255,255,255,.55);
  backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);overflow:hidden;transition:border-color .2s,transform .2s}
.ph:hover{transform:scale(1.025);border-style:solid}
.ph svg{opacity:.6;flex-shrink:0}
.ph-cap{font-family:var(--font-b);font-size:13px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;line-height:1.2}
.ph-sub{font-family:var(--font-b);font-size:12px;color:rgba(255,255,255,.82);letter-spacing:.01em;font-weight:500}
.module.shop .ph{border-color:rgba(255,133,0,.45)}
.module.shop .ph:hover{border-color:rgba(255,133,0,.8);background:linear-gradient(135deg,rgba(255,133,0,.1),rgba(8,9,22,.5))}
.module.build .ph{border-color:rgba(155,106,255,.5)}
.module.build .ph:hover{border-color:rgba(155,106,255,.85);background:linear-gradient(135deg,rgba(107,95,239,.12),rgba(8,9,22,.5))}
.module.community .ph{border-color:rgba(0,112,204,.45)}
.module.community .ph:hover{border-color:rgba(0,112,204,.85);background:linear-gradient(135deg,rgba(0,112,204,.1),rgba(8,9,22,.5))}
/* SHOP — 2x2 product grid */
.shop-grid{display:grid;grid-template-columns:repeat(2,1fr);grid-auto-rows:1fr;gap:12px;width:100%;height:420px}
/* BUILD — prebuilt (tall left) + 2 component chips (stacked right) */
.build-visual{width:100%}
.build-grid{display:grid;grid-template-columns:1fr 1fr;grid-template-rows:1fr 1fr;gap:12px;width:100%;height:400px}
.build-prebuilt{grid-column:1;grid-row:1 / span 2}
/* COMMUNITY — staggered setup collage */
.comm-collage{position:relative;width:100%;height:460px}
.comm-collage .ph{position:absolute;transition:border-color .2s,transform .2s,z-index 0s}
.comm-collage .c1{width:65%;height:285px;top:0;left:0;z-index:2}
.comm-collage .c2{width:52%;height:220px;top:210px;right:0;z-index:3}
.comm-collage .c3{width:44%;height:170px;bottom:0;left:7%;z-index:1}
.comm-collage .ph:hover{z-index:10}

/* ===== REAL PHOTO TILES (shared) ===== */
.ph.is-photo{padding:14px;border-style:solid;align-items:flex-start;justify-content:flex-end;text-align:left;gap:3px}
.ph.is-photo .ph-img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:0;transition:transform .6s cubic-bezier(.2,.7,.2,1)}
.ph.is-photo::after{content:"";position:absolute;inset:0;z-index:1;background:linear-gradient(0deg,rgba(5,6,14,.94) 0%,rgba(5,6,14,.45) 42%,rgba(5,6,14,.05) 100%)}
.ph.is-photo:hover{transform:none}
.ph.is-photo:hover .ph-img{transform:scale(1.07)}
.ph.is-photo .ph-cap,.ph.is-photo .ph-sub{position:relative;z-index:2;color:#fff;text-shadow:0 1px 6px rgba(0,0,0,.6)}
.ph.is-photo .ph-sub{color:rgba(255,255,255,.7)}
/* small "pill" tag inside a photo tile */
.ph-pill{position:absolute;top:13px;left:13px;z-index:2;font-size:11.5px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;padding:5px 11px;border-radius:999px;color:#fff;backdrop-filter:blur(6px);background:rgba(8,9,22,.55);border:1px solid rgba(255,255,255,.18)}

/* Product catalog image tiles — white-bg Newegg product photos */
.ph.is-product{border-style:solid;align-items:flex-end;justify-content:flex-end;text-align:left;gap:4px;background:#080A18}
.ph.is-product .ph-img{position:absolute;inset:0;width:100%;height:80%;top:2%;object-fit:contain;z-index:0;padding:4px;transition:transform .5s cubic-bezier(.2,.7,.2,1)}
.ph.is-product::after{content:"";position:absolute;inset:auto 0 0 0;height:38%;z-index:1;background:linear-gradient(0deg,rgba(5,6,14,.99) 0%,rgba(5,6,14,.7) 55%,transparent 100%)}
.ph.is-product:hover{transform:scale(1.025);border-style:solid}
.ph.is-product:hover .ph-img{transform:scale(1.06)}
.ph.is-product .ph-cap,.ph.is-product .ph-sub{position:relative;z-index:2;color:#fff;text-shadow:0 1px 6px rgba(0,0,0,.7)}

/* ===== HUB SHORTCUTS BAR ===== */
/* Hidden per 2026-07 hero redesign (tab navigation removed from the hero — title/subtagline
   only). Markup, GA hooks and all styles below are kept so it can be re-enabled by flipping
   this display back to flex. */
.hub-shortcuts{display:none;align-items:stretch;background:rgba(6,7,15,.72);border:1px solid rgba(255,255,255,.09);border-radius:18px;backdrop-filter:blur(28px);-webkit-backdrop-filter:blur(28px);overflow:hidden;margin-top:4px;width:100%;max-width:720px;box-shadow:0 8px 32px rgba(0,0,0,.35),inset 0 1px 0 rgba(255,255,255,.08);position:relative}
/* rainbow top accent line */
.hub-shortcuts::before{content:"";position:absolute;top:0;left:0;right:0;height:2px;z-index:2;background:linear-gradient(90deg,var(--accent-2),var(--ne-orange));opacity:.65}
.hub-sc{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:22px 10px 20px;cursor:pointer;border-right:1px solid rgba(255,255,255,.06);transition:background .25s cubic-bezier(.2,.7,.2,1);position:relative}
.hub-sc:last-child{border-right:none}
/* bottom accent bar per section — slides in on hover */
.hub-sc::after{content:"";position:absolute;bottom:0;left:14px;right:14px;height:2px;border-radius:2px 2px 0 0;opacity:0;transform:scaleX(.3);transition:opacity .25s,transform .3s cubic-bezier(.2,.7,.2,1)}
.hub-sc:hover::after{opacity:1;transform:scaleX(1)}
.hub-sc[data-m="build"]::after{background:var(--accent-2)}
.hub-sc[data-m="shop-play"]::after{background:var(--c-shop)}
.hub-sc[data-m="setup"]::after{background:var(--c-comm)}
/* hover fill */
.hub-sc[data-m="build"]:hover{background:rgba(107,95,239,.13)}
.hub-sc[data-m="shop-play"]:hover{background:rgba(255,133,0,.1)}
.hub-sc[data-m="setup"]:hover{background:rgba(0,112,204,.1)}
/* icon container — colored pill at rest */
.hub-sc-ico{width:48px;height:48px;border-radius:14px;display:flex;align-items:center;justify-content:center;transition:transform .22s cubic-bezier(.2,.7,.2,1),background .25s,box-shadow .25s}
.hub-sc[data-m="build"] .hub-sc-ico{background:rgba(107,95,239,.18);box-shadow:0 0 14px rgba(107,95,239,.18)}
.hub-sc[data-m="shop-play"] .hub-sc-ico{background:rgba(255,133,0,.15);box-shadow:0 0 14px rgba(255,133,0,.14)}
.hub-sc[data-m="setup"] .hub-sc-ico{background:rgba(0,112,204,.15);box-shadow:0 0 14px rgba(0,112,204,.14)}
.hub-sc:hover .hub-sc-ico{transform:translateY(-3px) scale(1.08)}
.hub-sc[data-m="build"]:hover .hub-sc-ico{background:rgba(107,95,239,.32);box-shadow:0 4px 20px rgba(107,95,239,.45)}
.hub-sc[data-m="shop-play"]:hover .hub-sc-ico{background:rgba(255,133,0,.28);box-shadow:0 4px 20px rgba(255,133,0,.38)}
.hub-sc[data-m="setup"]:hover .hub-sc-ico{background:rgba(0,112,204,.28);box-shadow:0 4px 20px rgba(0,112,204,.38)}
/* icon stroke — section-colored at rest */
.hub-sc[data-m="build"] .hub-sc-ico svg{stroke:var(--accent-2)}
.hub-sc[data-m="shop-play"] .hub-sc-ico svg{stroke:var(--c-shop)}
.hub-sc[data-m="setup"] .hub-sc-ico svg{stroke:var(--c-comm)}
.hub-sc-ico svg{transition:stroke .2s}
/* label */
.hub-sc-label{font-family:var(--font-b);font-size:14px;font-weight:700;color:rgba(255,255,255,.82);white-space:nowrap;transition:color .22s;letter-spacing:.06em;text-transform:uppercase;line-height:1}
.hub-sc[data-m="build"]:hover .hub-sc-label{color:var(--accent-2)}
.hub-sc[data-m="shop-play"]:hover .hub-sc-label{color:var(--c-shop)}
.hub-sc[data-m="setup"]:hover .hub-sc-label{color:var(--c-comm)}
/* subtitle */
.hub-sc-sub{font-family:var(--font-b);font-size:12px;font-weight:500;color:rgba(255,255,255,.56);white-space:nowrap;letter-spacing:.03em;transition:color .22s;line-height:1}
.hub-sc[data-m="build"]:hover .hub-sc-sub{color:rgba(155,106,255,.7)}
.hub-sc[data-m="shop-play"]:hover .hub-sc-sub{color:rgba(255,133,0,.7)}
.hub-sc[data-m="setup"]:hover .hub-sc-sub{color:rgba(0,112,204,.7)}

/* ===== SIDE PROGRESS NAV ===== */
/* visibility, not just opacity: pointer-events:none stops the mouse but NOT the keyboard, so
   while the hero owned the viewport a Tab user landed on three fully transparent links in a
   row with no visible focus (WCAG 2.4.7). visibility:hidden takes the items out of the tab
   order and the a11y tree. The 0s-delayed transition is what keeps the fade: on the way OUT
   visibility flips only after opacity has finished, on the way IN it flips immediately. */
.dotnav{position:fixed;right:36px;top:50%;transform:translateY(-50%);z-index:90;display:flex;flex-direction:column;gap:26px;opacity:0;visibility:hidden;transition:opacity .4s,visibility 0s .4s;pointer-events:none}
.dotnav.show{opacity:1;visibility:visible;transition:opacity .4s,visibility 0s;pointer-events:auto}
.dotnav-item{display:flex;align-items:center;gap:13px;cursor:pointer;justify-content:flex-end;position:relative}
/* the row is only 18px tall (label + 13px dot) — expand the hit area to 44px without moving
   anything visually, same pattern as the carousel controls */
.dotnav-item::before{content:"";position:absolute;inset:-13px 0}
.dotnav-label{font-family:var(--font-b);font-size:13px;font-weight:600;letter-spacing:.12em;text-transform:uppercase;color:rgba(165,165,205,.55);transition:color .22s,opacity .22s}
.dotnav-item:hover .dotnav-label{color:rgba(255,255,255,.75)}
.dotnav-item.active .dotnav-label{color:var(--text)}
/* QA P1: at mid desktop widths (≲1800px) the always-on labels reach into the 1460px content
   column and sit on top of card copy ("Shop & Play" measured 97px deep into it at 1443w).
   Below that width the dots carry wayfinding alone — the items keep their aria-labels. */
@media(max-width:1799px){.dotnav-label{display:none}}
.dotnav-dot{width:13px;height:13px;border-radius:50%;border:1.5px solid var(--border-2);flex-shrink:0;transition:all .25s cubic-bezier(.2,.7,.2,1)}
.dotnav-item.active .dotnav-dot{transform:scale(1.35);box-shadow:0 0 10px currentColor}
.dotnav-item[data-m="shop-play"].active .dotnav-dot{background:var(--c-shop);border-color:var(--c-shop)}
.dotnav-item[data-m="build"].active .dotnav-dot{background:var(--accent-2);border-color:var(--accent-2)}
.dotnav-item[data-m="setup"].active .dotnav-dot{background:var(--c-comm);border-color:var(--c-comm)}

/* ===== PERSISTENT TICKER — bottom rail, visible across every section ===== */
.ticker{position:fixed;left:0;right:0;bottom:0;z-index:95;height:32px;overflow:hidden;background:rgba(6,7,15,.82);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border-top:1px solid rgba(255,255,255,.08);transition:opacity .3s,visibility .3s}
.ticker.hide{opacity:0;visibility:hidden;pointer-events:none}
.ticker-lane{position:absolute;top:0;left:0;height:100%;display:flex;align-items:center;white-space:nowrap;animation:tickerScroll 34s linear infinite}
.ticker-lane span{display:inline-flex;align-items:center;gap:10px;padding:0 24px;font-family:ui-monospace,'SFMono-Regular',Consolas,monospace;font-size:10.5px;font-weight:600;letter-spacing:.13em;text-transform:uppercase;color:rgba(255,255,255,.55)}
.ticker-lane span b{color:var(--accent-2);font-weight:700}
.ticker-lane .tick-dot{width:4px;height:4px;border-radius:50%;background:rgba(255,255,255,.28);flex-shrink:0}
@keyframes tickerScroll{from{transform:translateX(0)}to{transform:translateX(-50%)}}
@media (prefers-reduced-motion: reduce){.ticker-lane{animation:none}}

/* ===== PRODUCT CAROUSEL (hero-embedded, frameless) ===== */
.pc-track{display:flex;transition:transform .5s cubic-bezier(.25,.46,.45,.94)}
.pc-slide{min-width:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;position:relative;overflow:hidden}
.pc-slide-bg{position:absolute;inset:0;opacity:.5;transition:opacity .4s}
.pc-tag{font-family:var(--font-b);font-size:11.5px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;margin-bottom:12px;display:flex;align-items:center;justify-content:center;gap:9px;opacity:1}
.pc-tag .pc-tag-line{height:1.5px;width:24px;border-radius:1px;background:currentColor;flex-shrink:0;opacity:.7}
.pc-slide h2,.pc-slide .pc-name{font-family:var(--font-h);font-weight:800;font-size:clamp(22px,2.6vw,38px);color:#fff;letter-spacing:-.01em;line-height:1.05;margin-bottom:14px;text-align:center;text-shadow:0 1px 3px rgba(0,0,0,.9),0 2px 18px rgba(0,0,0,.6)}
/* hides the old per-slide description paragraphs; .pc-name is the product title (a <p> since the
   heading-outline fix — carousel names shouldn't outrank section headings) and must stay visible */
.pc-slide p:not(.pc-name){display:none}
/* Newegg design-system primary CTA: NE Yellow #FA9D28 pill, #212121 ink, Open Sans 700 14px,
   999px radius; hover #FFB25D, active/pressed #FF8500. One per viewport (one slide visible). */
.pc-cta{height:44px;padding:0 28px;background:var(--ne-yellow);border:none;border-radius:999px;position:relative;font-family:var(--font-ui);font-size:14px;font-weight:700;letter-spacing:0;color:var(--ne-ink);cursor:pointer;display:inline-flex;align-items:center;gap:8px;transition:background .18s ease,transform .22s cubic-bezier(.2,.7,.2,1);flex:0 0 auto;min-width:max-content}
.pc-cta-wrap{margin:0 auto;filter:drop-shadow(0 6px 18px rgba(0,0,0,.35))}
.pc-cta:hover{background:var(--ne-yellow-hover);transform:translateY(-2px)}
.pc-cta:active{background:var(--ne-yellow-active);transform:translateY(0) scale(.98);transition-duration:.08s}
.pc-cta .caret{font-size:16px;line-height:1} /* Newegg LP caret, inherits ink color */
.pc-cta:focus-visible{outline:none}
.pc-cta:focus-visible::after{content:"";position:absolute;inset:-4px;border-radius:999px;border:2px solid #fff;box-shadow:0 0 0 4px rgba(16,97,205,.45);pointer-events:none}
.pc-visual{position:relative;display:flex;align-items:center;justify-content:center}
.pc-glow-ring{position:absolute;width:380px;height:380px;border-radius:50%;opacity:.22;filter:blur(90px);background:rgba(107,95,239,.7)}
/* real product photo */
/* overflow:visible — pc-scale-xl enlarges the laptop so the keyboard stays in frame;
   overflow:hidden was clipping the chassis under the screen. */
.pc-photo{position:relative;z-index:2;width:min(72%,410px);aspect-ratio:4/3;border-radius:16px;overflow:visible;background:transparent;border:none;box-shadow:none;--rx:0deg;--ry:0deg}
.pc-photo img{width:100%;height:100%;object-fit:contain;padding:10px;display:block;transition:transform .7s cubic-bezier(.2,.7,.2,1);filter:drop-shadow(0 8px 24px rgba(0,0,0,.5))}
/* per-slide visual balance — product shots have different canvas padding, so scales
   bring the subject to roughly the same on-screen size across the carousel.
   xl stays modest: open-laptop shots are tall; aggressive scale + clip hid the keyboard. */
.pc-photo img.pc-scale-sm{transform:scale(.96)}
.pc-photo img.pc-scale-md{transform:scale(1.04)}
/* xl runs padding:0 and fills the box, so scale>1 spills past .pc-photo on both axes — keep it
   low enough that the spill still clears .hero-carousel's overflow:hidden on short screens. */
.pc-photo img.pc-scale-xl{transform:scale(1.22);padding:0}
.pc-slide:hover .pc-photo img.pc-scale-sm{transform:scale(1.02) rotateX(var(--rx)) rotateY(var(--ry))}
.pc-slide:hover .pc-photo img.pc-scale-md{transform:scale(1.1) rotateX(var(--rx)) rotateY(var(--ry))}
.pc-slide:hover .pc-photo img.pc-scale-xl{transform:scale(1.28) rotateX(var(--rx)) rotateY(var(--ry))}
/* shimmer placeholder: painted as the <img>'s own background, so it shows through until the
   image has pixels to paint over it — no separate overlay element, no layout shift (the img's
   box is already sized by its container). JS just toggles .loaded once the real image lands. */
.ph-img,.pc-photo img,.gear-img{background:linear-gradient(100deg,rgba(255,255,255,.03) 25%,rgba(255,255,255,.1) 50%,rgba(255,255,255,.03) 75%);background-size:200% 100%;animation:imgShimmer 1.6s ease-in-out infinite}
.ph-img.loaded,.pc-photo img.loaded,.gear-img.loaded{animation:none;background:none}
@keyframes imgShimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}
@media(prefers-reduced-motion:reduce){.ph-img,.pc-photo img,.gear-img{animation:none}}
.pc-slide:hover .pc-photo img{transform:scale(1.05) rotateX(var(--rx)) rotateY(var(--ry))}
/* hero-carousel container — liquid glass, dialed clearer than v1: lower white fill, heavier
   blur + saturation so the video reads through. Text contrast is guaranteed by the per-slide
   bottom scrim below (WCAG: 24px+ bold white over the scrimmed zone stays ≥ 4.5:1 even on a
   white video frame), not by the glass fill. */
/* hologram material: almost no fill, a light blur (the video stays legible THROUGH the card),
   cool cyan-violet edge light and outer glow instead of a dark drop shadow. */
.hero-carousel{position:absolute;inset:0;display:flex;flex-direction:column;border-radius:16px;overflow:hidden;
  background:linear-gradient(160deg,rgba(130,205,255,.05),rgba(120,160,255,.015) 45%,rgba(165,120,255,.045) 100%);
  border:1px solid rgba(150,215,255,.38);
  backdrop-filter:blur(6px) saturate(160%);-webkit-backdrop-filter:blur(6px) saturate(160%);
  box-shadow:0 0 0 1px rgba(120,200,255,.10),0 0 26px rgba(90,180,255,.22),0 0 70px -14px rgba(130,140,255,.35),
    inset 0 0 24px rgba(120,200,255,.09),inset 0 1px 0 rgba(190,235,255,.28)}
/* projection scanlines drifting down the pane — the hologram tell */
@keyframes holoScan{from{background-position:0 0}to{background-position:0 24px}}
.hero-carousel::before{content:"";position:absolute;inset:0;z-index:3;pointer-events:none;
  background:repeating-linear-gradient(0deg,rgba(160,225,255,.055) 0 1px,transparent 1px 4px);
  animation:holoScan 3.2s linear infinite}
@media(prefers-reduced-motion:reduce){.hero-carousel::before{animation:none}}
/* readability scrim inside each slide (under the z:2 content, over photo/glow): keeps the
   keyword + CTA zone readable no matter how bright the video behind the hologram gets —
   tinted deep blue so it reads as projection shading, not solid glass. */
.hero-carousel .pc-slide::after{content:"";position:absolute;left:0;right:0;bottom:0;height:62%;z-index:1;pointer-events:none;
  background:linear-gradient(180deg,rgba(3,10,26,0),rgba(3,10,26,.42) 40%,rgba(3,10,26,.68) 100%)}
/* pan-y: the browser keeps handling vertical page scroll natively through this element;
   horizontal gestures are left alone for the swipe handler below instead of being claimed as
   a native scroll/pan, so a vertical scroll through the hero never gets hijacked mid-swipe. */
.hero-carousel .pc-wrap{flex:1;position:relative;overflow:hidden;touch-action:pan-y}
.hero-carousel .pc-track{height:100%}
.hero-carousel .pc-slide{height:100%;padding:24px 18px 6px;gap:12px}
/* compact-card scale for everything inside the hologram card */
/* The 28vh term caps the photo by viewport HEIGHT too: aspect-ratio 4/3 derives the height from
   the width, and the card is only clamp(310px,42vh,400px) tall, so a width-only cap would push
   the image past .hero-carousel's overflow:hidden on short screens. */
.hero-carousel .pc-photo{width:min(86%,278px,28vh)}
.hero-carousel .pc-glow-ring{width:200px;height:200px;max-width:86%}
.hero-carousel .pc-slide h2,.hero-carousel .pc-slide .pc-name{font-size:clamp(19px,1.5vw,26px);margin-bottom:8px;position:relative;z-index:2}
.hero-carousel .pc-cta{height:40px;padding:0 22px;font-size:13px}
.hero-carousel .pc-dots{position:relative;z-index:2}
.hero-carousel .pc-content{position:relative;z-index:2;text-align:center}
/* pc-visual used to be flex:1 + align-items:flex-end, pinning the photo to the bottom of
   whatever leftover space it was given — that worked when the photo was big enough to fill
   most of that space, but now that it's capped smaller (see .pc-photo), the box is still the
   same height, so a big empty gap opens up ABOVE the photo instead. Letting pc-visual size to
   its content and centering the whole slide (via .pc-slide's own justify-content:center)
   moves the photo+caption+button cluster up as one unit, keeping the tight photo-to-caption
   gap instead of reintroducing the old "gap too long" problem flex-end was there to fix. */
.hero-carousel .pc-visual{flex:0 1 auto;width:100%;align-items:center}
/* arrows */
.pc-prev,.pc-next{position:absolute;top:50%;transform:translateY(-50%);width:40px;height:40px;border-radius:50%;background:rgba(6,7,15,.7);border:1px solid rgba(255,255,255,.15);color:rgba(255,255,255,.8);cursor:pointer;display:grid;place-items:center;z-index:10;transition:all .22s cubic-bezier(.2,.7,.2,1);backdrop-filter:blur(12px);box-shadow:0 4px 16px rgba(0,0,0,.4)}
/* invisible hit-area expanders — bump the touch target to ~44px (Apple HIG / Material minimum)
   without changing the visible size of any of these controls. A ::before at inset:-Npx still
   counts as part of the real element for click/tap purposes (pseudo-elements have no event
   target of their own), so this is purely additive. */
/* hit-area expanders (visual size unchanged). Measured targets before these: arrows 42x43,
   hero-carousel CTA 40px tall, gear-head link 19px tall — all short of the 44px the project
   standard asks for. */
.pc-prev::before,.pc-next::before{content:"";position:absolute;inset:-3px}
.pc-cta::before{content:"";position:absolute;inset:-3px 0}
.gear-head-link::before{content:"";position:absolute;inset:-13px 0}
.pc-tab{position:relative}
.pc-tab::before{content:"";position:absolute;inset:-10px -4px}
.pc-dot{position:relative}
/* −9px horizontal, NOT −10px: paired with the 18px gap in .pc-dots this makes the 24px hit boxes
   tile exactly. −10px reintroduces a 2px overlap and the later sibling starts stealing taps. */
.pc-dot::before{content:"";position:absolute;inset:-19px -9px}
.pc-prev{left:14px}.pc-next{right:14px}
.pc-prev:hover,.pc-next:hover{background:rgba(107,95,239,.3);border-color:var(--accent-2);color:#fff;transform:translateY(-50%) scale(1.1);box-shadow:0 6px 22px rgba(107,95,239,.35)}
/* dots */
/* category labels tell the user what's next before they click, instead of the dots' pure
   position indicator — active state is kept in lockstep with .pc-dot by the same go() call. */
/* Hidden per 2026-07 hero redesign (tab navigation removed from the hero; dots remain the
   position indicator). JS still syncs .active on these — harmless while hidden. */
.pc-tabs{display:none;justify-content:center;gap:4px;flex-wrap:wrap;padding-top:8px}
.pc-tab{background:none;border:none;font-family:var(--font-b);font-size:11px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:rgba(255,255,255,.4);padding:5px 11px;border-radius:999px;transition:color .2s,background .2s}
.pc-tab:hover{color:rgba(255,255,255,.7)}
.pc-tab.active{color:#fff;background:rgba(155,106,255,.22)}
/* gap 18px (was 7px) is a HIT-TARGET requirement, not a spacing preference. The dots are 6px
   wide with a ±9px ::before expander, so the hit box is 24px — at the old 7px gap the pitch was
   only 13px, so each dot's expander reached 7px PAST its neighbour's centre and, being a later
   sibling, won the hit test. Measured by clicking each dot's visual centre: dot 1 selected slide
   3, dot 3 selected slide 5 — only 2 of 5 dots did what they looked like they would do. At an
   18px gap the pitch equals the 24px hit box, so the boxes tile edge-to-edge and never swallow a
   neighbour's centre. The visible dots are still 6px. Changing either value alone re-breaks it. */
.pc-dots{display:flex;justify-content:center;gap:18px;padding:10px 0 14px}
/* alpha .38, not .2: at .2 the resting dots composite to rgb(56,57,63) over the scene, which is
   1.74:1 against it — a WCAG 1.4.11 (non-text contrast, AA needs 3:1) failure, i.e. users cannot
   reliably see how many slides there are. .38 measures 3.6:1. The active dot stays at .9 / 16:1,
   so the two states are still unmistakable. */
.pc-dot{width:6px;height:6px;border-radius:3px;background:rgba(255,255,255,.38);cursor:pointer;transition:all .3s cubic-bezier(.2,.7,.2,1);border:none}
.pc-dot.active{background:rgba(255,255,255,.9);width:24px;box-shadow:0 0 8px rgba(255,255,255,.4)}

/* ===== SETUP section (replaces Community — cyan accent) ===== */
.module.setup .module-glow{background:var(--c-comm);bottom:-10%;right:-6%}
.module.setup .module-index{color:var(--c-comm)} .module.setup .bar{background:var(--c-comm)}
.module.setup .module-icon{box-shadow:0 4px 16px rgba(0,112,204,.2),inset 0 1px 0 rgba(255,255,255,.08)}
.module.setup .module-text{border-top-color:rgba(0,112,204,.7);box-shadow:0 24px 64px rgba(0,0,0,.35),0 0 40px rgba(0,112,204,.06),inset 0 1px 0 rgba(255,255,255,.06)}
.module.setup .feat .dot{background:var(--c-comm)}
.module.setup .module-cta{background:linear-gradient(120deg,#0356A6,#0070CC);box-shadow:inset 0 1px 0 rgba(255,255,255,.2)}
.module.setup .cut-wrap:has(.module-cta){filter:drop-shadow(0 6px 24px rgba(0,112,204,.45))}
.module.setup .cut-wrap:has(.module-cta):hover{filter:drop-shadow(0 14px 40px rgba(0,112,204,.75)) drop-shadow(0 0 30px rgba(0,112,204,.2))}
.module.setup .ph{border-color:rgba(0,112,204,.45)}
.module.setup .ph:hover{border-color:rgba(0,112,204,.85);background:linear-gradient(135deg,rgba(0,112,204,.1),rgba(8,9,22,.5))}
.deck-card.module.setup .module-glow{opacity:.32;mix-blend-mode:screen}

/* light-trail ring on card hover — CSS-only conic-gradient chase around the border */
@property --trail{syntax:'<angle>';inherits:false;initial-value:0deg}
@keyframes trailSpin{to{--trail:360deg}}
.ph-trail{position:absolute;inset:0;border-radius:16px;padding:2px;z-index:2;pointer-events:none;opacity:0;transition:opacity .35s;
  background:conic-gradient(from var(--trail),transparent 0 68%,rgba(155,106,255,.85) 84%,#F3EBFF 92%,rgba(155,106,255,.85) 97%,transparent 100%);
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;
  mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);mask-composite:exclude}
.ph.is-entry:hover .ph-trail{opacity:1;animation:trailSpin 2.6s linear infinite}
.module.shop .ph-trail{background:conic-gradient(from var(--trail),transparent 0 68%,rgba(255,133,0,.85) 84%,#FFF3E6 92%,rgba(255,133,0,.85) 97%,transparent 100%)}
.module.setup .ph-trail{background:conic-gradient(from var(--trail),transparent 0 68%,rgba(0,112,204,.85) 84%,#EAF3FF 92%,rgba(0,112,204,.85) 97%,transparent 100%)}
@media (prefers-reduced-motion: reduce){.ph.is-entry:hover .ph-trail{animation:none;opacity:.6}}

/* ===== ENTRY CARDS — section entry points (Newegg product-catalog style) ===== */
.entry-cards{display:grid;grid-template-columns:1fr 1fr;gap:14px;width:100%;height:380px}
.module.build .entry-cards,.module.setup .entry-cards{grid-template-columns:1fr 1fr 1fr}
/* ALL sections use the vertical layout: text on top, cards directly under, everything inside
   ONE bigger holo container whose bottom fades out into the video scene (mask-image). The
   module-text loses its own panel — the outer container is the panel now. */
/* ===== CATEGORY / GEAR CARDS (shared component; per-scene accent via --cat-* tokens) ===== */
.module{--cat-line:rgba(255,133,0,.5);--cat-glow:rgba(255,133,0,.18);--cat-base:rgba(34,23,10,.92);--cat-lt:#FFB86A;--cat-hover:#FF8500;--cat-sh:rgba(255,133,0,.5);--cat-trail:#FFB258}
.module.build{--cat-line:rgba(155,106,255,.5);--cat-glow:rgba(107,95,239,.2);--cat-base:rgba(20,16,45,.92);--cat-lt:#C4B5FD;--cat-hover:#9B6AFF;--cat-sh:rgba(107,95,239,.5);--cat-trail:#9b6aff}
.module.setup{--cat-line:rgba(0,112,204,.5);--cat-glow:rgba(0,112,204,.2);--cat-base:rgba(9,20,50,.92);--cat-lt:#8FC2FF;--cat-hover:#0070CC;--cat-sh:rgba(0,112,204,.5);--cat-trail:#3B8EFF}
.gear-head{display:flex;align-items:baseline;justify-content:space-between;gap:16px;margin-bottom:14px;flex-wrap:wrap}
.gear-head-label{font-family:var(--font-b);font-size:16px;font-weight:600;letter-spacing:.01em;text-transform:none;color:var(--cat-lt)}
.gear-head-link{font-family:var(--font-ui);font-size:13px;font-weight:700;color:var(--cat-lt);display:inline-flex;align-items:center;gap:6px;text-decoration:none;white-space:nowrap;position:relative}
/* invisible hit-area expander: the text link renders ~19px tall, under the 24px WCAG 2.5.8
   floor — stretch the touch target to ~45px without touching the visual. */
.gear-head-link::before{content:"";position:absolute;inset:-13px 0}
.gear-head-link:hover{color:#fff}
/* Same link treatment, but standing alone under a .module-text paragraph instead of inside the
   baseline-aligned .gear-head row — so it needs to become a block to earn its own line, and it
   carries the vertical rhythm the flex gap used to supply. Shop & Play's route to the Digital
   Games category rides on this (its .module-cta pill is hidden at both breakpoints). */
.module-textlink{display:inline-flex;margin-top:20px}
/* "card-filing" row: skewed parallelogram tabs sitting side by side; dim/desaturated at rest,
   the hovered card fills with its scene color and lifts forward (ref: the RIDE event picker). */
.gear-cards{display:flex;justify-content:center;align-items:stretch;gap:10px;padding:8px 2px 4px}
.gear-card{position:relative;flex:1 1 0;min-width:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;
  height:150px;padding:14px 8px 12px;text-decoration:none;color:#fff;overflow:hidden;
  border:1px solid rgba(165,185,215,.24);border-radius:12px;
  transform:skewX(-9deg);transform-origin:center bottom;
  background:linear-gradient(165deg,rgba(21,25,46,.92),rgba(9,11,24,.94));
  filter:grayscale(.78) brightness(.64);
  transition:filter .3s ease,transform .3s cubic-bezier(.2,.7,.2,1),box-shadow .3s,border-color .3s,background .3s}
.gear-card>*{transform:skewX(9deg)}                        /* keep icon + label upright */
.gear-card:hover,.gear-card:focus-visible{filter:none;outline:none;z-index:3;
  transform:skewX(-9deg) translateY(-10px) scale(1.05);
  border-color:var(--cat-hover);
  background:radial-gradient(ellipse 92% 66% at 50% 30%,var(--cat-glow),transparent 70%),linear-gradient(165deg,var(--cat-base),rgba(9,11,24,.9) 62%);
  box-shadow:0 18px 44px -10px var(--cat-sh),inset 0 1px 0 rgba(255,255,255,.14)}
.gear-ico{position:relative;z-index:1;color:var(--cat-lt);width:42px;height:42px;transition:color .3s}
.gear-ico svg{width:100%;height:100%}
/* real transparent product cutout (Newegg nobg CDN PNG) is the tab's hero visual. The card's
   rest filter (grayscale+dim) ghosts it; hover clears to full color — same reveal as the frame. */
.gear-shot{position:relative;z-index:1;flex:1 1 auto;min-height:0;width:100%;
  display:flex;align-items:center;justify-content:center;overflow:hidden}
.gear-img{max-width:96%;max-height:100%;width:auto;height:auto;object-fit:contain;
  filter:drop-shadow(0 6px 15px rgba(0,0,0,.55));transition:transform .3s cubic-bezier(.2,.7,.2,1)}
.gear-card:hover .gear-img,.gear-card:focus-visible .gear-img{transform:scale(1.07)}
/* Switch 2 boxart sits right-of-centre in the source shot — nudge it left so it reads centred */
.gear-card[data-ga-click="keyword-shop-nintendo-switch-2"] .gear-img{transform:translateX(-10%)}
.gear-card[data-ga-click="keyword-shop-nintendo-switch-2"]:hover .gear-img,
.gear-card[data-ga-click="keyword-shop-nintendo-switch-2"]:focus-visible .gear-img{transform:translateX(-10%) scale(1.07)}
.gear-label{position:relative;z-index:1;flex:none;font-family:var(--font-b);font-size:12px;font-weight:700;letter-spacing:.02em;text-align:center;line-height:1.15;color:#fff}
/* moving light-trail on hover — same effect as the tool cards but scaled down for the small tabs
   (thinner 1.2px ring, shorter arc). Rides ::after; pseudo isn't hit by the .gear-card>* counter-
   skew, so the ring follows the tab's skewed silhouette. */
.gear-card::after{content:"";position:absolute;inset:0;z-index:2;pointer-events:none;border-radius:12px;
  background:conic-gradient(from var(--tw-a),transparent 0 76%,var(--cat-trail,#FFB258) 87%,#fff 93%,var(--cat-trail,#FFB258) 97%,transparent 100%);
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;
  mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);mask-composite:exclude;
  padding:1.2px;opacity:0;transition:opacity .25s;animation:trailspin 2.2s linear infinite;animation-play-state:paused}
.gear-card:hover::after,.gear-card:focus-visible::after{opacity:1;animation-play-state:running}
@media(prefers-reduced-motion:reduce){.gear-card{transition:filter .3s,border-color .3s,background .3s}.gear-card:hover{transform:skewX(-9deg)}.gear-card::after{animation:none}}

/* ===== LIGHT-TRAIL HOVER (a bright arc that runs around the border of a clickable card) =====
   Driven by an animated conic-gradient angle masked to a thin border ring. @property makes the
   angle animatable; the ring rides whichever pseudo-element is free on each component. */
@property --tw-a{syntax:"<angle>";inherits:false;initial-value:0deg}
@keyframes trailspin{to{--tw-a:360deg}}
.lighttrail-ring{content:"";position:absolute;inset:0;z-index:4;pointer-events:none;border-radius:inherit;
  background:conic-gradient(from var(--tw-a),transparent 0 60%,var(--trail-c,#a9e4ff) 77%,#ffffff 86%,var(--trail-c,#a9e4ff) 95%,transparent 100%);
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;
  mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);mask-composite:exclude;
  padding:1.6px;opacity:0;transition:opacity .25s;animation:trailspin 1.9s linear infinite;animation-play-state:paused}
@media(prefers-reduced-motion:reduce){
  .lighttrail-ring,.ph.is-entry.is-tool::before,.gear-card::after,.floating-game::after{animation:none}
}

/* marquee loop keyframes — TOP LEVEL on purpose: keyframes declared inside a media query only
   exist while that query matches (they were desktop-scoped once, which froze the mobile rows).
   Loop distance = exactly one set (--fg-shift, set by JS) → seamless repeat at any set count.
   The fallback is 0px — NOT a guessed distance. If JS has not measured yet (or gave up), a
   non-zero fallback scrolls the un-cloned single set until the row runs out of covers and shows
   empty space: measured 174px of hole at 1920 with the old 1000px fallback, and on the mobile
   5-cover roster (~680px per set) a 1000px shift exceeds the whole set, so the row empties
   completely once per cycle. 0px means the worst case is a STATIC FULL row, never a hole. */
@keyframes fgScrollL{to{transform:translateX(calc(-1 * var(--fg-shift,0px)))}}
@keyframes fgScrollR{from{transform:translateX(calc(-1 * var(--fg-shift,0px)))}to{transform:translateX(0)}}
/* mobile-only expander ("Show N more games") — desktop's marquee already cycles the pool */
.fg-more{display:none}
/* author display:grid would beat the UA [hidden] rule and leave the grid permanently open */
.fg-more-grid[hidden]{display:none !important}

@media(min-width:1200px){
  .module{padding:60px clamp(40px,4.5vw,80px)}
  /* shared big-card glass (the "main" panel that dissolves into the scene at its bottom).
     Fill is now a near-opaque dark navy so body/heading text clears WCAG AA over ANY video
     frame (white text on this ≈ 14:1; measured in the contrast checker each change). */
  .module-main,.module.shop .module-inner{
    position:relative;display:flex;flex-direction:column;align-items:stretch;justify-content:flex-start;
    background:linear-gradient(160deg,rgba(13,16,36,.93),rgba(9,11,25,.9) 55%,rgba(16,13,33,.92) 100%);
    border:1px solid rgba(150,215,255,.34);border-bottom:none;border-radius:20px 20px 0 0;
    backdrop-filter:blur(10px) saturate(150%);-webkit-backdrop-filter:blur(10px) saturate(150%);
    -webkit-mask-image:linear-gradient(180deg,#000 calc(100% - 44px),transparent);
    mask-image:linear-gradient(180deg,#000 calc(100% - 44px),transparent)}

  /* BUILD & SETUP: row = big tools card (focus) + slim parts-shopping aside floating beside it */
  .module.build .module-inner,.module.setup .module-inner{
    flex-direction:row;align-items:stretch;justify-content:center;gap:22px;
    max-width:1560px;background:none;border:none;backdrop-filter:none;-webkit-backdrop-filter:none;
    -webkit-mask-image:none;mask-image:none;padding:0}
  /* 1460 (was 1240): .module's own padding leaves 1760px usable at 1920w, so the panel was
     giving up ~500px of width — the tool cards inside were the ones paying for it. */
  .module-main{flex:0 1 1460px;min-width:0;margin:0 auto;padding:34px 42px 50px}
  .parts-aside{flex:0 0 236px;align-self:center;display:flex;flex-direction:column;gap:14px;
    padding:22px 20px;border-radius:16px;
    background:linear-gradient(165deg,rgba(18,20,46,.94),rgba(9,11,24,.9));
    border:1px solid rgba(150,215,255,.26);
    backdrop-filter:blur(12px) saturate(150%);-webkit-backdrop-filter:blur(12px) saturate(150%);
    box-shadow:0 18px 50px rgba(0,0,0,.42),inset 0 1px 0 rgba(190,235,255,.14)}
  .parts-aside-title{font-family:var(--font-b);font-size:12.5px;font-weight:400;line-height:1.4;color:rgba(224,228,248,.7)}
  .parts-aside-title b{display:block;margin-top:3px;font-weight:800;font-size:14.5px;color:#fff;letter-spacing:.01em}
  .parts-aside .module-keywords{flex-direction:column;align-items:stretch;gap:8px;margin:0}
  .parts-aside .module-kw{width:100%;justify-content:flex-start;font-size:12.5px;padding:9px 14px}

  /* neutralize the inner text panel — the outer card is the panel now */
  .module-main .module-text,.module.shop .module-text{
    background:none;border:none;box-shadow:none;backdrop-filter:none;-webkit-backdrop-filter:none;
    padding:0 0 22px;max-width:none;flex:none;width:100%}
  /* let heading + body run the full card width (no narrow centered column) */
  .module-main .module-text,.module.shop .module-text{text-align:left}
  .module-main .module-text .desc,.module.shop .module-text .desc{max-width:none}
  .module-main .module-text h2,.module.shop .module-text h2,.module-main .module-text h3,.module.shop .module-text h3{max-width:none}
  .module-main .module-visual,.module.shop .module-visual{flex:none;width:100%;max-width:none}
  /* The three tool cards are the section's primary entries — give them real presence (was a flat
     230px). Height has to stay viewport-relative: .module-main is centered in a 100vh section, so
     a fixed 300px overflowed the section on short screens (768/800px tall). The 30vh term hands
     back the old 230px floor exactly where it's needed. */
  .module.build .entry-cards,.module.setup .entry-cards{height:clamp(230px,30vh,300px);gap:20px;margin-bottom:26px}

  /* SHOP & PLAY: hint + game covers + content card are stacked as ONE bottom-anchored column,
     so the empty scene space always falls ABOVE the covers — no dead gap between the covers and
     the card on tall viewports. DOM order: .shop-hint, .floating-games, .module-inner. */
  /* CENTERED vertically like Build/Setup so all three sections read in-sync; hint + covers + card
     form one centred stack (equal breathing space above and below). */
  .module.shop{flex-direction:column;justify-content:center;align-items:center;
    padding:40px clamp(30px,4vw,64px)}
  /* how-to-shop guidance rides just above the covers (in flow now, not floated) */
  .shop-hint{position:relative;top:auto;left:auto;transform:none;margin:0 auto 12px;z-index:7;
    display:inline-flex;align-items:center;gap:8px;white-space:nowrap;
    font-family:var(--font-b);font-size:12.5px;font-weight:600;letter-spacing:.03em;color:#EAF2FF;
    background:rgba(6,9,20,.72);border:1px solid rgba(150,215,255,.36);border-radius:999px;padding:7px 16px;
    backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);box-shadow:0 4px 16px rgba(0,0,0,.4)}
  .shop-hint-ico{width:15px;height:15px;color:var(--c-shop);display:inline-flex}
  .shop-hint-ico svg{width:100%;height:100%}
  /* roomy 2-column card (text left, category filing tabs right) hugging the floor right under
     the covers — the reclaimed space (previously a dead gap) now gives the content more air. */
  /* extended container: more padding + a min-height so the text and category tabs have room to
     breathe (was a tight strip hugging the floor) */
  /* 1460 matches .module-main on Build/Setup so all three sections read as one column width */
  .module.shop .module-inner{flex-direction:row;align-items:center;justify-content:center;
    width:100%;max-width:1460px;min-height:clamp(248px,34vh,300px);margin:0;padding:clamp(22px,3.2vh,34px) 52px clamp(26px,3.6vh,38px);gap:52px}
  .module.shop .module-text{flex:0 0 35%;width:auto;max-width:35%;padding:0}
  .module.shop .module-text .desc{font-size:15.5px;line-height:1.6;margin:12px 0 20px}
  .module.shop .module-visual{flex:1 1 65%;width:auto;justify-content:center}

  /* ===== REFERENCE-STYLE TOOL CARDS (tall, full-bleed, beveled, neon frame, bottom title) ===== */
  .module.build{--tool-accent:#9b6aff}
  .module.setup{--tool-accent:#3B8EFF}
  /* .module prefix out-ranks the generic .module.build/.setup .ph holo-frame rule so the
     section-accent neon frame (purple / cyan) wins over the default cyan border */
  /* rounded frame (back to the original rounded corners — the bevel is retired) */
  .module .ph.is-entry.is-tool{
    padding:16px;border:1.5px solid var(--tool-accent);border-radius:16px;background:#070b18;
    box-shadow:0 0 24px -4px var(--tool-accent),inset 0 0 30px rgba(10,14,34,.6)}
  .module .ph.is-entry.is-tool:hover{border-color:var(--tool-accent);box-shadow:0 0 40px -6px var(--tool-accent),inset 0 0 30px rgba(10,14,34,.5)}
  /* light-trail rides the (otherwise unused) ::before on tool cards */
  .ph.is-entry.is-tool::before{--trail-c:var(--tool-accent);
    content:"";position:absolute;inset:0;z-index:4;pointer-events:none;border-radius:16px;
    background:conic-gradient(from var(--tw-a),transparent 0 60%,var(--trail-c) 77%,#fff 86%,var(--trail-c) 95%,transparent 100%);
    -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;
    mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);mask-composite:exclude;
    padding:1.6px;opacity:0;transition:opacity .25s;animation:trailspin 1.9s linear infinite;animation-play-state:paused;display:block;top:auto;left:auto;right:auto;height:auto}
  .ph.is-entry.is-tool:hover::before{opacity:1;animation-play-state:running}
  .ph.is-entry.is-tool::before{display:none}                     /* drop the soft glow plate */
  .ph.is-entry.is-tool .ph-img{height:100%;top:0;padding:0;object-fit:cover}
  /* tool NAME sits bottom-left; at rest only the name shows. On hover the feature info slides in
     directly below the name. No box — the strong bottom GRADIENT (::after) carries the contrast. */
  .ph.is-entry.is-tool .ph-bullets{display:flex;flex-direction:column;max-height:0;opacity:0;margin:0;overflow:hidden;
    transition:max-height .32s cubic-bezier(.2,.7,.2,1),opacity .28s ease,margin .32s}
  .ph.is-entry.is-tool:hover .ph-bullets,.ph.is-entry.is-tool:focus-within .ph-bullets{max-height:120px;opacity:1;margin:5px 0 9px}
  .ph.is-entry.is-tool .ph-cap{font-family:var(--font-h);font-weight:800;font-size:clamp(20px,1.7vw,26px);
    text-transform:uppercase;letter-spacing:.01em;line-height:1.04}
  .ph.is-entry.is-tool .ph-pill{background:rgba(6,9,20,.7);border-color:var(--tool-accent)}
  /* taller, stronger scrim so the name + revealed info clear WCAG over any part of the scene image */
  /* even, full-width darkening from the bottom edge (was reading diagonal because the scene's
     bright case sits bottom-right); stronger mid-stops tame that so the scrim reads "from bottom" */
  .ph.is-entry.is-tool::after{height:90%;
    background:linear-gradient(0deg,rgba(4,6,14,.99) 0%,rgba(4,6,14,.96) 40%,rgba(4,6,14,.78) 62%,rgba(4,6,14,.32) 84%,transparent 100%)}
  .ph.is-entry.is-tool:hover{transform:translateY(-4px)}
  .ph.is-entry.is-tool:hover .ph-img{transform:scale(1.05)}

  /* ===== FLOATING GAMES → 2-row infinite marquee (uniform-height covers, label on top) =====
     z-index:6 puts the covers ABOVE the content card so hover/click is never blocked; the band
     is kept in the upper ~50% so it doesn't visually overlap the (lower) card. JS measures one
     set's width, clones enough sets to fill, and sets --fg-shift so the loop is truly infinite. */
  /* The band is the SINGLE stacking context for all rows (mask + clip-path make one context), and
     sits above the hint/card (z-8). clip-path clips the marquee horizontally (tight left/right) but
     is LOOSE vertically (negative top/bottom insets) so a hovered cover escapes up/down with no
     cutoff. Rows carry no mask/overflow, so a hovered cover's z-index:60 beats every other row and
     the card — true "top priority" on hover. */
  /* The mask's clip region is the band's BORDER BOX (mask-clip default) — anything painted outside
     it is erased, which is what cut hovered covers at the band's top/bottom edges. So the border box
     itself is grown with extra vertical padding (+60px top / +40px bottom escape room), cancelled by
     negative margins so the layout doesn't move. Enlarged covers now stay inside the masked box. */
  .floating-games{position:relative;left:auto;top:auto;right:auto;width:100%;z-index:8;pointer-events:none;
    display:flex;flex-direction:column;justify-content:flex-start;gap:clamp(9px,1.4vh,13px);
    padding:73px 0 53px;margin:-60px 0 -26px;overflow:visible;
    -webkit-mask-image:linear-gradient(90deg,transparent,#000 5%,#000 95%,transparent);
    mask-image:linear-gradient(90deg,transparent,#000 5%,#000 95%,transparent)}
  /* each .fg-track animates transform → it's its own stacking context, which would trap a hovered
     cover inside its row. So lift the whole hovered ROW above its siblings; the cover's z-index:60
     then wins inside that row → the hovered cover paints over every row and the card. */
  .fg-row{position:relative;overflow:visible;pointer-events:auto;z-index:1}
  /* hovered row jumps well above the others so its (enlarged) cover floats over EVERY row + the
     card. No will-change on the track: it pins a composited layer whose order won't update on
     hover, which is what kept the cover stuck to its own row. */
  .fg-row:hover,.fg-row:focus-within{z-index:40}
  .fg-track{display:flex;width:max-content}
  .fg-row-1 .fg-track{animation:fgScrollL 88s linear infinite}
  .fg-row-2 .fg-track{animation:fgScrollR 80s linear infinite}
  .fg-row-3 .fg-track{animation:fgScrollL 96s linear infinite}
  /* hover-name scales up with the bigger covers */
  .floating-game:hover .fg-label{font-size:13.5px}
  .fg-row:hover .fg-track{animation-play-state:paused}
  /* (fgScrollL/R keyframes are TOP-LEVEL, above this block — keyframes declared inside a media
     query only exist while it matches, which silently froze the mobile marquee) */
  .floating-game{position:relative;flex:0 0 auto;margin-right:24px;text-decoration:none;overflow:hidden;border-radius:10px;
    filter:drop-shadow(0 10px 22px rgba(0,0,0,.55));transition:transform .22s cubic-bezier(.2,.7,.2,1),filter .22s}
  /* FFVII Remake boxart ships with thin white bands top/bottom — scale it up a touch so the
     rounded cover crops them out (cover clips via overflow:hidden above) */
  .floating-game[data-ga-click="float-game-ffvii-remake"] img{transform:scale(1.07)}
  /* uniform height for every cover (short art scales UP to match); width follows aspect, no crop.
     Responsive so 3 rows + the roomy card fit short viewports and grow on tall ones. */
  .floating-game img{height:clamp(104px,14vh,146px);width:auto;display:block;border-radius:10px;
    border:1px solid rgba(150,215,255,.3);background:#0A0D1B}   /* opaque: several covers are transparent-canvas PNGs */
  /* game name shown CENTERED over the cover on hover (scrim keeps it readable over any art) */
  .fg-label{position:absolute;inset:0;z-index:8;display:flex;align-items:center;justify-content:center;text-align:center;
    padding:8px 10px;font-family:var(--font-b);font-size:12.5px;font-weight:700;letter-spacing:.01em;color:#fff;line-height:1.2;
    background:radial-gradient(ellipse 130% 85% at 50% 50%,rgba(6,9,20,.8),rgba(6,9,20,.58) 82%);border-radius:10px;
    opacity:0;transition:opacity .18s;pointer-events:none;text-shadow:0 1px 6px rgba(0,0,0,.9)}
  .fg-row:hover .floating-game{filter:drop-shadow(0 10px 22px rgba(0,0,0,.55)) saturate(.55) brightness(.68)}
  /* hovered cover pops forward and enlarges ~30% (title scales with it) */
  .floating-game:hover{transform:translateY(-6px) scale(1.3);z-index:60;filter:drop-shadow(0 0 22px rgba(255,133,0,.6))!important}
  .floating-game:hover img{border-color:var(--c-shop)}
  .floating-game:hover .fg-label{opacity:1}
  /* light-trail ring on each cover */
  .floating-game::after{--trail-c:#FFB258;content:"";position:absolute;inset:0;z-index:5;pointer-events:none;border-radius:10px;
    background:conic-gradient(from var(--tw-a),transparent 0 60%,var(--trail-c) 77%,#fff 86%,var(--trail-c) 95%,transparent 100%);
    -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;
    mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);mask-composite:exclude;
    padding:1.5px;opacity:0;transition:opacity .25s;animation:trailspin 1.9s linear infinite;animation-play-state:paused}
  .floating-game:hover::after{opacity:1;animation-play-state:running}
  @media(prefers-reduced-motion:reduce){.fg-row-1 .fg-track,.fg-row-2 .fg-track,.fg-row-3 .fg-track{animation:none}}

  /* ===== GEAR CARDS (VR & Gear categories → reference-style visual cards) ===== */
  /* gear/category card layout per scene (visual base is top-level; see .gear-card there).
     Shop = 6-up single row; Build & Setup = 4-up two rows (8 categories). */
  /* filing row: build/setup pack 8 tabs, shop 6 — flex:1 sizes them; tighter on build/setup */
  /* one shared tab spec across ALL three sections — Shop matches Build/Setup exactly */
  .module.build .gear-card,.module.setup .gear-card,.module.shop .gear-card{height:150px;gap:7px;padding:12px 5px 11px}
  .module.build .gear-label,.module.setup .gear-label,.module.shop .gear-label{font-size:11px}
}
/* ===== < 1200px: new components flow instead of float/overlay ===== */
@media(max-width:1199px){
  .parts-aside{margin-top:10px;background:linear-gradient(165deg,rgba(20,22,52,.6),rgba(10,12,28,.4));border:1px solid rgba(150,215,255,.2);border-radius:14px;padding:16px 16px 18px}
  .parts-aside-title{font-family:var(--font-b);font-size:13px;color:rgba(224,228,248,.78);margin-bottom:10px}
  .parts-aside-title b{font-weight:800;color:#fff}
  /* mobile: keep the two-row marquee but smaller covers, static (non-elevating) layer */
  .floating-games{position:static;height:auto;display:flex;flex-direction:column;gap:22px;margin:4px 0 18px;overflow:hidden;
    -webkit-mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent);mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent)}
  .fg-row{overflow:visible;pointer-events:auto}
  .fg-track{display:flex;width:max-content}
  /* Durations are per-CYCLE, and a cycle here is ONE SET — which on mobile is the 5-cover roster
     (~600px), not the desktop 2737px stride. Carrying the desktop-magnitude 160/184/208s over
     therefore made the covers crawl at ~3.5px/s (measured) versus ~31px/s on desktop: with the
     rows also only running WHILE the user scrolls, a whole scroll gesture moved them ~3px and the
     marquee read as completely static. 20/23/26s restores ~30px/s at a 600px set, i.e. the same
     perceived speed as PC. If the roster size ever changes, re-derive these from
     setWidth / 30px-per-second — do not copy the desktop numbers. */
  .fg-row-1 .fg-track{animation:fgScrollL 20s linear infinite}
  .fg-row-2 .fg-track{animation:fgScrollR 23s linear infinite}
  .fg-row-3 .fg-track{animation:fgScrollL 26s linear infinite}
  /* the desktop block has this same override at its own scope — WCAG 2.2.2/2.3.3 */
  @media(prefers-reduced-motion:reduce){.fg-row-1 .fg-track,.fg-row-2 .fg-track,.fg-row-3 .fg-track{animation:none}}
  /* no hover-pause on touch: mobile browsers keep :hover latched after a tap, which froze rows */
  /* how-to-shop hint becomes a centered caption above the stacked marquee */
  .shop-hint{display:flex;width:fit-content;margin:2px auto 12px;align-items:center;gap:7px;
    font-family:var(--font-b);font-size:12px;font-weight:600;color:#EAF2FF;
    background:rgba(6,9,20,.7);border:1px solid rgba(150,215,255,.34);border-radius:999px;padding:6px 14px}
  .shop-hint-ico{width:14px;height:14px;color:var(--c-shop);display:inline-flex}
  .shop-hint-ico svg{width:100%;height:100%}
  .floating-game{position:relative;flex:0 0 auto;margin-right:16px;transform:none}
  .floating-game img{height:120px;width:auto;display:block;border-radius:8px;border:1px solid rgba(150,215,255,.3);background:#0A0D1B}
  .floating-game .fg-label{position:absolute;left:0;right:0;top:0;padding:6px 6px 12px;text-align:center;opacity:0;font-size:10.5px;font-weight:700;color:#fff;line-height:1.12;background:linear-gradient(180deg,rgba(6,9,20,.96),rgba(6,9,20,.7) 72%,transparent);border-radius:8px 8px 0 0;white-space:normal}
  .floating-game:hover .fg-label{opacity:1}
  /* mobile: no skew/dim (no hover on touch) — a flat colored grid */
  .gear-cards{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;padding:0}
  .gear-card{transform:none;filter:none;flex:none;height:104px;
    border-color:var(--cat-line);
    background:radial-gradient(ellipse 80% 60% at 50% 28%,var(--cat-glow),transparent 68%),linear-gradient(165deg,var(--cat-base),rgba(9,11,24,.9) 62%)}
  .gear-card>*{transform:none}
  .gear-head{flex-wrap:wrap;gap:6px}
}
/* small phones: 2-up category tabs (the covers keep their natural aspect — a fixed anchor width
   here used to clip them) */
@media(max-width:640px){ .gear-cards{grid-template-columns:repeat(2,1fr)} }
/* 3-up cards are narrower — slightly tighter type/padding so bullets + CTA still fit */
.module.build .ph.is-entry,.module.setup .ph.is-entry{padding:14px}
.module.build .ph.is-entry .ph-cap,.module.setup .ph.is-entry .ph-cap{font-size:clamp(14px,1.25vw,17.5px)}
.module.build .ph.is-entry .ph-bullets li,.module.setup .ph.is-entry .ph-bullets li{font-size:13px;color:#fff}
.module.build .ph-entry-cta,.module.setup .ph-entry-cta{height:36px;padding:0 14px;font-size:12px}
.ph.is-entry{border-style:solid;align-items:flex-end;justify-content:flex-end;text-align:left;gap:5px;background:#080A18;padding:18px}
/* soft glow plate behind product images so dark products don't sink into the card */
.ph.is-entry::before{content:"";position:absolute;top:6%;left:12%;right:12%;height:60%;background:radial-gradient(ellipse 75% 65% at 50% 48%, rgba(125,135,190,.20), transparent 72%);pointer-events:none}
.ph.is-entry .ph-img{position:absolute;inset:0;width:100%;height:78%;top:3%;object-fit:contain;z-index:0;padding:10px;transition:transform .5s cubic-bezier(.2,.7,.2,1)}
.ph.is-entry .ph-img.scene{object-fit:cover;height:100%;top:0;padding:0}
/* box-art images (e.g. the Games card) ship on Newegg's CDN as a tall poster centered in a
   4:3 canvas with wide blank margins baked in — object-fit:contain has no way to crop that
   padding, so the poster reads much smaller than a product shot that fills its own canvas.
   Scale the image itself up so the artwork (not the margin) fills the card; .ph overflow:hidden
   clips the excess. */
.ph.is-entry .ph-img.is-poster{transform:scale(1.4)}
.ph.is-entry::after{content:"";position:absolute;inset:auto 0 0 0;height:44%;z-index:1;background:linear-gradient(0deg,rgba(5,6,14,.99) 0%,rgba(5,6,14,.72) 55%,transparent 100%)}
/* 3D tilt: JS writes --rx/--ry via mousemove (pointer:fine desktop only), CSS just consumes
   them here so the existing scale(1.025) hover isn't clobbered by an inline transform. Default
   0deg means non-JS/touch/reduced-motion visits get identical behavior to before this feature. */
.ph.is-entry{--rx:0deg;--ry:0deg}
.ph.is-entry:hover{transform:scale(1.025) rotateX(var(--rx)) rotateY(var(--ry));border-style:solid}
.ph.is-entry:hover .ph-img{transform:scale(1.06)}
.ph.is-entry:hover .ph-img.is-poster{transform:scale(1.48)}
.ph.is-entry .ph-cap{position:relative;z-index:2;color:#fff;font-size:clamp(17px,1.6vw,20.5px);font-weight:700;font-family:var(--font-h);letter-spacing:-.005em;text-transform:none;line-height:1.15;text-shadow:0 1px 6px rgba(0,0,0,.7)}
.ph.is-entry .ph-sub{position:relative;z-index:2;font-size:13px;color:rgba(255,255,255,.78);font-weight:500;margin-bottom:11px;text-shadow:0 1px 4px rgba(0,0,0,.6)}
/* short feature bullets — replaces the single subtitle line on entry cards */
.ph.is-entry .ph-bullets{position:relative;z-index:2;list-style:none;margin:0 0 11px;padding:0;display:flex;flex-direction:column;gap:4px}
.ph.is-entry .ph-bullets li{font-family:var(--font-b);font-size:12px;font-weight:500;color:rgba(255,255,255,.8);letter-spacing:.01em;text-shadow:0 1px 4px rgba(0,0,0,.6);display:flex;align-items:center;gap:7px;line-height:1.3}
.ph.is-entry .ph-bullets li::before{content:"";width:4px;height:4px;border-radius:50%;background:currentColor;opacity:.65;flex-shrink:0}
/* browsing strip instead of one static poster — communicates "there's a whole catalog here" the
   way a single hero image can't. Overrides the base .ph.is-entry flex-end/bottom-gradient
   styling (built for one full-bleed image) since this card has no single image to gradient over. */
/* justify-content:space-between (not flex-start) — the card is a fixed 440px (matching its
   sibling via .entry-cards{height:440px}), but the track's actual content is nowhere near
   that tall. flex-start left a huge stretched-empty gap *inside* the track (align-items:stretch
   was blowing up each item's height to fill it); space-between instead puts that leftover
   space as breathing room between the track and the CTA, with items sized to their own content. */
.ph.is-entry.games-scroll{align-items:stretch;justify-content:space-between;gap:12px}
.ph.is-entry.games-scroll::before,.ph.is-entry.games-scroll::after{display:none}
/* margin-top clears the .ph-pill badge (absolute, top:13px) — this card has no full-bleed
   background image for the pill to float over like its siblings, so the track (a real flow
   element here) needs actual headroom instead. */
.games-scroll-track{display:flex;gap:10px;overflow-x:auto;scroll-snap-type:x mandatory;align-items:flex-start;margin:22px -18px 0;padding:0 18px 6px}
.games-scroll-track::-webkit-scrollbar{height:5px}
.games-scroll-track::-webkit-scrollbar-thumb{background:rgba(255,255,255,.2);border-radius:3px}
.games-scroll-item{position:relative;flex:0 0 118px;scroll-snap-align:start;border-radius:10px;overflow:hidden;display:flex;flex-direction:column;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.1);transition:border-color .2s,transform .2s}
.games-scroll-item:hover{border-color:var(--c-shop);transform:translateY(-3px)}
.games-scroll-item:focus-visible{outline:2px solid var(--accent-2);outline-offset:2px}
/* contain, never cover — the boxart shows exactly as published, no cropping */
.games-scroll-item img{width:100%;height:158px;object-fit:contain;display:block;background:rgba(4,6,16,.5);padding:4px}
.games-scroll-title{font-family:var(--font-b);font-size:11.5px;font-weight:600;color:rgba(255,255,255,.85);padding:7px 7px;line-height:1.25}
@media(prefers-reduced-motion:reduce){.games-scroll-item{transition:none}.games-scroll-item:hover{transform:none}}
/* keyword / subcategory search chips — replaces the SKU .more-products strip with durable
   search links that don't go stale when inventory changes. Kept visually light so they stay
   secondary to .module-cta and the entry cards. */
/* label that separates the direct-shopping category chips from the tool entrance cards */
.module-kw-title{font-family:var(--font-b);font-size:11px;font-weight:700;letter-spacing:.2em;text-transform:uppercase;color:rgba(235,235,250,.6);margin:20px 0 0}
.module-keywords{display:flex;flex-wrap:wrap;gap:8px;margin:10px 0 0}
/* category chips living INSIDE a holo card (Gaming & VR) */
.ph-kw{position:relative;z-index:2;display:flex;flex-wrap:wrap;gap:7px;justify-content:flex-start;margin:2px 0 10px;max-width:100%}
.ph-kw .module-kw{font-size:11px;padding:6px 11px}
.module-kw{display:inline-flex;align-items:center;font-family:var(--font-b);font-size:12px;font-weight:600;letter-spacing:.03em;padding:7px 13px;border-radius:999px;transition:color .2s,background .2s,border-color .2s,box-shadow .2s,transform .2s;white-space:nowrap;text-decoration:none}
.module-kw:hover{transform:translateY(-1px)}
.module-kw:focus-visible{outline:2px solid var(--accent-2);outline-offset:2px}
/* section-tinted chips — readable at rest, stronger on hover */
.module.build .module-kw{color:#C4B5FD;background:rgba(107,95,239,.16);border:1px solid rgba(155,106,255,.42)}
.module.build .module-kw:hover{color:#fff;background:rgba(107,95,239,.32);border-color:rgba(155,106,255,.75);box-shadow:0 0 16px rgba(107,95,239,.28)}
.module.shop .module-kw{color:#FFB86A;background:rgba(255,133,0,.14);border:1px solid rgba(255,133,0,.42)}
.module.shop .module-kw:hover{color:#fff;background:rgba(255,133,0,.28);border-color:rgba(255,133,0,.75);box-shadow:0 0 16px rgba(255,133,0,.25)}
.module.setup .module-kw{color:#8FC2FF;background:rgba(0,112,204,.14);border:1px solid rgba(0,112,204,.42)}
.module.setup .module-kw:hover{color:#fff;background:rgba(0,112,204,.28);border-color:rgba(0,112,204,.75);box-shadow:0 0 16px rgba(0,112,204,.25)}
/* parts-aside chips: opaque dark fill (not the translucent section tint) so the light-colored
   chip text clears WCAG AA; section identity kept via the colored border. (0,4,0) out-ranks
   the per-section chip fill rules above. Hover still floods the section color. */
.module.build .parts-aside .module-kw,.module.setup .parts-aside .module-kw{background:rgba(10,12,30,.9)}
.module.build .parts-aside .module-kw:hover{background:rgba(107,95,239,.42)}
.module.setup .parts-aside .module-kw:hover{background:rgba(0,112,204,.42)}
@media(prefers-reduced-motion:reduce){.module-kw{transition:none}.module-kw:hover{transform:none}}
/* shop-the-look hotspots — percentage top/left so they track the responsive photo underneath.
   z-index:3 clears both the ph-img (z-index:0) and the bottom-gradient ::after (z-index:1). */
.hotspot{position:absolute;transform:translate(-50%,-50%);z-index:3;display:flex;align-items:center;justify-content:center;width:22px;height:22px}
/* ::after (not ::before — that's already the pulsing ring below) bumps the touch target to
   ~44px without enlarging the visible dot or its ring. */
.hotspot::after{content:"";position:absolute;inset:-11px}
.hotspot.tapped .hotspot-dot{transform:scale(1.3)}
.hotspot.tapped .hotspot-tip{opacity:1;transform:translateX(-50%) translateY(0)}
.hotspot-dot{width:12px;height:12px;border-radius:50%;background:rgba(255,255,255,.9);border:2px solid var(--c-comm);box-shadow:0 0 0 4px rgba(0,112,204,.25),0 0 12px rgba(0,112,204,.6);transition:transform .2s}
.hotspot::before{content:"";position:absolute;inset:-6px;border-radius:50%;border:1.5px solid rgba(0,112,204,.5);animation:hotspotPulse 2s ease-out infinite}
.hotspot:hover .hotspot-dot,.hotspot:focus-visible .hotspot-dot{transform:scale(1.3)}
.hotspot-tip{position:absolute;bottom:calc(100% + 8px);left:50%;transform:translateX(-50%) translateY(4px);white-space:nowrap;background:rgba(6,7,15,.92);border:1px solid rgba(0,112,204,.4);border-radius:8px;padding:5px 10px;font-family:var(--font-b);font-size:11px;font-weight:600;color:#fff;opacity:0;pointer-events:none;transition:opacity .2s,transform .2s}
.hotspot:hover .hotspot-tip,.hotspot:focus-visible .hotspot-tip{opacity:1;transform:translateX(-50%) translateY(0)}
@keyframes hotspotPulse{0%{opacity:.8;transform:scale(1)}100%{opacity:0;transform:scale(1.6)}}
@media(prefers-reduced-motion:reduce){.hotspot::before{animation:none}.hotspot-dot,.hotspot-tip{transition:none}}
/* NO `width:fit-content` here (QA #4, Firefox): this pill is a flex item of .cut-wrap, whose own
   width is content-derived — so fit-content had to resolve against an indefinite containing block.
   That is the classic circular-sizing construct engines disagree on: Chrome resolved it to
   max-content, Firefox collapsed the pill to a sliver (only a wedge of yellow left, label gone).
   An inline-flex box already shrink-wraps, so the declaration was redundant in Chrome anyway.
   flex:0 0 auto + min-width:max-content make the collapse impossible BY CONSTRUCTION rather than
   by betting on one engine's resolution: the parent may never shrink the pill (the default
   flex:0 1 auto let it), and it can never be narrower than its own label. Same hardening on
   .module-cta / .pc-cta — every CTA sits in a content-sized .cut-wrap. */
.ph-entry-cta{z-index:2;height:40px;padding:0 20px;font-family:var(--font-ui);font-size:13px;font-weight:700;letter-spacing:0;cursor:pointer;display:inline-flex;align-items:center;gap:7px;white-space:nowrap;transition:background .18s ease,transform .22s cubic-bezier(.2,.7,.2,1);flex:0 0 auto;min-width:max-content}
.ph-entry-cta:hover{background:var(--ne-yellow-hover);transform:translateY(-2px)}
.ph-entry-cta:active{background:var(--ne-yellow-active);transform:translateY(0) scale(.98);transition-duration:.08s}
/* per-section gradient fills retired — all section CTAs are NE Yellow pills now (see the
   .module-cta/.ph-entry-cta base rules above) */
.module.build .cut-wrap:has(.ph-entry-cta){filter:drop-shadow(0 4px 16px rgba(107,95,239,.4))}
.module.shop .cut-wrap:has(.ph-entry-cta){filter:drop-shadow(0 4px 16px rgba(255,133,0,.4))}
.module.setup .cut-wrap:has(.ph-entry-cta){filter:drop-shadow(0 4px 16px rgba(0,112,204,.4))}

/* ===== DEPTH DECK (each module rises from behind & replaces the previous) ===== */
/* 100vh viewport + 3 transitions × 160vh each (DECK_SPAN=1.6 in JS) — slower, smoother ride */
.deck{position:relative;z-index:1;height:580vh}
.deck-viewport{position:sticky;top:0;height:100vh;overflow:hidden}
.deck-card{position:absolute;inset:0;overflow:hidden;will-change:transform,opacity;transform-origin:center center;backface-visibility:hidden}
/* transition-only feedback flash — a thin sweep line, gated to opacity 0 outside the t=0.5-0.7
   crossover window by updateDeck(). mix-blend-mode:screen so it reads as a light flare against
   the dark theme rather than a flat colored bar. */
.deck-fx{position:absolute;inset:0;z-index:50;pointer-events:none;opacity:0;overflow:hidden;mix-blend-mode:screen}
.deck-fx::before{content:"";position:absolute;left:0;right:0;top:-5%;height:2px;background:linear-gradient(90deg,transparent,rgba(155,106,255,.9),rgba(255,133,0,.7),transparent);animation:fxsweep .5s linear infinite}
@keyframes fxsweep{0%{top:-5%}100%{top:105%}}
@media(prefers-reduced-motion:reduce){.deck-fx{display:none}}

/* ===== FAQ (native <details>, sits after the deck — see the markup comment for the why) =====
   Purple side of the palette so it reads as part of this page and not a bolted-on SEO block:
   --accent-2 #9B6AFF is the same purple .module.build uses for --tool-accent.
   The panel is glass on desktop, where nothing sits behind it but the page's own #06070F ground.
   On phones it gets a near-solid backing instead — .deck-video-layer is position:fixed there, and
   sampling the canvas behind that region gave 1.25:1 on the brightest neon frame. A large block of
   answer prose can afford that even less than the one-line freshness strip did. */
.faq{position:relative;padding:52px 22px 20px}
.faq-inner{max-width:1200px;margin:0 auto;
  background:linear-gradient(145deg,rgba(14,14,36,.82),rgba(8,9,22,.45));
  backdrop-filter:blur(14px);
  border:1px solid rgba(255,255,255,.09);border-top:1px solid rgba(155,106,255,.8);
  border-radius:24px;padding:30px 32px 12px;
  box-shadow:0 24px 64px rgba(0,0,0,.35),0 0 40px rgba(155,106,255,.06),inset 0 1px 0 rgba(255,255,255,.06)}
.faq-title{font-family:var(--font-h);font-size:26px;font-weight:800;letter-spacing:.01em;
  color:var(--text);margin:0 0 6px}
.faq-item{border-bottom:1px solid var(--border)}
.faq-item:last-child{border-bottom:none}
/* list-style + ::-webkit-details-marker both needed: Safari uses the pseudo-element, others the
   list marker. Without both a stray triangle sits next to our own caret. */
.faq-item summary{list-style:none;cursor:pointer;display:flex;align-items:center;gap:14px;
  justify-content:space-between;padding:17px 2px;position:relative}
.faq-item summary::-webkit-details-marker{display:none}
.faq-item summary h3{margin:0;font-family:var(--font-b);font-size:16px;font-weight:600;
  line-height:1.45;color:var(--text);transition:color .2s}
.faq-item summary:hover h3{color:var(--accent-2-hi)}
.faq-item[open] summary h3{color:var(--accent-2-hi)}
.faq-caret{flex:0 0 auto;font-size:14px;color:var(--accent-2-hi);
  transition:transform .25s cubic-bezier(.2,.7,.2,1)}
.faq-item[open] .faq-caret{transform:rotate(90deg)}
.faq-a{padding:0 2px 18px}
.faq-a p{margin:0;font-size:15px;line-height:1.68;color:rgba(212,212,235,.93)}
.faq-item summary:focus-visible{outline:2px solid var(--accent-2-hi);outline-offset:3px;border-radius:8px}
@media(prefers-reduced-motion:reduce){
  .faq-caret,.faq-item summary h3{transition:none}
}
@media(max-width:1199px){
  /* Lift above the fixed video layer AND give it real opacity — the two lessons from .page-updated
     shipping invisible. Do not reduce this alpha: the footage underneath peaks bright enough that
     even .94 left body text at 3.87:1. */
  .faq{z-index:2;padding:34px 14px 14px}
  .faq-inner{background:rgba(4,6,14,.97);backdrop-filter:blur(10px);padding:24px 20px 10px;
    border-radius:20px}
  .faq-title{font-size:22px}
  .faq-item summary{padding:15px 2px}
  .faq-item summary h3{font-size:15.5px}
  .faq-a p{font-size:15px}
}

/* Freshness line, last element inside .page-content-root — see the markup comment for why the
   wording is what it is. Deliberately NOT position:fixed or sticky: it must scroll away like real
   content, because the point is that a crawler reads it as page copy. --sub on the page's own
   #06070F ground folds to 6.04:1 after the .72 opacity, so it stays AA at this size and weight.
   Bottom padding is deliberately small: .page-content-root already ends ~60px above the injected
   footer (measured — that gap predates this element), so anything generous here reads as dead
   space rather than breathing room.
   The dimming lives in the COLOUR's alpha, never in `opacity`. Element opacity would fade the
   backing the mobile rule adds below along with the text, so a .9 strip would land at an effective
   .65 and lose the contrast it exists to provide. */
.page-updated{
  margin:0;padding:26px 22px 10px;text-align:center;
  font-family:var(--font-b);font-size:13px;font-weight:500;letter-spacing:.02em;
  color:rgba(190,190,222,.72)}
.page-updated time,.page-updated .byline-org{font-weight:700;color:rgba(238,238,255,.92)}
/* On phones the deck video layer is position:fixed, so this line sits ON the footage rather than on
   the page's own ground — the 6.04:1 figure above only holds on desktop. Sampled the canvas pixels
   behind the strip (after the layer's own rgba(6,8,20,.46) scrim): darkest frame 8.30:1, average
   4.42:1, brightest neon frame 1.25:1. So it needs its own backing, the same conclusion .ph-pill
   reached for the same reason. Do not swap this for a text-shadow: a shadow cannot rescue 1.25:1.
   The strip is FULLY opaque on purpose. At .94 the brightest frame still bled through enough to
   leave the body text at 3.87:1; going solid decouples the ratio from the footage entirely, so it
   reads the same 6.0:1 as desktop no matter which frame is under it. */
@media(max-width:1199px){
  .page-updated{padding:14px 18px;font-size:14px;
    background:#04060E;
    border-top:1px solid rgba(255,255,255,.07)}
}
.swipe-hint{display:none}
/* Module backgrounds are now TRANSLUCENT SCRIMS over the shared HP-BG video (the old
   per-section Pexels photos are retired). Directional gradients keep the text column dark
   and readable while the video's shop interiors show through the open side. */
.deck-card.module{background:rgba(7,9,24,.28)}
.deck-card.module.shop{background:
  linear-gradient(270deg, rgba(6,7,15,.88) 0%, rgba(6,7,15,.52) 42%, rgba(255,133,0,.08) 62%, rgba(6,7,15,.80) 100%)}
.deck-card.module.build{background:
  linear-gradient(90deg, rgba(6,7,15,.88) 0%, rgba(6,7,15,.52) 42%, rgba(107,95,239,.08) 62%, rgba(6,7,15,.80) 100%)}
.deck-card.module.community{background:
  linear-gradient(90deg, rgba(6,7,15,.86) 0%, rgba(6,7,15,.50) 40%, rgba(0,112,204,.08) 60%, rgba(6,7,15,.80) 100%)}
.deck-card.module.setup{background:
  linear-gradient(90deg, rgba(6,7,15,.88) 0%, rgba(6,7,15,.52) 42%, rgba(0,112,204,.08) 62%, rgba(6,7,15,.80) 100%)}

/* ===== DECK BACKGROUND VIDEO (HP-BG, scrubbed by scroll — see home_page_demo.js) =====
   z:-1 keeps it under every deck card (hero is opaque and covers it while front; passed
   cards fade to opacity 0, so the video shows through whichever module is on screen). */
.deck-video-layer{position:absolute;top:0;left:0;right:0;bottom:0;inset:0;z-index:-1;overflow:hidden;pointer-events:none;
  /* ⚠ NOT decoration — this is what the visitor looks at until the scrub video has a frame.
     The presentation canvas below is TRANSPARENT until its first paint, so with no background
     here the page's own near-black #06070F showed through: measured cold, that black window ran
     from first paint until the canvas lit (5-13s desktop, up to 15s on a 5Mbps phone). The
     .video-fallback class paints a scene too, but it is JS-state-driven and only lands once the
     poster probe / unlock callbacks have run — it was still absent at t=2000ms and only appeared
     at 2500ms, so it cannot cover the opening window. Declaring the gradient on the base rule
     needs no JS at all and is therefore correct from the very first frame the browser paints. */
  background:
    radial-gradient(ellipse 80% 60% at 30% 20%, rgba(107,95,239,.25), transparent 60%),
    radial-gradient(ellipse 70% 55% at 75% 75%, rgba(255,133,0,.16), transparent 60%),
    linear-gradient(180deg,#080A18,#05060E)}
/* One 64x36 WEBP per section (600-800 bytes each, ~3.7KB of base64 for all four), inlined so they
   cost ZERO requests and are painted with the stylesheet — a separate file would arrive after the
   window they exist to cover. Upscaled ~30x by background-size:cover, which is exactly the soft
   blur we want; the section's real colour and composition read through, and the layer's ::after
   darkening sits on top so it matches the video's tonality. Frames sampled at the DECK_VIDEO_KEYS
   anchors (1.2 / 3.55 / 9.5 / 14.0s) so each one is the scene that card actually maps to.
   Stacking: these sit under the JS-appended canvas (later DOM = painted later), so a decoded
   frame simply covers them; .deck-lit then drops them out of the compositor entirely. In the
   App-webview case (QA A-8) the canvas is display:none and these become the fallback scene —
   strictly better than the flat gradient that case used to get. */
.deck-scene{position:absolute;top:0;left:0;right:0;bottom:0;inset:0;
  background-size:cover;background-position:center;background-repeat:no-repeat;
  opacity:0;transition:opacity .45s ease}
.deck-scene[data-i="0"]{opacity:1}   /* pre-JS default: hero's scene */
.deck-video-layer[data-scene="0"] .deck-scene[data-i="0"],
.deck-video-layer[data-scene="1"] .deck-scene[data-i="1"],
.deck-video-layer[data-scene="2"] .deck-scene[data-i="2"],
.deck-video-layer[data-scene="3"] .deck-scene[data-i="3"]{opacity:1}
.deck-video-layer[data-scene="1"] .deck-scene[data-i="0"],
.deck-video-layer[data-scene="2"] .deck-scene[data-i="0"],
.deck-video-layer[data-scene="3"] .deck-scene[data-i="0"]{opacity:0}
/* real frame on the canvas — stop compositing four full-viewport layers behind it.
   :not(.video-fallback) matters for ?nv=1: that switch forces the fallback class onto a HEALTHY
   video, so the canvas gets hidden while deck-lit is already set — without this guard the review
   mode showed the flat gradient instead of the scenes a real no-decoder webview would show
   (there deckHasFrame never flips, so deck-lit never lands and the guard is moot). */
.deck-video-layer.deck-lit:not(.video-fallback) .deck-scene{display:none}
@media(prefers-reduced-motion:reduce){.deck-scene{transition:none}}
.deck-scene[data-i="0"]{background-image:url(data:image/webp;base64,UklGRuwCAABXRUJQVlA4IOACAAAwDQCdASpAACQAPpVAmkklo6IhMBK6qLASiWYAnTNVjCeQumoBf7t6w/9LgoGtF+gBP58RYtfwd0zzSxwu1X++KZVeEn2sQ0JU+UfL+nOcLGBXIMlsCv1gRdWsTJynZTHO+OdEO+GuHx9Ou4kJYen38gAA/voLuTI9IalYQ+VSMvdjrrH3sIxUjh8/1YbvZgsMsEkIytc/9QG1+ECU85fCTgOh7VPd4P6JnQn0oHMhuFiGnRlrNKCR/dtyKk2jnM1ui1cdScImdMOY80gVlek6hxuXUWCJtILdl5z5U+GlMN5Uy8JGZhJM1GS4iOY8mPi3tKJtK15W6FJdvdOKzlW8blptB+fa+YNe+F047NWetwSjTh0eOhqIjML66xbqmSaFITnGt5XovHmFGS0bX7y6AVrFg8DIGufPSyf9Fs5RoJr/EnhxmfoqSWATKlFYK/SZlxy2/6vk8xHxX//MZzDj/HlnSAgpf8vOmUlv5eq6NB6kjCh60xMj4xAhPJipPZ6K1ox54PTph71OvSFg0bv4L3nMyDdig+QhwxjyEmGieqFQK4ho9YXWjJ9+f8Cxr0C2i3H6XS/kyiti/y0W/IhqFNWlqnAmlW+EIIJOE8wtBv6Ps6zlhv0kEYm5k/L247A6F2+HZB1JsGoF+sHeGeEzB5LIgxiWmZ//9UeeXPLsqs2+94dAflJ1nABO1yx9/qnxsgWLCoSzhQLGgWVDyiClfb7zwNFJ2/borKKD1VT5EgegTUAZaDeXYQh+DEcuox9EjQHdxm4Mhb/G9j560z/s9xVI3D25+3zvnEYKQIVa+7iWI0YIVT/Cj9J8Ek2p6kF63b7rP2xB8NDUaONz8d0YUeKb85HsUIjDfuxGDPhD6QbaxtxdZWBCFS5ht8djPhT2aewmg4Tcb9M9y6RxGzoC/ISyVffjHOQ4Ohr4MTy+Ef3UFO/zsVLGTIeHLZh12cUNss9PZZtPWGMbyADo4AAA)}
.deck-scene[data-i="1"]{background-image:url(data:image/webp;base64,UklGRpoCAABXRUJQVlA4II4CAAAwDgCdASpAACQAPpU8l0glo6IhMrSbMLASiWQApeWN1L5IZm+F/0ZjfPgXdBAk+pQsGuUE5NRkgv0wdSs6CM1cwSfVdVMV20y/MIrlz8a3zMuYpDVw90/HWlb29WvqViuP468rlkbQ5GEht0WxiUqxU1W9pXAHi90zgAD9wgXPGJwCH903t/YP+j1M+Jp7F/w0OkRCv6t0GSLGbA0470Hun8TajJdKqukTkKMPl2/wD1J5V6RQuVYHKUU0h+Yf0QtKhy5yycC5ke+bhxJWGcGZADX3DWq+NqnboHfQJVzyKZVdFjrr+O8ifJFPcmgbeoFzJ2i0OmX8CXE/XQ373Jd1V0sBr/P4D4P/fi4WcoMoOJ46sPcm+QCzM0X6tdXe+ws0H5MsC6/s8fjKXqRS+tr+DHywj6XAEM94bp0vlq+YjRC4L7X/wF2CrqMh9oN4zOfL2fPkL5MfqjUUeUoo3JfIx3VAl/UJkX5S4LqMmoccB5BXVkRajSpDWm3X2lVcnYkyFN5F0o6CTNTW+nf/25utomlh1atLhQ8R3QkaBhdOqtT1lCyQFPr93PcYLPHgOlvgJ2TZ3hxiGD1vdWdSM6lp/4ZcfougOn8kPqEkmR50wcmS+Kx9+eCS779fa27r3TFvBU8h3F6W1yDFHBSuiFk+6whlTRmFM6wZX2OKdWi4s4LSJICkWmIhKOsFti4afs0VL/o5XQMYFy/787Rdss+ggkX0329uiO26P6/QRIiIEK57nfQOcxl+14k4+vEv1i2XoRDP37G0AeqRCFreI7l1KnqVf69FeDPHJTH/yxtTVLYlw+FLfHhRD+WyTWL0O9I3vnJwDB7YxwLvl8YWok4gfMglr2STl4IF3+Z/oAA=)}
.deck-scene[data-i="2"]{background-image:url(data:image/webp;base64,UklGRgQDAABXRUJQVlA4IPgCAACwDgCdASpAACQAPpVCmUklpCIhKrv8ALASiWIAnTOOjCgnPHPwu5Db6Pn/30BPkeff25JAOA2fp2ct+qs+rWorSILKJa7xK8w/xe0Uh3gkUZb20WaZ3uVu+r5ahdBG420giINCl2Ls7TNBuHQt8rDFXqAQhd+mEhTb2SJr2NgA/v6hVzfY7f7jRFSExqY9Y/tyfDZZ71ibaCKwTAnHILB6Ug2MN7e7tHkUBx9OLOB9umE342Y4TGvszV1K1v+1dLlRia1rDxCQWxTjWC6F7zbwbOweXgJI9Jh92p56eILEDzH4HW5zb9yDdt8CDnrg9zBnsZZ/M9oVGZ35wjPXiM1ITPb5ViiuHtptB8xYLN4MV093o+AbaN+BuV1GPBSSN6/ccH3Km2TY1tYB3BRmLR74ZSvWAhrzfrb5tnSEfRmYY/+Tey1+UU1YzKe3HiHNIHsDDYFMWMPEWyjWL1cl2E2khNcdrnyj2s9N/VPJOW6gzvBtLTm4Sk5AC2ES2TlJdaO9LMvnVTgntLideZrPfaVP7k6gAzhGNoxcryEm0PHb9M+KPjeYfBuy05Nj2sp1WeLunr3oIQJrpr5OGN/KS/zRexl2zYDVfrXn1DPqTRwruK28IOVV2/6ovUTU5Diby5qF5WyTbaX1T1AUR+vE61hQiCXbRjgWNY8iub6kCait8b1U5dpix05Q7bfI93BI6Spxfy7ktnoUY5eXL4urhnu0jpqKCbULoGhY/+Y4Aaj6IaaNYyi/AIUaOzhDezKVbjurUytIEIM03BS3H3q/97p3nhMo6oKnE10KAqb1Bi83C4ObYXmpj6n66Epsteg4Ifbedy/twnb3j2uTKpXOERWjN8+RKXL/nTv30yWWkHg6iq4nbIse/gcSkw0f2o+L70WeTkPzm4dgBlL5BF7y0YBfgy61lqB0YuY+BBBVJasJmzs6uybDlgJop5UugmB/lYAWmeNq8ERyQTPsgavi8T29i+Ttl0I0xWm6bMfX/5rAj/rjXSlPHYAA)}
.deck-scene[data-i="3"]{background-image:url(data:image/webp;base64,UklGRnICAABXRUJQVlA4IGYCAABQDQCdASpAACQAPok0k0glIqGhNV38AKARCWgAnTMz0WJlhf/Ru2+DfboFY8/MXbwo2Axjq2AHGi2oMLx38wPTx70VNyYSsYvv5QbTO38dfdhmAaPK79Kkpbklw9LYnC7/YZQdXvmrmcHkzo+Lnxjuw/MwAP78qeC2LlZNv03bVVKer0jJOXmGVvOXF56EbvZ9x/k2RMww/kMHoPxkiCmqksFAy1QQmjeB6IA765J2b4qHSAVJydssKsqZn4RwoKNHroe/zB/fqvUC+Y7gFcnRLtKls+pk4LJhh+EXrV61krL17lgmuoPSdLNQnEuZ7Q6BQE1FCKf3CD7nZFaRA6ZK38bosNfcmZuC801631EpX9zeNfYVTQWplM6Tp9gHx9iAtLo6vucz2RpDH7BupQ/iKV3W3SrJf97a4OnA7iPdgsmLDJeYVGUKE+pYBUleBYf5mUdpQeVSXAsuxVgmdNjOmNo7c1KP3o1p09EHsvR60zcrdez+pGsx3R+FDh0Z5gWoIAeI022vssezpl6wzX5zQLudfq1aGRmuQoNK6O0NPzRv96NlAV5SojEYuElu5vr1GKdW+NswUcQbU6i0bcAC4l6Hh+r3+uM9qjdTkYCecp3LTyQ3KZFzHzeut5qrVe+Dd1AjgNiA9C9QDMZMhp+ghvOBii8xh2XVgc5g5Y5vDo41U5RKx+7C3wQXwxQPinSacKuTYR18Sw/zQhuFn3Bf+8uC0rRQaxCjuHIw/PrxS9bedv3ah5grI9h6yQuRpya+qYQXOfYy2wnO/t8+9e3/lF7qKqZwfh7stMGwCg/uPFmKT46AAA==)}
.deck-video-layer video{width:100%;height:100%;object-fit:cover;object-position:center}
/* flash-free scrub presentation: JS paints each landed frame onto this canvas and the canvas is
   what's displayed — the <video> keeps decoding offstage (visibility, not display: display:none
   can stall frame production in some engines). Canvas sizing mirrors the video's cover fit. */
.deck-video-layer canvas{position:absolute;top:0;left:0;right:0;bottom:0;inset:0;width:100%;height:100%;object-fit:cover;object-position:center;display:block}
.deck-video-layer video.deck-video-offstage{visibility:hidden}
.deck-video-layer.video-fallback canvas{display:none}
/* full-viewport gradient scrim — every section rides on this, edge to edge */
.deck-video-layer::after{content:"";position:absolute;top:0;left:0;right:0;bottom:0;inset:0;
  background:
    linear-gradient(180deg,rgba(6,7,15,.55),rgba(6,7,15,.30) 45%,rgba(6,7,15,.66)),
    radial-gradient(ellipse 120% 90% at 50% 50%,transparent 55%,rgba(6,7,15,.45) 100%)}
.deck-card.module .module-glow{opacity:.32;mix-blend-mode:screen}

/* ===== SECTION TILES → HERO HOLOGRAM MATERIAL (2026-07) =====
   Entry/photo/product tiles adopt the hero product card's look: near-clear cyan-violet glass,
   1px light border, 16px radius, blur + glow. Grouped selectors out-rank the per-section
   accent-color rules above. (No scanline layer here — ::before/::after are already used for
   the tiles' inner glow and text scrim.) */
.module .ph,.module.shop .ph,.module.build .ph,.module.setup .ph,.module.community .ph,
.module .ph.is-product{
  border:1px solid rgba(150,215,255,.38);border-radius:16px;
  background:linear-gradient(160deg,rgba(130,205,255,.05),rgba(120,160,255,.015) 45%,rgba(165,120,255,.045) 100%);
  backdrop-filter:blur(6px) saturate(160%);-webkit-backdrop-filter:blur(6px) saturate(160%);
  box-shadow:0 0 0 1px rgba(120,200,255,.10),0 0 26px rgba(90,180,255,.18),0 0 70px -14px rgba(130,140,255,.30),
    inset 0 0 24px rgba(120,200,255,.08),inset 0 1px 0 rgba(190,235,255,.26)}
.module .ph:hover,.module.shop .ph:hover,.module.build .ph:hover,.module.setup .ph:hover,.module.community .ph:hover{
  border-color:rgba(180,230,255,.62);border-style:solid;
  background:linear-gradient(160deg,rgba(130,205,255,.09),rgba(120,160,255,.03) 45%,rgba(165,120,255,.07) 100%);
  box-shadow:0 0 0 1px rgba(140,215,255,.16),0 0 34px rgba(90,180,255,.30),0 0 90px -14px rgba(130,140,255,.42),
    inset 0 0 28px rgba(120,200,255,.12),inset 0 1px 0 rgba(190,235,255,.30)}

/* color-reveal: Build & Setup entrance-card visuals rest in blue monochrome and bloom to
   full color on hover — the reveal effect ties the holo cards to the video's blue palette */
.module.build .ph .ph-img,.module.setup .ph .ph-img{
  filter:grayscale(1) sepia(.6) hue-rotate(175deg) saturate(2.3) brightness(.82);
  transition:filter .5s ease,transform .6s cubic-bezier(.2,.7,.2,1)}
.module.build .ph:hover .ph-img,.module.setup .ph:hover .ph-img{filter:none}
@media(prefers-reduced-motion:reduce){
  .module.build .ph .ph-img,.module.setup .ph .ph-img{transition:none}
}

/* ===== REVEAL ANIMATION ===== */
.reveal{opacity:0;transform:translateY(36px);transition:opacity .75s cubic-bezier(.16,1,.3,1),transform .75s cubic-bezier(.16,1,.3,1)}
.reveal.in{opacity:1;transform:none}
.reveal.d1{transition-delay:.1s}.reveal.d2{transition-delay:.2s}.reveal.d3{transition-delay:.3s}.reveal.d4{transition-delay:.4s}

/* ===== REDUCED MOTION ===== */
@media (prefers-reduced-motion: reduce){
  html{scroll-behavior:auto}
  .reveal{opacity:1!important;transform:none!important;transition:none!important}
  .scroll-mouse::after{animation:none}
  .btn-primary,.btn-ghost,.module-cta,.pc-cta{transition:none}
  /* same flow layout as the tablet/phone blocks — flowMode() in JS treats reduced-motion as an
     equal trigger to the width breakpoint, so the CSS has to offer the same fallback shape at
     any viewport width, not just narrow ones. */
  .deck{height:auto}
  .deck-viewport{position:static;height:auto;overflow:visible}
  .deck-card{position:static;inset:auto;overflow:visible;will-change:auto;transform:none;z-index:auto}
  .module,.deck-card.module{overflow:visible;min-height:0;height:auto}
  /* flow layout: the shared bg video rides sticky behind the stacked sections */
  .deck-video-layer{position:sticky;inset:auto;top:0;height:100vh;height:100svh;margin-bottom:-100vh;margin-bottom:-100svh}
}

/* ===== RESPONSIVE BREAKPOINTS =====
   ≥1200  PC     — depth deck, side-by-side hero, multi-col entry cards
   800–1199 tablet — flow sections, tighter hero, stacked modules, grid cards kept
   <800   phone  — stacked hero, swipe entry cards, compact chrome
*/

/* ===== TABLET (and flow layout shared with phone) ===== */
@media(max-width:1199px){
  :root{--max:100%}
  .scroll-cue{display:none}
  .hero-rail{display:none}
  .hero{height:auto;min-height:100vh;min-height:100svh;overflow:visible}
  /* tablet: same composition as phone — TITLE leads from the top, card follows below (the old
     card-first + space-between layout pinned the card to the page top and pushed the title below
     the fold). Gap trimmed 20svh → 12svh and the offset 15% → 12%: the phone block below used to
     be the only place the "too much dead space" QA note was applied, which left tablets with the
     20svh band QA had already rejected once. Still roomier than the phone's 8svh — a tablet has
     the height to spare. */
  .hero-inner{display:grid;grid-template-columns:1fr;grid-template-areas:"tx" "card";
    align-content:start;justify-items:center;row-gap:12vh;row-gap:12svh;
    padding:max(14px,calc(12vh - 46px)) 24px 40px;padding:max(14px,calc(12svh - 46px)) 24px 40px;
    height:auto;min-height:100vh;min-height:100svh}
  .hero-card{position:relative;top:auto;left:auto;right:auto;grid-area:card;width:min(58vw,340px);height:400px;margin:0}
  /* left/top:auto: the desktop cluster is absolutely placed with left:50% + translateX —
     killing only the transform would leave a half-viewport rightward shift */
  .hero-left{position:relative;left:auto;right:auto;top:auto;bottom:auto;transform:none;grid-area:tx;width:100%;max-width:760px;padding:0}
  .hero .hero-title{font-size:clamp(26px,calc((100vw - 56px)/7.2),64px);margin-bottom:16px}
  .hero p{font-size:15.5px;margin-bottom:14px;max-width:min(92%,640px)}
  .hub-shortcuts{max-width:100%}
  .hub-sc{padding:18px 8px 16px;gap:7px}
  .hub-sc-ico{width:44px;height:44px}
  .hub-sc-label{font-size:12.5px}
  .hub-sc-sub{font-size:11px}
  .pc-tabs{gap:2px}
  .pc-tab{font-size:10px;padding:5px 8px}
  .hero-carousel .pc-slide{padding:24px 18px 4px;gap:12px}
  /* flow sections must not clip or stack-paint over each other */
  .module,.deck-card,.deck-card.module{
    overflow-x:hidden;
    overflow-y:visible;
    min-height:0;
    height:auto;
    z-index:auto;
    max-width:100%;
  }
  .module{padding:64px 40px;align-items:flex-start}
  .module-inner{flex-direction:column;align-items:stretch;gap:28px}
  .module.alt .module-inner{flex-direction:column;justify-content:flex-start}
  .module-text{max-width:none;flex:none;width:100%}
  .module-visual{flex:none;width:100%;max-width:none}
  /* tablet keeps a readable multi-col grid (not swipe) */
  .entry-cards{height:auto;min-height:320px;grid-template-columns:repeat(2,1fr)}
  .module.build .entry-cards,.module.setup .entry-cards{grid-template-columns:repeat(3,minmax(0,1fr))}
  .module-visual .ph.is-entry{height:340px;min-height:300px}
  .dotnav{display:none}
  .deck{height:auto}
  .deck-viewport{position:static;height:auto;overflow:visible}
  .deck-card{position:static;inset:auto;will-change:auto;transform:none}
  .deck-fx{display:none}
  /* flow layout: the shared bg video rides sticky behind the stacked sections */
  .deck-video-layer{position:sticky;inset:auto;top:0;height:100vh;height:100svh;margin-bottom:-100vh;margin-bottom:-100svh}
  .ticker{display:none}
  .breadcrumb{padding:10px 28px}
}

/* mid-tablet: 3 entry cards get tight — drop to 2-col wrap */
@media(max-width:999px) and (min-width:800px){
  .module.build .entry-cards,.module.setup .entry-cards{grid-template-columns:repeat(2,minmax(0,1fr))}
  .hero-left{flex:none;padding:0}
  .module{padding:56px 28px}
}

/* ===== PHONE ===== */
@media(max-width:799px){
  /* kill horizontal black-bar / clipped content: remote header + our glow/negative margins
     can push wider than the viewport; clip hard and drop fixed bg (known mobile paint bug). */
  html,body{
    overflow-x:hidden !important;
    max-width:100% !important;
    width:100% !important;
    min-width:0 !important;
    background-attachment:scroll;
  }
  /* scoped under #newProductPageContent: the injected footer is
     <footer class="footer2021 page-section"> and carries its own .page-content-inner — the bare
     class selectors were force-sizing the REAL footer's containers on mobile too. */
  #app,#main-content,#newProductPageContent,
  #newProductPageContent .page-section,#newProductPageContent .page-content-inner,
  .page-content-root,.deck,.deck-viewport{
    max-width:100% !important;
    width:100% !important;
    min-width:0 !important;
    overflow-x:hidden;
  }
  .hero-brackets,.hero-rail,.hud-boot{display:none}

  .hero{
    min-height:0;
    height:auto;
    overflow-x:hidden;
    overflow-y:visible;
    padding:0 0 8px;
    max-width:100%;
  }
  .hero-inner{
    display:grid;
    grid-template-columns:1fr;
    grid-template-areas:"card" "tx";
    justify-items:center;
    gap:14px;
    padding:28px 12px 16px;
    height:auto;
    min-height:0;
    max-width:100%;
    width:100%;
    box-sizing:border-box;
  }
  .hero-left{
    position:relative;
    left:auto;right:auto;top:auto;bottom:auto;   /* desktop places this at left:50% — must reset */
    transform:none;
    grid-area:tx;
    flex:none;
    width:100%;
    max-width:100%;
    padding:0;
    min-width:0;
  }
  .hero-eyebrow{
    margin-bottom:14px;
    padding:7px 14px;
    font-size:10.5px;
    letter-spacing:.14em;
    max-width:100%;
  }
  .hero .hero-title{font-size:clamp(20px,calc((100vw - 24px)/7.2),44px);margin-bottom:12px;line-height:1}
  .hero p{margin-bottom:12px;font-size:17px;line-height:1.5;max-width:100%}
  .hero-callout{font-size:12px;margin-bottom:10px;min-height:0;max-width:100%}

  .hub-shortcuts{margin-top:8px;border-radius:14px;max-width:100%;width:100%;box-sizing:border-box}
  .hub-sc{padding:12px 4px 11px;gap:5px;min-width:0}
  .hub-sc-ico{width:36px;height:36px;border-radius:10px}
  .hub-sc-ico svg{width:17px;height:17px}
  .hub-sc-label{
    font-size:10px;
    letter-spacing:.03em;
    white-space:normal;
    text-align:center;
    line-height:1.2;
  }
  .hub-sc-sub{display:none}

  .hero-card{
    grid-area:card;
    width:min(86vw,340px);
    max-width:100%;
    height:320px;
    min-height:0;
    position:relative;
    left:auto;
    right:auto;
    top:auto;
    transform:none;
    flex:none;
    margin:0;
    overflow:hidden;
    border-radius:16px;
  }
  .hero-carousel{
    position:relative;
    inset:auto;
    height:100%;
    display:flex;
    flex-direction:column;
    max-width:100%;
    overflow:hidden;
  }
  .hero-carousel .pc-wrap{
    flex:1;
    height:auto;
    position:relative;
    max-width:100%;
    overflow:hidden;
  }
  .hero-carousel .pc-track{height:100%;max-width:100%}
  .hero-carousel .pc-slide{
    height:100%;
    padding:12px 40px 8px;
    gap:8px;
    justify-content:center;
    max-width:100%;
    box-sizing:border-box;
  }
  .hero-carousel .pc-visual{flex:0 1 auto;max-width:100%}
  .pc-photo{
    width:min(88%,280px);
    aspect-ratio:4/3;
  }
  .pc-photo img{padding:4px}
  .pc-photo img.pc-scale-sm{transform:scale(.8)}
  .pc-photo img.pc-scale-md{transform:scale(.88)}
  .pc-photo img.pc-scale-xl{transform:scale(1.12);padding:0}
  .pc-slide:hover .pc-photo img.pc-scale-sm{transform:scale(.84)}
  .pc-slide:hover .pc-photo img.pc-scale-md{transform:scale(.92)}
  .pc-slide:hover .pc-photo img.pc-scale-xl{transform:scale(1.16)}
  .pc-glow-ring{width:200px;height:200px;max-width:70%}
  .pc-slide h2,.pc-slide .pc-name{font-size:22px;margin-bottom:8px}
  .pc-tag{font-size:10px;margin-bottom:6px}
  .pc-cta{height:40px;padding:0 18px;font-size:12px;max-width:100%}
  .pc-prev,.pc-next{
    width:32px;height:32px;
    top:42%;
  }
  /* the shared ::before hit-area expander is inset:-3px, which only gets a 32px button to 38px —
     stretch it here so the touch target still clears 44px at this size (measured 39x43 at -6px). */
  .pc-prev::before,.pc-next::before{inset:-7px}
  .pc-prev{left:2px}.pc-next{right:2px}
  .pc-tabs{
    position:relative;
    flex-shrink:0;
    gap:3px;
    flex-wrap:nowrap;
    justify-content:center;
    padding:10px 4px 0;
    overflow-x:auto;
    max-width:100%;
    scrollbar-width:none;
    -webkit-overflow-scrolling:touch;
  }
  .pc-tabs::-webkit-scrollbar{display:none}
  .pc-tab{
    font-size:10px;
    padding:6px 9px;
    flex:0 0 auto;
  }
  .pc-dots{
    position:relative;
    flex-shrink:0;
    padding:8px 0 4px;
  }

  .module,
  .deck-card,
  .deck-card.module{
    overflow-x:hidden !important;
    overflow-y:visible !important;
    min-height:0 !important;
    height:auto !important;
    z-index:auto !important;
    transform:none !important;
    max-width:100% !important;
    width:100% !important;
    box-sizing:border-box;
  }
  .module-glow{display:none}
  .module{
    padding:36px 14px 48px;
    align-items:flex-start;
    display:block;
  }
  .module-inner{
    display:flex;
    flex-direction:column;
    align-items:stretch;
    gap:18px;
    width:100%;
    max-width:100%;
    min-width:0;
  }
  .module-text{
    padding:20px 16px 22px;
    border-radius:16px;
    width:100%;
    max-width:100%;
    box-sizing:border-box;
    min-width:0;
    order:1;
  }
  .module-index{font-size:12px;margin-bottom:14px}
  .module h2,.module h3{font-size:24px;margin-bottom:12px}
  .module .desc{font-size:14.5px;line-height:1.55;margin-bottom:16px;max-width:none}
  .module-feats{margin-bottom:14px;gap:9px}
  .feat{font-size:13.5px;align-items:flex-start}
  .module-keywords{gap:6px;margin:4px 0 14px;max-width:100%}
  .module-kw{font-size:11px;padding:6px 11px}
  .module .cut-wrap:has(.module-cta){display:block;width:100%;max-width:100%}
  .module-cta{
    width:100%;
    justify-content:center;
    height:48px;
    font-size:14px;
  }

  .swipe-hint{
    display:block;
    order:2;
    font-family:var(--font-b);
    font-size:11px;
    font-weight:600;
    letter-spacing:.08em;
    text-transform:uppercase;
    /* sits directly on the video walk — 40% white washed out against bright scenes; keep it
       subtle but separated from the footage */
    color:rgba(255,255,255,.78);
    text-shadow:0 1px 4px rgba(0,0,0,.85),0 0 12px rgba(0,0,0,.5);
    margin:0;
  }
  .module-visual{
    order:3;
    display:flex;
    flex-direction:row;
    align-items:stretch;
    justify-content:flex-start;
    width:100%;
    max-width:100%;
    overflow-x:auto;
    overflow-y:visible;
    scroll-snap-type:x mandatory;
    -webkit-overflow-scrolling:touch;
    gap:12px;
    scrollbar-width:none;
    padding-bottom:4px;
    box-sizing:border-box;
  }
  .module-visual::-webkit-scrollbar{display:none}
  .entry-cards,
  .module.build .entry-cards,
  .module.setup .entry-cards{display:contents}
  .module-visual .ph.is-entry{
    flex:0 0 88%;
    width:88%;
    scroll-snap-align:center;
    min-width:0;
    height:360px;
  }
  .games-scroll-track{margin-left:0;margin-right:0}
  .breadcrumb{padding:10px 16px;max-width:100%;box-sizing:border-box}
  .breadcrumb-item{font-size:13px}
}

/* ===== MOBILE-FIRST UPGRADE (2026-07) =====================================================
   Tunes this session's newer components (scene tool cards, filing tabs with product cutouts,
   3-row cover marquee, shop hint) for touch + small screens. Appended last so it wins the
   cascade over the older phone rules above at equal specificity. Principles:
   — keep the immersive pillars (hero video + flip title, scroll-scrubbed HP-BG behind the
     flowing sections, cover marquee, holo tool cards) instead of flattening them;
   — hover-gated reveals become always-on or tap feedback (touch has no hover);
   — trim GPU-heavy chrome (backdrop blurs) and network weight for phones. */
@media(max-width:799px){
  /* -- HERO: full-screen cinematic opener — the TITLE leads over the video (the story beat),
        the holo product card follows below it. vh lines are fallbacks for engines w/o svh. -- */
  .hero{min-height:100vh;min-height:100svh}
  /* title cluster offset from the top + the beat between it and the holo card. Both have been
     tightened twice on QA feedback: 20svh → 12svh → 8svh gap, and a 15% → 10% top offset
     (QA #9, "刷新时页面内容与 header 之间的空白比较多"). Measured at 375×812 the header-to-first-
     text distance goes 123px → ~82px and the title-to-card beat 97px → 65px, which is ~9% of the
     viewport reclaimed without letting the two blocks touch. Keep a real gap here: it is what
     lets the scene video read as a background rather than a texture behind a wall of UI. */
  .hero-inner{grid-template-areas:"tx" "card";min-height:100vh;min-height:100svh;
    align-content:start;row-gap:8vh;row-gap:8svh;
    padding:max(12px,calc(10vh - 46px)) 14px calc(20px + env(safe-area-inset-bottom,0px));
    padding:max(12px,calc(10svh - 46px)) 14px calc(20px + env(safe-area-inset-bottom,0px))}
    /* −46px: the breadcrumb bar above the hero counts toward the 10%-of-window offset */
  .hero .hero-title{margin-bottom:14px}
  .hero p{font-size:15px;line-height:1.55;max-width:36ch;margin-left:auto;margin-right:auto}
  .hero-card{width:min(80vw,320px);height:300px}

  /* -- COVER MARQUEE: TWO large rows (presence over density on a phone); art unclipped;
        tap = go, with press feedback (hover doesn't exist on touch) -- */
  .fg-row-3{display:none}
  .floating-games{gap:12px;margin:2px 0 14px}
  .floating-game{width:auto;margin-right:12px;overflow:hidden;border-radius:9px}
  /* square covers on touch (matches the expanded grid's card ratio), filled by vertical height */
  .floating-game img{width:124px;height:124px;object-fit:cover;object-position:center;border-radius:9px}
  .floating-game:active{transform:scale(.96)}
  .floating-game:active .fg-label{opacity:1}
  .shop-hint{font-size:11.5px;padding:6px 12px;margin:0 auto 10px;max-width:94%;white-space:normal;text-align:center}

  /* -- TOOL CARDS (PC Builder/Finder/Upgrader · Game Room/Sim/Showcase): full-bleed scene art,
        name bottom-left, feature notes ALWAYS visible (they were hover-revealed on desktop) -- */
  .module-visual .ph.is-entry{flex:0 0 82%;width:82%;height:330px}
  .ph.is-entry.is-tool .ph-img{position:absolute;inset:0;width:100%;height:100%;top:0;padding:0;object-fit:cover}
  .ph.is-entry.is-tool::before{display:none}                    /* glow plate is for cutouts */
  .ph.is-entry.is-tool::after{height:82%;
    background:linear-gradient(0deg,rgba(4,6,14,.99) 0%,rgba(4,6,14,.95) 36%,rgba(4,6,14,.62) 66%,transparent 100%)}
  .ph.is-entry.is-tool .ph-bullets{display:flex;max-height:none;opacity:1;margin:4px 0 8px;overflow:visible}
  /* out-ranks the desktop clamp() cap rule (same compound + later in file) */
  .module.build .ph.is-entry.is-tool .ph-cap,.module.setup .ph.is-entry.is-tool .ph-cap{font-size:19px}
  .ph.is-entry.is-tool .ph-pill{font-size:10px}

  /* -- CATEGORY TABS: colored grid (no dim/hover on touch), sized for cutout + label -- */
  .gear-card{height:118px;gap:6px;padding:10px 6px 9px;border-radius:10px}
  .gear-card:active{transform:scale(.96)}
  .gear-img{max-width:92%}
  .gear-label{font-size:11.5px}
  .gear-head{flex-direction:column;align-items:flex-start;gap:4px;margin-bottom:12px}
  /* on ≤1199 these sit straight on the video walk (the desktop glass panel is gone) — shadow
     them off the footage the same way as .swipe-hint */
  .gear-head-label{font-size:16px;text-shadow:0 1px 4px rgba(0,0,0,.85),0 0 12px rgba(0,0,0,.5)}
  .gear-head-link{text-shadow:0 1px 4px rgba(0,0,0,.85),0 0 12px rgba(0,0,0,.5)}
  .gear-head-link{font-size:13px}

  /* -- SHOP-THE-LOOK HOTSPOTS: comfortable tap targets. The padding gives a 0-width content
     box on this flex anchor, so the dot (flex-shrink:1) was being crushed to 4×15 — an oval.
     flex:none keeps it a true circle; centring places it in the middle of the tap area. -- */
  .hotspot{padding:14px;margin:-14px;align-items:center;justify-content:center}
  .hotspot-dot{width:15px;height:15px;flex:none}

  /* -- perf: near-opaque fills don't need backdrop blur (big GPU cost on phones) -- */
  .module-text,.hub-shortcuts,.shop-hint,.parts-aside,.hero-eyebrow{
    backdrop-filter:none;-webkit-backdrop-filter:none}

  /* -- section rhythm: consistent breathing room BETWEEN the pieces of one section
        (text card → tools row → "shop directly" head → category grid → marquee) -- */
  .module{padding:46px 14px 56px}
  .module-inner{gap:24px}
  .module-text{margin-bottom:16px}              /* .module-main children stack on margins, not gap */
  .swipe-hint{margin:10px 0 6px}
  .module-visual{gap:12px;padding-bottom:8px}
  .gear-head{margin:20px 0 12px}                /* clear break between the tools row and categories */
  .gear-cards{gap:10px}
  /* clear breathing room between the category tabs above and the cover carousel */
  .module.shop .floating-games{margin:30px 0 18px}
  .shop-hint{margin:0 auto 12px}
  .module h2,.module h3{font-size:26px}
  .module .desc{font-size:15px}
  .module.setup{padding-bottom:calc(56px + env(safe-area-inset-bottom,0px))}
}

/* tiny phones: keep the marquee readable and the title clear of the edges */
@media(max-width:400px){
  .floating-game img{width:108px;height:108px}
  .hero-card{width:min(88vw,310px);height:292px}
  .module-visual .ph.is-entry{flex:0 0 88%;width:88%}
}

/* ===== FLOW-MODE VIDEO LAYER (≤1199): position:fixed, not sticky ==========================
   The phone block hard-clips ancestors with overflow-x:hidden, and ANY non-visible overflow on
   an ancestor silently breaks position:sticky — the HP-BG layer was scrolling away with the
   hero, leaving the sections with no scene video at all. fixed pins to the viewport no matter
   what the ancestors do. Sections stack above it (relative z:1); the scroll scrub in JS keeps
   mapping each section to its scene. !important out-ranks the earlier flow-block resets. */
@media(max-width:1199px){
  .deck-video-layer{position:fixed !important;inset:0 !important;height:auto !important;
    margin:0 !important;z-index:0 !important}
  .deck-card{position:relative !important;z-index:1 !important}
  /* Anything in normal flow OUTSIDE the deck has to be lifted here or the fixed video layer above
     paints straight over it. A static block element loses to a positioned z-index:0 element no
     matter how far down the document it sits, so "it's after the deck" is not protection.
     .page-updated shipped without this and was completely invisible on phones — the footage was
     drawn on top of it. Two checks gave false confidence and neither is sufficient on its own:
     screenshotting the element renders it in isolation so occlusion never shows, and
     elementFromPoint skips the video layer because it carries pointer-events:none. The only
     reliable test is a FULL-VIEWPORT screenshot at that scroll position.
     Add every future post-deck section to this list. */
  .breadcrumb,.scroll-progress,.page-updated{position:relative;z-index:2}

  /* ===== IMMERSIVE SCENES (flow mode): each section is its own scene against the video walk.
     Footprint ≈ 2× viewport for short scenes (min-height + margins) and content +≈15% for tall
     ones — the collapsed 26svh margins are the video-only "walking" beats between scenes, and
     the scroll FX in JS rises each scene in / lifts it away (mobile mirror of the desktop deck).
     !important where the phone flow-reset block hard-forces min-height:0. */
  /* align-items:stretch — the phone .module rule sets flex-start, which shrink-wrapped every
     child of this flex column (the "Show more games" button rendered at text width, not full
     width). Stretch restores full-bleed children. */
  .deck-card.module{min-height:100vh !important;min-height:100svh !important;
    display:flex;flex-direction:column;justify-content:center;align-items:stretch;
    margin:26vh 0;margin:26svh 0}
  .deck-card.module:last-of-type{margin-bottom:6vh}

  /* one continuous backdrop for ALL sections (hero excluded — its own video is opaque): the
     tint lives on the fixed video layer, so there are no per-section overlay seams in the
     walking gaps anymore. */
  .deck-card.module,.deck-card.module.build,.deck-card.module.shop,
  .deck-card.module.setup,.deck-card.module.community{background:none !important}
  .deck-video-layer::after{background:rgba(6,8,20,.46) !important}

  /* Shop's categories block was inheriting the phone swipe-row styling (head beside the grid) —
     stack it exactly like Build/Setup's "Already know what you want?" area */
  .module.shop .module-visual{display:block !important;overflow:visible !important;padding:0}

  /* TOOL CARDS on touch: the radial "glow plate" behind cutout products ends in a hard edge over
     a full-bleed scene photo (visible band across the card) — drop it. And no hover elevation:
     the scale/lift revealed the scrim's edge as a container cutoff line on tap-latched :hover. */
  .ph.is-entry::before,.ph.is-entry.is-tool::before{display:none !important}
  /* every back-glow source off: the outer neon box-shadow gets sliced by .module-visual's
     scroll container (overflow) → that's the harsh cutoff line; the conic light-trail also
     latches on after a tap. Border alone carries the card edge on touch. */
  .ph,.ph.is-entry,.ph.is-entry.is-tool,
  .ph:hover,.ph.is-entry:hover,.ph.is-entry.is-tool:hover{box-shadow:none !important}
  .ph-trail{display:none !important}
  .ph.is-entry:hover,.ph.is-entry:focus-within,
  .ph.is-entry.is-tool:hover,.ph.is-entry.is-tool:focus-within{transform:none !important;background:#070b18 !important}
  .ph.is-entry:hover .ph-img,.ph.is-entry.is-tool:hover .ph-img{transform:none !important}
  /* the blue-monochrome → colour reveal is hover-driven, and touch has no hover — so the
     scene art would sit desaturated forever. Ship it in full colour by default here. */
  .module.build .ph .ph-img,.module.setup .ph .ph-img,
  .module.build .ph.is-entry .ph-img,.module.setup .ph.is-entry .ph-img{filter:none !important}
  .ph.is-entry:active{transform:scale(.985) !important}   /* honest press feedback instead */
  /* pill chips (DIY / AI / ROOM / SIM …) were 70% transparent over full-bleed scene photos —
     their accent ink dropped to ~1.2:1 on bright frames. Opaque chip → 5.7-8.2:1. */
  .ph-pill,.ph.is-entry .ph-pill,.ph.is-entry.is-tool .ph-pill{background:rgba(4,6,14,.94) !important}
  /* Setup's pills carry the deep NE blue inline (#0070CC → 3.64:1). Use the section's light
     blue on touch so ROOM / SIM / Community clear AA (10.8:1). */
  .module.setup .ph-pill{color:#8FC2FF !important}

  /* touch has no hover — the hover how-to pill is meaningless here */
  .shop-hint{display:none !important}

  /* this big CTA doesn't exist on desktop (hidden ≥1200) — same on mobile */
  .module-text .cut-wrap,.module-cta{display:none !important}

  /* legibility floor: ≥14px for ALL text on touch layouts */
  .gear-label{font-size:14px !important}
  .module.build .ph.is-entry .ph-bullets li,.module.setup .ph.is-entry .ph-bullets li{font-size:14px}
  .fg-label{font-size:14px}
  .parts-aside-title{font-size:14px}
  .parts-aside .module-kw,.module-kw{font-size:14px !important}
  .breadcrumb-item{font-size:14px}
  .pc-tab{font-size:14px}
  .pc-tag{font-size:14px}
  .pc-cta{font-size:14px !important}
  .ph-pill,.ph.is-entry .ph-pill{font-size:14px !important}
  /* full-width entry CTA on touch — a wider, taller tap target than the desktop pill */
  .ph-entry-cta,.module.build .ph-entry-cta,.module.setup .ph-entry-cta{
    font-size:14px !important;width:100%;justify-content:center;height:46px;padding:0 20px}
  .ph.is-entry .cut-wrap{display:block;width:100%}
  .fg-label,.floating-game .fg-label{font-size:14px !important}
  .swipe-hint{font-size:14px}
  .gear-head-link{font-size:14px}
  .hotspot-tip{font-size:14px}
  .feat{font-size:14px}
  .hub-sc-label{font-size:14px}

  /* touch: tabs never skew — phones latch :hover after a tap, which re-applied the desktop
     skew/lift to a supposedly-flat mobile tab */
  .gear-card:hover,.gear-card:focus-visible{transform:none !important;box-shadow:none}

  /* "Show N more games" expander + store CTA — grid expands DIRECTLY under the roster,
     no container chrome (items float on the scene like the roster covers) */
  .fg-more{display:block;margin:14px 0 6px}
  /* QA: dark-outline style read as a different button family — matched to the NE yellow pill
     the rest of the page uses (Shop laptops, PC builder, …), same font stack and radius. */
  .fg-more-btn{display:flex;align-items:center;justify-content:center;gap:8px;
    width:100%;min-height:52px;padding:14px 24px;box-sizing:border-box;
    font-family:var(--font-ui);font-size:15px;font-weight:700;letter-spacing:0;
    line-height:1.2;white-space:nowrap;color:var(--ne-ink);cursor:pointer;text-align:center;
    background:var(--ne-yellow);border:none;border-radius:999px;
    box-shadow:0 4px 18px rgba(0,0,0,.35)}
  .fg-more-btn:active{background:var(--ne-yellow-active)}
  .fg-more-btn::after{content:"";width:8px;height:8px;flex:none;margin-top:-4px;
    border-right:2px solid var(--ne-ink);border-bottom:2px solid var(--ne-ink);transform:rotate(45deg)}
  .fg-more-btn:active{transform:scale(.98)}
  .fg-more-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:14px 10px;
    margin-top:12px;padding:0;border:none;background:none}
  /* one card per game: the navy backdrop wraps the SQUARE cover and its title together */
  .fg-more-item{display:flex;flex-direction:column;align-items:stretch;gap:8px;text-decoration:none;min-width:0;
    padding:8px 8px 10px;border-radius:12px;
    background:rgba(13,20,48,.94);border:1px solid rgba(150,215,255,.2)}
  /* fill the square by the image's VERTICAL height (side overflow is cropped) so no boxart
     ever letterboxes with empty bands above/below */
  .fg-more-item img{width:100%;aspect-ratio:1/1;height:auto;object-fit:cover;object-position:center;
    border-radius:8px;background:#0A0D1B;border:none}
  .fg-more-item span{font-family:var(--font-b);font-size:14px;font-weight:600;color:#EAF2FF;
    text-align:center;line-height:1.25;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
  .fg-more-item:active{opacity:.85}
  .fg-more-cta{grid-column:1/-1;display:flex;align-items:center;justify-content:center;gap:8px;
    height:48px;margin-top:4px;border-radius:999px;text-decoration:none;
    font-family:var(--font-b);font-size:15px;font-weight:800;color:var(--ne-ink);
    background:linear-gradient(120deg,var(--ne-yellow),var(--ne-yellow-active))}
  .fg-more-cta:active{transform:scale(.98)}

  /* Shop & Play content card leads the section; the rotating roster + expander follow it */
  .module.shop .module-inner{order:-1}

  /* covers read fully SOLID: no horizontal fade mask on touch (edge covers looked transparent) */
  .floating-games{-webkit-mask-image:none !important;mask-image:none !important}

  /* The roster used to rotate ONLY while the page was being scrolled: default play-state paused,
     with a .is-scrolling class (added by the scroll handler, dropped 160ms after the last event)
     switching it to running. That is gone — the rows now run continuously, same as desktop.
     Why: measured, it worked exactly as written (31px/s, class toggling fine) and was still
     invisible in practice. While you scroll, the covers are already travelling vertically with
     the page, so a 31px/s horizontal drift can't be picked out; a typical half-second gesture
     moved them ~15px of a 108pt cover; and the instant you stop to look, they froze. The net
     effect was a carousel that appeared permanently static (reported as "mobile marquee doesn't
     scroll"). Two transform animations are GPU-composited and cheap enough to just leave running.
     No hover-pause on touch — mobile browsers latch :hover after a tap, which used to freeze a
     row for good. Tapping a moving cover is fine at this speed (~5px of travel per tap). */
  /* after "Show more games": the roster is frozen for good — it has joined the list */
  .floating-games.fg-frozen .fg-track{animation-play-state:paused !important}
}
