@font-face {
  font-family: "ValveOracle";
  src: url("../valveoracle-thin.ttf") format("truetype");
  font-weight: 300;
}
@font-face {
  font-family: "ValveOracle";
  src: url("../valveoracle-medium.ttf") format("truetype");
  font-weight: 500;
}
@font-face {
  font-family: "ValveOracle";
  src: url("../valveoracle-semibold.ttf") format("truetype");
  font-weight: 600;
}
@font-face {
  font-family: "ValvePulp";
  src: url("../valvepulp-bold.ttf") format("truetype");
  font-weight: 700;
}
@font-face {
  font-family: "RetailText";
  src: url("../retailtextdemo-bold.otf") format("opentype");
  font-weight: 700;
}

:root {
  --panel-w: 1076px;
  --panel-h: 960px;
  --tooltip-display-h: 960px;
  /* .tooltip-display's width at the design-time --panel-h (i.e. its
     width when the shop panel is at native size), used as the "100%
     scale" reference point for .tooltip-card below. */
  --tooltip-display-native-w: calc(var(--panel-h) * 800 / 1618);

  /* Free knobs for manually tweaking .tooltip-card.visible's placement —
     no-op defaults (auto height, no vertical shift). Edit these directly
     to experiment; height is in the card's native (pre-scale) pixels,
     same space as its width/padding, and y-offset is applied AFTER the
     existing scale so it's also in native pixels, not rendered ones. */
  --tooltip-card-height: auto;
  --tooltip-card-y-offset: 0px;

  /* Free knobs for where .tooltip-card starts getting visually clipped —
     i.e. how far (from .tooltip-display's own top/bottom) the actual
     scroll viewport is inset. No-op defaults (viewport fills the display
     exactly). Native (pre-scale) pixels — same space as
     --tooltip-card-height/-y-offset above — converted to real on-screen
     px via .tooltip-scroll-viewport's own top/bottom (below) using the
     exact same scale ratio .tooltip-card's transform uses, so the crop
     line stays visually locked to the card's own (also-scaled) content
     at any panel/window size instead of drifting apart the way mixing
     native and real px did in an earlier attempt at this. */
  --tooltip-crop-top: 60px;
  --tooltip-crop-bottom: 38px;

  /* .build-sections-container's "100% scale" reference width — capped
     well short of the full build panel width, leaving .shop-builds-right
     room alongside it instead of the sections column claiming all
     available width. */
  --build-sections-native-w: 1158px;

  /* .shop-builds' own native/design width — --panel-w + the 6px gap +
     .tooltip-display's native width, i.e. what #shop-builds' actual
     (JS-synced, see --shop-builds-w below) width would be if the shop
     panels were rendering at their own native, unshrunk size. */
  --shop-builds-native-w: calc(var(--panel-w) + 6px + var(--tooltip-display-native-w));

  /* #shop-builds' own actual current rendered width — kept in sync by a
     ResizeObserver in app.js (syncShopBuildsAlignment), same pattern as
     --tooltip-display-h. Falls back to --shop-builds-native-w (i.e. no
     scaling) until JS measures it. .build-tabs uses the ratio of this to
     --shop-builds-native-w as its own scale factor, so it visibly scales
     in step with #shop-builds instead of staying pinned at native size
     while the panel beside it scales independently. */
  --shop-builds-w: var(--shop-builds-native-w);

  /* .shop-builds-inner's own content-box width (--shop-builds-native-w
     minus its 24px+24px left/right padding). This is the SAME ratio
     .shop-builds' own background art scales by (background-size: 100%
     auto is keyed to .shop-builds' actual width) — .build-actions-left/
     -right below use it as their scale reference (100cqw / this) instead
     of their own small content width, specifically so the row's controls
     visibly scale together with that background rather than drifting out
     of proportion with it (an earlier attempt scaled each cluster against
     its own tiny native width instead, which looked static/mismatched
     against the background at most realistic window sizes). */
  --shop-builds-content-native-w: calc(var(--shop-builds-native-w) - 48px);

  /* .build-actions-left/-search/-right's own native content width/height
     (Save Build + gap + New Build + gap + the title input; the search
     input alone; Add Section) — used to size each cluster's viewport/
     inner content, NOT as the scale divisor (see
     --shop-builds-content-native-w above for that). Separate units
     rather than one for the whole row, since the row spans past
     .shop-builds-right (uncapped flex-grow width, no single native width
     of its own) — see .build-actions-left-viewport/.build-actions-
     search-viewport/.build-actions-right-viewport below. */
  --build-actions-left-native-w: 604px;
  --build-actions-search-native-w: 304px;
  /* 138px Add Section + 12px gap + 150px Reset Original (hidden except
     while a curated build is loaded, but still reserved here so nothing
     reflows when it appears/disappears). */
  --build-actions-right-native-w: 300px;

  /* .investment-bars' "100% scale" reference width — its natural
     content width (souls column + 3 pill+label columns + gaps/padding/
     border) at 100% scale, measured in-browser. Used as
     .shop-builds-right's flex-basis (the "start growing/shrinking from
     here" point). */
  --investment-bars-native-w: 325px;

  /* .investment-bars-content's own native width — the souls column + 3
     pill columns (each now pill + its tick-label column side by side)
     + their gaps, measured in-browser. */
  --investment-bars-content-native-w: 293px;

  /* .graph-chart's "100% scale" reference width — sized to fit
     .shop-graphs' own padded content area at that panel's native/largest
     rendered size, given its background image's fixed aspect ratio. */
  --graph-chart-native-w: 1464px;

  /* Scattered black-dot grain texture, varied sizes/opacities for a
     random noise look. Shared via variable so it can be reused on
     any tab background without duplicating the gradient list. */
  --tab-grain: radial-gradient(circle at 6% 8%, rgba(0, 0, 0, 0.35) 0.6px, transparent 0.6px),
    radial-gradient(circle at 14% 22%, rgba(0, 0, 0, 0.22) 1.1px, transparent 1.1px),
    radial-gradient(circle at 9% 41%, rgba(0, 0, 0, 0.4) 0.5px, transparent 0.5px),
    radial-gradient(circle at 21% 55%, rgba(0, 0, 0, 0.18) 1.6px, transparent 1.6px),
    radial-gradient(circle at 28% 12%, rgba(0, 0, 0, 0.3) 0.8px, transparent 0.8px),
    radial-gradient(circle at 33% 68%, rgba(0, 0, 0, 0.42) 0.5px, transparent 0.5px),
    radial-gradient(circle at 40% 30%, rgba(0, 0, 0, 0.2) 1.3px, transparent 1.3px),
    radial-gradient(circle at 46% 78%, rgba(0, 0, 0, 0.28) 0.7px, transparent 0.7px),
    radial-gradient(circle at 52% 9%, rgba(0, 0, 0, 0.15) 1.8px, transparent 1.8px),
    radial-gradient(circle at 58% 44%, rgba(0, 0, 0, 0.35) 0.6px, transparent 0.6px),
    radial-gradient(circle at 63% 88%, rgba(0, 0, 0, 0.25) 1px, transparent 1px),
    radial-gradient(circle at 70% 20%, rgba(0, 0, 0, 0.4) 0.5px, transparent 0.5px),
    radial-gradient(circle at 75% 60%, rgba(0, 0, 0, 0.2) 1.4px, transparent 1.4px),
    radial-gradient(circle at 81% 35%, rgba(0, 0, 0, 0.3) 0.7px, transparent 0.7px),
    radial-gradient(circle at 87% 72%, rgba(0, 0, 0, 0.38) 0.5px, transparent 0.5px),
    radial-gradient(circle at 92% 15%, rgba(0, 0, 0, 0.22) 1.2px, transparent 1.2px),
    radial-gradient(circle at 5% 85%, rgba(0, 0, 0, 0.28) 0.9px, transparent 0.9px),
    radial-gradient(circle at 18% 95%, rgba(0, 0, 0, 0.32) 0.6px, transparent 0.6px),
    radial-gradient(circle at 25% 5%, rgba(0, 0, 0, 0.16) 1.5px, transparent 1.5px),
    radial-gradient(circle at 37% 90%, rgba(0, 0, 0, 0.4) 0.5px, transparent 0.5px),
    radial-gradient(circle at 44% 52%, rgba(0, 0, 0, 0.3) 0.8px, transparent 0.8px),
    radial-gradient(circle at 49% 65%, rgba(0, 0, 0, 0.42) 0.5px, transparent 0.5px),
    radial-gradient(circle at 55% 25%, rgba(0, 0, 0, 0.24) 1.1px, transparent 1.1px),
    radial-gradient(circle at 61% 58%, rgba(0, 0, 0, 0.35) 0.6px, transparent 0.6px),
    radial-gradient(circle at 68% 8%, rgba(0, 0, 0, 0.28) 0.9px, transparent 0.9px),
    radial-gradient(circle at 73% 92%, rgba(0, 0, 0, 0.14) 1.7px, transparent 1.7px),
    radial-gradient(circle at 79% 48%, rgba(0, 0, 0, 0.4) 0.5px, transparent 0.5px),
    radial-gradient(circle at 85% 25%, rgba(0, 0, 0, 0.3) 0.7px, transparent 0.7px),
    radial-gradient(circle at 90% 60%, rgba(0, 0, 0, 0.26) 1px, transparent 1px),
    radial-gradient(circle at 96% 40%, rgba(0, 0, 0, 0.34) 0.6px, transparent 0.6px);
}

/* Tier quadrant position offsets (px) and item columns per tier —
   nudge these to align each tier's item grid against that category's
   background artwork. Each category panel has its own independent
   set, so weapon/vitality/spirit can be tuned separately. */
.shop-panel.category-weapon {
  --tier-800-x: 32px;
  --tier-800-y: 170px;
  --tier-1600-x: -30px;
  --tier-1600-y: 40px;
  --tier-3200-x: 32px;
  --tier-3200-y: 34px;
  --tier-6400-x: 133px;
  --tier-6400-y: 34px;

  --tier-800-cols: 5;
  --tier-1600-cols: 6;
  --tier-3200-cols: 7;
  --tier-6400-cols: 4;
}

.shop-panel.category-vitality {
  --tier-800-x: 36px;
  --tier-800-y: 170px;
  --tier-1600-x: -28px;
  --tier-1600-y: 40px;
  --tier-3200-x: 36px;
  --tier-3200-y: 38px;
  --tier-6400-x: -28px;
  --tier-6400-y: 38px;

  --tier-800-cols: 5;
  --tier-1600-cols: 6;
  --tier-3200-cols: 5;
  --tier-6400-cols: 6;
}

.shop-panel.category-spirit {
  --tier-800-x: 36px;
  --tier-800-y: 170px;
  --tier-1600-x: -28px;
  --tier-1600-y: 40px;
  --tier-3200-x: 36px;
  --tier-3200-y: 102px;
  --tier-6400-x: -28px;
  --tier-6400-y: 102px;

  --tier-800-cols: 5;
  --tier-1600-cols: 6;
  --tier-3200-cols: 5;
  --tier-6400-cols: 6;
}

/* Same "position offsets (px) + columns per quadrant" knobs as the
   weapon/vitality/spirit panels above, just keyed by release-year
   quadrant instead of soul-cost tier (see .hero-quadrant.year-* below) —
   starting values only, likely needs nudging once checked against
   shop_bg_heros.png's actual artwork. Column counts are sized to each
   quadrant's real hero count: 2025 (10 heroes) 4 cols, 2024 (22 heroes,
   the largest bucket) 6 cols, 2026 (6 heroes) 3 cols, 2027 (placeholder,
   no heroes yet) 4 cols. */
.shop-panel.category-hero {
  --year-2025-x: 596px;
  --year-2025-y: 168px;
  --year-2024-x: -502px;
  --year-2024-y: 40px;
  --year-2026-x: 36px;
  --year-2026-y: -30px;
  --year-2027-x: 54px;
  --year-2027-y: 34px;

  --year-2025-cols: 5;
  --year-2024-cols: 6;
  --year-2026-cols: 6;
  --year-2027-cols: 4;
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
  background-color: #0b0a0d;
  background-image: url("../frontend_assets/background_gothic.png");
  background-size: cover;
  background-position: top center;
  background-repeat: no-repeat;
  background-attachment: fixed;
  color: #e0e0e0;
  font-family: "ValveOracle", "Segoe UI", sans-serif;
  font-weight: 500;
}

.site-header {
  position: relative;
  text-align: center;
  padding: 28px 16px 12px;
}

.site-header-logo {
  height: 80px;
  width: auto;
}

.site-header-credit {
  position: absolute;
  top: 50%;
  right: 16px;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  gap: 8px;
  color: #bcb49c;
  font-size: 16px;
  text-decoration: none;
}

.site-header-credit:hover {
  color: #f2ead9;
}

.site-header-credit img {
  height: 46px;
  width: auto;
}

.site-header-credit:hover img {
  transform: translateY(-2px);
  animation: selected-rotate 3.6s ease-in-out infinite;
}

.site-header p {
  margin: 6px 0 0;
  color: #8b8794;
  font-size: 13px;
}

/* ---------- layout ---------- */

.shop-layout {
  display: flex;
  justify-content: center;
  align-items: flex-start;
  gap: 0;
  padding: 20px 20px 0;
  max-width: 1700px;
  margin: 0 auto 6px;
}

.tooltip-display {
  /* --tooltip-display-h is kept in sync with #shop-panels' actual
     rendered height by a ResizeObserver in app.js (falls back to the
     --panel-h design constant until JS measures it). Both width and
     height are derived from that single value at the image's native
     800:1618 ratio — same trick .shop-panel uses via aspect-ratio — so
     the box always matches the image's shape exactly and
     background-size: cover never has a mismatched ratio to crop.

     This element is just the decorative background frame — it no longer
     scrolls itself (see .tooltip-scroll-viewport below, an inset child
     that does), so its own edges aren't necessarily where content gets
     visually clipped. overflow:hidden here just keeps the background art
     clipped to the rounded corners. */
  position: relative;
  flex-shrink: 0;
  margin-left: 6px;
  height: var(--tooltip-display-h);
  width: calc(var(--tooltip-display-h) * 800 / 1618);
  background-image: url("../frontend_assets/shop_bg_tooltip.png");
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  border-radius: 6px;
  overflow: hidden;
  container-type: inline-size;
}

/* The actual scrolling/clipping element — inset from .tooltip-display's
   own top/bottom by --tooltip-crop-top/-bottom (see :root), converted
   from their native-pixel values to real on-screen px via the exact same
   ratio .tooltip-card's own transform uses (100cqw / the display's
   native width) — so this crop line scales together with the card's
   content at any panel size instead of the two drifting apart, which is
   what went wrong the first time this was tried (that version mixed
   real, unscaled px here with native, scaled px on the card). */
.tooltip-scroll-viewport {
  position: absolute;
  top: calc(var(--tooltip-crop-top) * (100cqw / var(--tooltip-display-native-w)));
  left: 0;
  right: 0;
  bottom: calc(var(--tooltip-crop-bottom) * (100cqw / var(--tooltip-display-native-w)));
  overflow-x: hidden;
  overflow-y: auto;
  /* Firefox equivalent of the ::-webkit-scrollbar rules below — thumb
     color then track color, no separate width control (scrollbar-width
     only accepts auto/thin/none). */
  scrollbar-color: #ffffff rgb(15 13 19);
}

/* Dark track, white thumb — 20px total (widened from the previous
   16px). */
.tooltip-scroll-viewport::-webkit-scrollbar {
  width: 20px;
}

.tooltip-scroll-viewport::-webkit-scrollbar-track {
  background-color: rgb(15 13 19);
  border-radius: 8px;
}

.tooltip-scroll-viewport::-webkit-scrollbar-thumb {
  background-color: #ffffff;
  background-clip: padding-box;
  border: 3px solid transparent;
  border-radius: 8px;
}

/* Purely a positioning anchor for .build-tabs-viewport (position:absolute,
   pulled out to the left) — .shop-builds itself has overflow:hidden (its
   max-height backstop), which would clip the tabs if they were placed
   inside it instead of alongside it in a wrapper without that overflow
   rule. Has no size/layout properties of its own, so it doesn't affect
   .shop-builds' own JS-computed width/margin-left in any way. */
.shop-builds-wrap {
  position: relative;
}

/* Same relationship as .category-tabs-viewport/.category-tabs, just
   scaled by #shop-builds' own ratio (--shop-builds-w over
   --shop-builds-native-w — see :root) instead of #shop-panels', since
   this stack sits beside .shop-builds, not the shop panels. 294px is
   this stack's own constant native height (216px for whichever of the 2
   build tabs is active + 72px for the other + the 6px gap between them
   — see .build-tab.active below), same "always exactly one is active,
   so this never actually varies" reasoning as .category-tabs-viewport.
   Positioned pulled out to the left of #shop-builds the same way
   .category-tabs sits left of #shop-panels. */
.build-tabs-viewport {
  position: absolute;
  top: 0;
  right: 100%;
  width: calc(72px * (var(--shop-builds-w) / var(--shop-builds-native-w)));
  height: calc(294px * (var(--shop-builds-w) / var(--shop-builds-native-w)));
}

.build-tabs {
  position: absolute;
  top: 0;
  left: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  transform: scale(calc(var(--shop-builds-w) / var(--shop-builds-native-w))) translateX(4px);
  transform-origin: top left;
}

.build-tab {
  appearance: none;
  border: none;
  background-color: #17151b;
  background-image: var(--tab-grain);
  padding: 0;
  width: 72px;
  height: 72px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 14px 0 0 14px;
  transition: height 0.25s ease, background-color 0.15s ease;
  flex-shrink: 0;
}

.build-tab .tab-icon {
  width: 72px;
  height: 72px;
  object-fit: contain;
  pointer-events: none;
}

.build-tab.is-builds {
  background-color: #492e76;
}

.build-tab.is-saves {
  background-color: #76362f;
}

.build-tab.is-info {
  background-color: #1f294f;
}

.build-tab:not(.active):hover {
  filter: brightness(1.1);
}

.build-tab.active {
  height: 216px;
}

.shop-builds {
  /* width and margin-left are set by app.js (syncShopBuildsAlignment) to
     exactly match #shop-panels + the 6px gap + .tooltip-display combined,
     so this stays aligned with them (excluding .category-tabs) as the
     layout scales with the viewport.

     Height is content-driven (arbitrary number of sections) rather than
     the fixed aspect-ratio the panel used when it was just a static
     image, so the background tiles vertically at its native width
     instead of being cropped/blurred by `cover` as content grows.

     max-height is a hard backstop against runaway growth from adding
     sections indefinitely — app.js (updateAddSectionBtnAvailability)
     disables Add Section before this would actually have to clip
     anything, so overflow:hidden here should only ever act as a safety
     net, not the primary limit. */
  position: relative;
  box-sizing: border-box;
  margin-bottom: 6px;
  min-height: 220px;
  max-height: 950px;
  overflow: hidden;
  background-color: #17141d;
  background-image: url("../frontend_assets/shop_bg_builds.png");
  background-size: 100% auto;
  background-position: top center;
  background-repeat: repeat-y;
  border-radius: 6px;
}

/* The "Saved Builds" tab — no real content yet (placeholder for the
   eventual replacement of the My Builds dropdown, see app.js
   setActiveBuildTab), just swaps the panel's background and hides
   .shop-builds-inner so it reads as an empty, distinct section rather
   than the sections/investment-bars view bleeding through underneath. */
.shop-builds.is-saves-tab {
  min-height: 552px;
  background-image: url("../frontend_assets/shop_bg_saves.png");
}

/* Info tab — same placeholder role as .shop-builds.is-saves-tab above,
   content to be added later (see .info-tab-viewport). */
.shop-builds.is-info-tab {
  min-height: 552px;
  background-image: url("../frontend_assets/shop_bg_info.png");
}

/* Only the sections/investment-bars columns hide — .build-section-actions-row
   itself stays visible on both tabs (its Save Build/search box slot
   swaps which one is shown instead — see .build-actions-left-viewport/
   .build-actions-search-viewport above), same as
   .build-add-section-viewport's role as a page-spanning header above
   both. */
.shop-builds.is-saves-tab .shop-builds-columns,
.shop-builds.is-info-tab .shop-builds-columns {
  display: none;
}

.shop-graphs {
  /* Same width/margin-left sync as .shop-builds (app.js
     syncShopBuildsAlignment applies to both), and a static background
     image, so it uses the simpler aspect-ratio + cover approach
     .shop-builds itself used before it grew dynamic content — the
     timeline chart (app.js renderGraphsChart) renders on top of it. */
  position: relative;
  box-sizing: border-box;
  padding: 24px;
  margin-bottom: 60px;
  aspect-ratio: 2631 / 836;
  background-image: url("../frontend_assets/shop_bg_graphs.png");
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  border-radius: 6px;
  /* Query container for .graph-chart's cqw-based scale below — matches
     .shop-builds-left/.shop-builds-right's role for the other scaled
     sections (.build-sections-container, .investment-bars-content). */
  container-type: inline-size;
}

/* Toggled from app.js (setActiveBuildTab) — the investment graphs don't
   apply to browsing saved builds, and .shop-graphs is a sibling of
   .shop-builds-wrap rather than a descendant of #shop-builds, so the
   .shop-builds.is-saves-tab CSS toggle used elsewhere can't reach it. */
.shop-graphs.is-hidden {
  display: none;
}

/* ---------- build timeline chart ---------- */

/* Reserves layout space for .graph-chart, which is positioned out of
   flow below (same relationship as .build-sections-viewport /
   .build-sections-container) — a plain 100%-of-padded-content-box div so
   .graph-chart's top:0/left:0 lands right at .shop-graphs' padding
   edge instead of ignoring that padding the way an absolutely
   positioned element positioned directly against a padded ancestor
   would. */
.graph-chart-viewport {
  position: relative;
  width: 100%;
  height: 100%;
}

/* Fixed-native-size block (1464x400 — sized to fit .shop-graphs' own
   padded content area at its native/largest rendered size) scaled as one
   rigid unit via a container-query-relative transform — same technique
   as .build-sections-container/.tier-grid, so the whole chart shrinks
   proportionally with the panel instead of reflowing its own layout. */
.graph-chart {
  position: absolute;
  top: 0;
  left: 0;
  display: flex;
  width: var(--graph-chart-native-w);
  height: 380px;
  transform: scale(calc(100cqw / var(--graph-chart-native-w)));
  transform-origin: top left;
}

.graph-axis {
  position: relative;
  flex-shrink: 0;
  width: 44px;
  margin-right: 10px;
}

.graph-axis-label {
  position: absolute;
  left: 0;
  right: 0;
  transform: translateY(50%);
  text-align: right;
  font-family: "ValveOracle", "Segoe UI", sans-serif;
  font-size: 12px;
  font-weight: 700;
  color: #f2ead9;
  opacity: 0.5;
}

/* 4,800 is the game's own "milestone" investment threshold (see
   GRAPH_AXIS_TIERS/items-data.js in app.js) — full opacity plus the same
   yellow as .graph-bar-rect.is-weapon below, so this one tier reads as
   the spike that actually matters instead of blending into the rest. */
.graph-axis-label.is-milestone {
  color: #e5b30b;
  opacity: 1;
}

.graph-plot {
  position: relative;
  flex: 1;
  min-width: 0;
}

.graph-gridline {
  position: absolute;
  left: 0;
  right: 0;
  height: 1px;
  background-color: rgba(240, 236, 226, 0.15);
}

/* Same yellow as .graph-axis-label.is-milestone above, for the same
   4,800-is-the-real-milestone reason. */
.graph-gridline.is-milestone {
  background-color: #e5b30b;
}

.graph-bars {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: flex-end;
  gap: 8px;
}

.graph-bar {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  max-width: 44px;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
}

.graph-bar-icon {
  width: 28px;
  height: 28px;
  flex-shrink: 0;
  margin-bottom: 4px;
  border-radius: 3px;
  object-fit: cover;
}

.graph-bar-rect {
  width: 100%;
  flex-shrink: 0;
  border-radius: 2px 2px 0 0;
}

.graph-bar-rect.is-weapon {
  background-color: #e5b30b;
}

.graph-bar-rect.is-vitality {
  background-color: #a6cd3c;
}

.graph-bar-rect.is-spirit {
  background-color: #b766de;
}

/* .graph-bar-icon hover tooltip — appended straight to <body> from
   app.js (showGraphBarTooltip) so its position:fixed positions against
   the real viewport instead of getting trapped by .graph-chart's own
   scale() transform (see that rule above). Same texture/shadow
   treatment as .build-section-settings-dropdown/.save-build-modal for a
   consistent popover look. */
.graph-bar-tooltip {
  display: none;
  position: fixed;
  z-index: 300;
  /* Fixed (not max-width/shrink-to-fit) on purpose — this box's own width
     is what background-size:100% auto below scales the header band
     against, so a shrink-to-fit width meant every item rendered that band
     at a slightly different scale (narrower items = a shorter band),
     which is what let the header drift outside it (see Lightning Scroll).
     One constant width means one constant band scale for every item,
     every time. 260px comfortably fits the longest real item name
     ("Spirit Shredder Bullets") plus its cost on one line (measured
     ~248px) with a little breathing room. */
  width: 260px;
  padding: 16px 16px 20px;
  box-sizing: border-box;
  /* background-size:100% auto (not cover) so the image's own top edge —
     including its fixed-height colored header band, which the header
     text is meant to sit inside — renders at a constant scale (see the
     fixed-width note above) rather than being rescaled to whatever
     height this tooltip's content happens to produce. Same technique as
     .tooltip-card-calculated-top/.hero-tooltip-section-title for the same
     reason. */
  background-image: url("../frontend_assets/settings_bg_tooltip.png");
  background-size: 100% auto;
  background-repeat: no-repeat;
  background-position: top;
  border-radius: 8px;
  box-shadow: 9px 15px 20px 0px rgb(0 0 0 / 9%);
  pointer-events: none;
}

.graph-bar-tooltip.visible {
  display: block;
}

.graph-bar-tooltip-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 22px;
}

.graph-bar-tooltip-name {
  /* Without this, a 2-word name (e.g. "Lightning Scroll") wraps onto a
     second line whenever the tooltip's own shrink-to-fit width (driven
     by its narrowest content, often the stat list) ends up narrower than
     the header needs — since this is a flex item with the default
     flex-shrink:1 and no flex-basis floor (unlike .graph-bar-tooltip-cost,
     protected by flex-shrink:0), it was the one collapsing. That second
     line spilled below the top-aligned background's colored header band
     (see .graph-bar-tooltip's background-size comment), poking out into
     the plain body texture below it. nowrap forces the tooltip to widen
     to fit the full name on one line instead, which also scales the
     band's rendered height up to match (band height tracks tooltip
     width), so header and band grow together and stay aligned. */
  white-space: nowrap;
  font-family: "ValveOracle", "Segoe UI", sans-serif;
  font-size: 18px;
  font-weight: 700;
  letter-spacing: 0.5px;
  color: #f2ead9;
}

.graph-bar-tooltip-cost {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-shrink: 0;
  font-family: "ValveOracle", "Segoe UI", sans-serif;
  font-size: 18px;
  font-weight: 700;
  color: #00fff5;
  opacity: 1;
}

.graph-bar-tooltip-cost-icon {
  width: auto;
  height: 13px;
}

.graph-bar-tooltip-stats .tooltip-innate-stat {
  color: #dbc198;
  font-size: 14px;
  line-height: 1.45;
}

.graph-bar-tooltip-none {
  font-family: "ValveOracle", "Segoe UI", sans-serif;
  font-size: 14px;
  color: #dbc198;
  opacity: 0.5;
}

.graph-bar-tooltip-total {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px solid rgba(242, 234, 217, 0.15);
  font-family: "ValveOracle", "Segoe UI", sans-serif;
  font-size: 14px;
  color: #f2ead9;
  opacity: 1;
}

.graph-bar-tooltip-total-icon {
  width: auto;
  height: 12px;
}

.shop-builds-inner {
  display: flex;
  /* .build-add-section-viewport (containing .build-section-actions-row —
     Save Build/search, Add Section, Clear) is now a full-width header
     stacked above the left+right columns, rather than sitting inside
     .shop-builds-left alone — see .shop-builds-columns below for the row
     layout that used to live directly on this element. Kept at the same
     20px gap .shop-builds-left used to use between the header and the
     sections below it, so .build-sections-container's vertical offset is
     just this padding-top + header height + that 20px gap. */
  flex-direction: column;
  gap: 20px;
  padding: 8px 24px 24px;
  min-height: 552px;
  box-sizing: border-box;
  /* Query container for the cqw-based scale on .info-tab-content and
     .saved-builds-hero-panel-content below (and .saved-builds-hero-badge,
     which doesn't need a transform of its own but still reads the same
     ratio for its width/height/font-size) — nothing here needs its own
     size to come from content, so containment is safe; more specific
     containers already declared on descendants (.build-section-actions-row,
     .shop-builds-left/-right) still win for their own subtrees regardless. */
  container-type: inline-size;
}

/* Save Build/search/Add Section/Clear live in a header that spans the
   FULL shop-builds-inner width (both columns), so this needs its own
   elevated stacking context — otherwise the Save Build modal it opens
   would paint BEHIND .shop-builds-columns below, which comes later in
   DOM order and would otherwise win the default stacking order since
   neither element has a transform of its own anymore to trigger an
   implicit stacking context. */
.build-add-section-viewport {
  position: relative;
  z-index: 20;
  width: 100%;
}

.shop-builds-columns {
  display: flex;
  /* Both .shop-builds-left and .shop-builds-right now scale their own
     content proportionally (cqw transforms keyed off each column's own
     shrinkable flex-basis, container-type: inline-size), same as the
     rest of the site — so this stays a single non-wrapping row and lets
     that shrink handle narrow viewports, rather than wrapping the right
     column onto its own line where it would just sit at native size
     (and never actually engage its own scale transform). */
  flex-wrap: nowrap;
  align-items: flex-start;
  gap: 20px;
}

.shop-builds-left {
  display: flex;
  flex-direction: column;
  gap: 20px;
  /* flex-basis (not just max-width) is required here: container-type:
     inline-size makes this element size-contained, meaning it can no
     longer size itself from its own content (that's what breaks the
     circular dependency for .build-sections-container's cqw units below)
     — so without an explicit basis it collapses to 0 width instead of
     falling back to content-based auto-sizing. flex-grow:0 caps it at
     that basis (roughly 2 sections of 6 items each, see
     --build-sections-native-w) even when more row space is available,
     leaving .shop-builds-right room alongside it; flex-shrink:1 still
     lets it shrink below that on a narrow viewport. */
  flex: 0 1 var(--build-sections-native-w);
  container-type: inline-size;
}

.shop-builds-right {
  display: flex;
  flex-direction: column;
  gap: 16px;
  /* flex-grow:1 (unlike .shop-builds-left's grow:0) — the investment-bars
     *panel* is meant to stretch and fill whatever row space is left over
     to the right of .shop-builds-left, all the way to .shop-builds-inner's
     own right edge, rather than staying pinned to its content's native
     width. container-type still makes this the cqw reference for
     .investment-bars-content's shrink-only transform below. */
  flex: 1 1 var(--investment-bars-native-w);
  container-type: inline-size;
}

/* The panel background — always 100% of .shop-builds-right's width (which
   itself now grows/shrinks with the row), independent of whatever size
   .investment-bars-content is currently rendering at. overflow:hidden
   clips .investment-bars-header's square top corners to the panel's own
   border-radius, same relationship as .build-section (its rounded
   corners come from the outer element while .build-section-header inside
   is a plain rectangle). */
.investment-bars {
  display: flex;
  flex-direction: column;
  width: 100%;
  border: 2px solid rgba(55, 58, 76, 0.6);
  border-radius: 4px;
  background-color: rgba(23, 21, 27, 0.2);
  box-sizing: border-box;
  overflow: hidden;
}

/* Same styling as .build-section-header, minus the drag handle/title-
   edit/optional-badge/delete-button — this panel isn't a reorderable or
   deletable build section, just a fixed title. */
.investment-bars-header {
  display: flex;
  align-items: center;
  /* Scaled by .shop-builds-right's own local ratio (that column already
     establishes its own query container — see container-type:inline-size
     on it above — so 100cqw here means ITS width, not .shop-builds-inner's)
     rather than staying pinned at native size while the column around it
     shrinks/grows. Plain padding/font-size (not a scale() transform) since
     this header is meant to keep spanning the column's actual width, not
     render at a fixed native width and get scaled as a block. */
  padding: calc(8px * (100cqw / var(--investment-bars-native-w))) calc(12px * (100cqw / var(--investment-bars-native-w)));
  background-color: rgb(15 13 19);
  border-bottom: 1px solid rgba(240, 236, 226, 0.15);
}

.investment-bars-title {
  font-family: "ValveOracle", "Segoe UI", sans-serif;
  font-size: calc(16px * (100cqw / var(--investment-bars-native-w)));
  font-weight: 700;
  letter-spacing: 1px;
  color: #f2ead9;
}

.investment-bars-body {
  display: flex;
  justify-content: center;
  align-items: flex-start;
  width: 100%;
  padding: 12px 14px 13px;
  box-sizing: border-box;
  background-color: transparent;
}

/* Reserves layout space for .investment-bars-content, which is positioned
   out of flow below (same relationship as .build-sections-viewport /
   .build-sections-container) — width/height are set from JS
   (updateInvestmentBarsViewportHeight) to the content's actual post-scale
   rendered size, so .investment-bars-body's justify-content:center
   centers it correctly inside the (now wider) panel. */
.investment-bars-viewport {
  position: relative;
}

/* Fixed-native-width block, scaled as one rigid unit — same technique as
   .build-sections-container/.tier-grid — so the souls column + pills
   shrink proportionally once the panel gets squeezed narrower than their
   native size. Unlike those, the scale factor here is computed in JS
   (updateInvestmentBarsContentScale) rather than a cqw calc(): this
   panel's header + .investment-bars-body's padding + the panel's own
   border sit OUTSIDE this transform and don't shrink with it, so a
   width-only cqw formula (as used previously) makes the panel's total
   height fall out of sync with .build-sections-container's height as
   both columns shrink at slightly different rates — the panel ends up
   taller than the sections beside it. JS instead solves directly for
   whatever scale makes header + padding + border + this (scaled) content
   sum to .build-sections-container's actual rendered height, so the two
   heights always match. `scale(1)` here is just the pre-JS fallback. */
.investment-bars-content {
  position: absolute;
  top: 0;
  left: 0;
  display: flex;
  align-items: flex-start;
  gap: 10px;
  transform: scale(1);
  transform-origin: top left;
}

/* Shared left-hand souls column — row-for-row aligned with each pill's
   11 segments (same 22px row height, same top-to-bottom tier order),
   so it reads as one shared axis for all 3 category pills rather than
   repeating the souls amount inside each pill's own labels. */
.investment-souls-col {
  display: flex;
  flex-direction: column;
  padding-top: 1px;
}

.investment-souls-row {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 4px;
  height: 28px;
  font-size: 14px;
  color: #f2ead9;
  white-space: nowrap;
}

.investment-souls-row.is-milestone {
  font-size: 15px;
  font-weight: 700;
}

.investment-souls-star {
  position: relative;
  top: -2px;
  color: #ffd166;
  font-size: 15px;
}

.investment-bar-col {
  display: flex;
  flex-direction: column;
  /* flex-start (not center) — .investment-bar-pill-row is wider than
     just the pill once the label column is added to its right, and the
     icon below needs to stay centered under the pill specifically (via
     .investment-bar-icon-wrap's own fixed width), not under that wider
     combined row. */
  align-items: flex-start;
  gap: 6px;
}

.investment-bar-pill-row {
  display: flex;
  align-items: flex-start;
}

/* Fixed to the pill's own width so the icon below centers under the
   pill, not under the wider pill-row (pill + labels). */
.investment-bar-icon-wrap {
  display: flex;
  justify-content: center;
  width: 34px;
}

.investment-bar-icon {
  width: 30px;
  height: 30px;
  object-fit: contain;
}

/* overflow:hidden clips the 11 stacked, individually-square segment
   rows to this outer wrapper's own rounded corners. */
.investment-bar {
  display: flex;
  flex-direction: column;
  width: 34px;
  border: 1px solid rgba(55, 58, 76, 0.6);
  border-radius: 4px;
  overflow: hidden;
  background-color: #17151b;
}

.investment-bar-row {
  height: 28px;
  border-top: 1px solid rgba(0, 0, 0, 0.4);
}

.investment-bar-row:first-child {
  border-top: none;
}

.investment-bar-segment {
  height: 100%;
  background-color: transparent;
}

.investment-bar.is-weapon .investment-bar-segment.is-filled {
  background-color: #e5b30b;
}

.investment-bar.is-vitality .investment-bar-segment.is-filled {
  background-color: #a6cd3c;
}

.investment-bar.is-spirit .investment-bar-segment.is-filled {
  background-color: #b766de;
}

/* White border around the segment range a hovered build item accounts
   for within the total — inset box-shadow rather than an actual border
   so it doesn't add layout width and shift adjacent segments. */
.investment-bar-segment.is-highlighted {
  box-shadow: inset 0 0 0 2px #ffffff;
}

/* Tick labels live outside the pill now (see .investment-bar-labels),
   row-for-row aligned with the pill's own segments — same relationship
   as .investment-souls-col alongside the pills, just per-category
   instead of shared. */
.investment-bar-labels {
  display: flex;
  flex-direction: column;
}

.investment-bar-label-row {
  display: flex;
  align-items: center;
  /* Matches .investment-bar-row's height — stays row-for-row aligned
     with the pill's own segments (and the shared souls column) now that
     both use the same 28px row height. */
  height: 28px;
  padding-left: 6px;
}

.investment-bar-tick-label {
  font-size: 13px;
  font-weight: 700;
  color: #f2ead9;
  white-space: nowrap;
  opacity: 0.25;
}

.investment-bar-label-row.is-filled .investment-bar-tick-label {
  opacity: 1;
}

/* A fluid, full-width header row spanning .shop-builds-inner's full
   width, including .shop-builds-right — which has no fixed native width
   of its own (it flex-grows to fill whatever row space is left,
   uncapped), so there's no single width the WHOLE row could scale
   against as one rigid unit the way .build-sections-container does.
   Instead each cluster (Save Build; the saved-builds search box; Add
   Section+Clear) is its own small scaled unit (see .build-actions-left/
   -search/-right-viewport below) that scales by the SAME ratio
   .shop-builds' own background art scales by — see
   --shop-builds-content-native-w in :root — so the controls visibly
   track the background instead of just sitting at native size while it
   scales independently. container-type:inline-size here is what makes
   this row's own current width available (as 100cqw) to those viewports
   below, regardless of how .shop-builds-right's width varies. Shared
   between both tabs (see .shop-builds.is-saves-tab below) — Save
   Build/the search box occupy the same left slot and are never both
   visible at once, so whichever is display:none just collapses out of
   this row's flex layout. */
.build-section-actions-row {
  display: flex;
  align-items: center;
  width: 100%;
  container-type: inline-size;
}

/* Each cluster's viewport: a normal flex item (so they still lay out
   left/right within the fluid row, same as before), sized directly from
   --shop-builds-content-native-w's scale ratio rather than from its own
   flex-shrink amount — so it grows and shrinks together with
   .shop-builds' background as that ratio changes, not just when this row
   specifically runs out of space. */
.build-actions-left-viewport {
  position: relative;
  flex: 0 0 auto;
  width: calc(var(--build-actions-left-native-w) * (100cqw / var(--shop-builds-content-native-w)));
  height: calc(41px * (100cqw / var(--shop-builds-content-native-w)));
}

/* The actual controls, built at native size and scaled down (or up) as
   one rigid unit to match .build-actions-left-viewport's (above) own
   size — same technique as .build-sections-container, just scaled by
   --shop-builds-content-native-w's ratio instead of its own width. */
.build-actions-left {
  position: absolute;
  top: 0;
  left: 0;
  display: flex;
  align-items: center;
  gap: 12px;
  width: var(--build-actions-left-native-w);
  transform: scale(calc(100cqw / var(--shop-builds-content-native-w)));
  transform-origin: top left;
}

/* Saves-tab-only counterpart to .build-actions-left-viewport, occupying
   the same left slot in the row — hidden by default; shown (and
   .build-actions-left-viewport hidden instead) via
   .shop-builds.is-saves-tab below. */
.build-actions-search-viewport {
  display: none;
  position: relative;
  flex: 0 0 auto;
  width: calc(var(--build-actions-search-native-w) * (100cqw / var(--shop-builds-content-native-w)));
  height: calc(41px * (100cqw / var(--shop-builds-content-native-w)));
}

.build-actions-search {
  position: absolute;
  top: 0;
  left: 0;
  width: var(--build-actions-search-native-w);
  transform: scale(calc(100cqw / var(--shop-builds-content-native-w)));
  transform-origin: top left;
}

.shop-builds.is-saves-tab .build-actions-left-viewport,
.shop-builds.is-info-tab .build-actions-left-viewport {
  display: none;
}

.shop-builds.is-saves-tab .build-actions-search-viewport {
  display: block;
}

/* Add Section/Clear/Save Build only make sense while editing a build on
   the Builds tab — neither the Saves tab (just for loading a saved one)
   nor the Info tab (no build content at all) have anything for them to
   act on. */
.shop-builds.is-saves-tab .build-actions-right-viewport,
.shop-builds.is-info-tab .build-actions-right-viewport {
  display: none;
}

/* Same relationship as .build-actions-left-viewport, but pushed to the
   row's far right edge via margin-left:auto — replacing the old
   margin-left:auto that used to live on .build-add-section-btn itself
   back when these controls weren't wrapped in their own scaled unit. */
.build-actions-right-viewport {
  position: relative;
  flex: 0 0 auto;
  width: calc(var(--build-actions-right-native-w) * (100cqw / var(--shop-builds-content-native-w)));
  height: calc(40px * (100cqw / var(--shop-builds-content-native-w)));
  margin-left: auto;
}

.build-actions-right {
  position: absolute;
  top: 0;
  left: 0;
  display: flex;
  align-items: center;
  /* flex-end, not flex-start: the reserved width above accounts for
     Reset Original even though it's display:none except while a curated
     build is loaded (see updateResetOriginalBtnVisibility) — right-
     aligning keeps Add Section hugging the row's actual right edge in
     the (much more common) case where it's the only button shown,
     instead of sliding left into now-empty reserved space. */
  justify-content: flex-end;
  gap: 12px;
  width: var(--build-actions-right-native-w);
  transform: scale(calc(100cqw / var(--shop-builds-content-native-w)));
  transform-origin: top left;
}

.build-add-section-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 10px 28px;
  font-family: "ValveOracle", "Segoe UI", sans-serif;
  font-size: 15px;
  letter-spacing: 1px;
  color: #f2ead9;
  background-color: transparent;
  border: none;
  cursor: pointer;
  white-space: nowrap;
  font-weight: 400;
}

/* Own width matches shop_header_large.png's native aspect ratio (280x81)
   at this button's ~40px rendered height (280 * 40/81), so the texture
   fills the box exactly with no horizontal stretch/squish. */
.build-add-section-btn {
  background-image: url("../frontend_assets/shop_header_large.png");
  background-size: 100% 100%;
  width: 138px;
  font-size: 16px;
}

.build-add-section-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.build-add-section-btn:hover {
  filter: brightness(1.3);
}

.build-add-section-btn:disabled:hover {
  filter: none;
}

.build-add-section-icon {
  width: 14px;
  height: 14px;
  background-image: url("../frontend_assets/icon_plus.svg");
  background-size: contain;
  background-position: center;
  background-repeat: no-repeat;
  filter: invert(1);
}

/* Only visible while a curated build is loaded — see
   updateResetOriginalBtnVisibility in app.js. Same chrome as
   .build-add-section-btn for consistency, just wider (its own text is
   longer) and hidden by default so it doesn't flash visible before that
   check runs. */
.build-reset-original-btn {
  display: none;
  align-items: center;
  justify-content: center;
  padding: 10px 20px;
  font-family: "ValveOracle", "Segoe UI", sans-serif;
  font-size: 16px;
  letter-spacing: 1px;
  color: #f2ead9;
  background-color: transparent;
  background-image: url("../frontend_assets/shop_header_large.png");
  background-size: 100% 100%;
  border: none;
  width: 150px;
  cursor: pointer;
  white-space: nowrap;
  font-weight: 400;
}

.build-reset-original-btn:hover {
  filter: brightness(1.3);
}

.build-hero-option {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 4px;
  border-radius: 4px;
  cursor: pointer;
}

/* Persistent selection highlight — this list (the save-build modal's
   hero list) stays open after picking, unlike a typical dropdown that
   closes on click, so the current choice needs to stay visibly marked. */
.build-hero-option.is-selected {
  background-color: rgba(0, 255, 245, 0.12);
}

.build-hero-option:hover {
  background-color: rgba(255, 255, 255, 0.08);
}

.build-hero-option img {
  width: 50px;
  height: 50px;
  flex-shrink: 0;
  box-sizing: border-box;
  border-radius: 4px;
  object-fit: cover;
  display: block;
}

.build-hero-option-name {
  flex: 1;
  min-width: 0;
  font-family: "ValveOracle", "Segoe UI", sans-serif;
  font-size: 13px;
  color: #f2ead9;
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---------- Save Build button + confirm modal ---------- */

.build-save-btn,
.build-new-build-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 41px;
  /* Same width as .build-add-section-btn, which uses this same image —
     matches shop_header_large.png's own ratio (280x81) at this ~41px
     height, so background-size:100% 100% shows the whole texture with
     no stretch or crop. */
  width: 138px;
  padding: 0 12px;
  box-sizing: border-box;
  background-color: transparent;
  background-image: url("../frontend_assets/shop_header_large.png");
  background-size: 100% 100%;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-family: "ValveOracle", "Segoe UI", sans-serif;
  font-size: 16px;
  color: #f2ead9;
  white-space: nowrap;
  flex-shrink: 0;
}

.build-save-btn:hover,
.build-new-build-btn:hover {
  filter: brightness(1.3);
}

.build-title-input {
  height: 41px;
  /* shop_header_extra_large.png's own ratio (600x81) at this input's
     41px height (600 * 41/81). */
  width: 304px;
  padding: 0px 26px;
  box-sizing: border-box;
  font-family: "ValveOracle", "Segoe UI", sans-serif;
  font-size: 16px;
  letter-spacing: 0.5px;
  color: #f2ead9;
  background-color: transparent;
  background-image: url("../frontend_assets/shop_header_extra_large.png");
  background-size: auto 100%;
  background-position: center;
  border: none;
  flex-shrink: 0;
}

.build-title-input::placeholder {
  color: #f2ead9;
  opacity: 0.4;
}

.build-title-input:focus {
  filter: brightness(1.15);
  outline: 1px solid rgba(0, 255, 245, 0.5);
  outline-offset: 2px;
  border-radius: 2px;
}

.save-build-modal-overlay {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 100;
  align-items: center;
  justify-content: center;
  background-color: rgba(0, 0, 0, 0.6);
}

.save-build-modal-overlay.is-open {
  display: flex;
}

.save-build-modal {
  display: flex;
  flex-direction: column;
  width: 320px;
  max-height: 80vh;
  padding: 20px;
  box-sizing: border-box;
  background-image: url("../frontend_assets/settings_bg_tooltip.png");
  background-size: cover;
  background-position: center;
  border-radius: 8px;
  box-shadow: 9px 15px 20px 0px rgb(0 0 0 / 9%);
}

.save-build-modal-title {
  margin-bottom: 30px;
  font-family: "ValveOracle", "Segoe UI", sans-serif;
  font-size: 18px;
  font-weight: 700;
  letter-spacing: 1px;
  color: #f2ead9;
}

.save-build-modal-label {
  margin-top: 10px;
  margin-bottom: 4px;
  font-family: "ValveOracle", "Segoe UI", sans-serif;
  font-size: 16px;
  letter-spacing: 0.5px;
  color: #f2ead9;
  opacity: 0.6;
}

/* New Build's confirm dialog (see .new-build-confirm-modal in app.js) —
   its one piece of body content, standing in for the name/hero fields
   the Save Build modal has instead. */
.new-build-confirm-message {
  font-family: "ValveOracle", "Segoe UI", sans-serif;
  font-size: 14px;
  line-height: 1.5;
  color: #f2ead9;
  opacity: 0.8;
}

/* settings_bg_tooltip.png is a repeating pattern with a distinct top
   edge — center (.save-build-modal's own default) crops into the middle
   of that pattern, hiding the top edge entirely; top instead keeps the
   image's own top aligned with this modal's top, so that edge stays
   visible. */
.new-build-confirm-modal {
  background-position: top center;
}

.save-build-name-input {
  height: 36px;
  padding: 0 20px;
  box-sizing: border-box;
  font-family: "ValveOracle", "Segoe UI", sans-serif;
  font-size: 14px;
  color: #f2ead9;
  background-color: transparent;
  background-image: url("../frontend_assets/shop_header_extra_large.png");
  background-size: 100% 100%;
  border: 1px solid rgba(240, 236, 226, 0.15);
  border-radius: 4px;
}

.save-build-name-input:focus {
  outline: 1px solid rgba(0, 255, 245, 0.5);
  outline-offset: 1px;
}

.save-build-hero-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-height: 240px;
  overflow-y: auto;
  padding: 4px;
  background-color: rgb(15 13 19);
  border-radius: 4px;
}

/* Same small-note treatment as .new-build-confirm-message, just with its
   own top margin since it follows the hero list rather than the title. */
.save-build-modal-autosave-note {
  margin-top: 10px;
  font-family: "ValveOracle", "Segoe UI", sans-serif;
  font-size: 14px;
  line-height: 1.5;
  color: #f2ead9;
  opacity: 0.8;
}

.save-build-modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 16px;
}

.save-build-cancel-btn,
.save-build-confirm-btn {
  padding: 8px 18px;
  font-family: "ValveOracle", "Segoe UI", sans-serif;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.5px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.save-build-cancel-btn {
  background-color: transparent;
  background-image: url("../frontend_assets/shop_header_medium.png");
  background-size: 100% 100%;
  color: #f2ead9;
}

.save-build-cancel-btn:hover {
  filter: brightness(1.3);
}

.save-build-confirm-btn {
  background-color: transparent;
  background-image: url("../frontend_assets/shop_header_medium.png");
  background-size: 100% 100%;
  color: #f2ead9;
}

.save-build-confirm-btn:hover {
  filter: brightness(1.3);
}

/* ---------- saved-builds tab (hero roster + per-hero build panel) ---------- */

.saved-builds-viewport {
  display: none;
  width: 100%;
  gap: 24px;
}

.shop-builds.is-saves-tab .saved-builds-viewport {
  display: flex;
}

/* ---------- info tab (placeholder — content added later) ---------- */

.info-tab-viewport {
  display: none;
  position: relative;
  width: 100%;
  /* Height is set from JS (syncInfoTabViewportHeight) to
     .info-tab-content's actual post-scale rendered height, same
     relationship as .build-sections-viewport/.build-sections-container. */
}

.shop-builds.is-info-tab .info-tab-viewport {
  display: block;
}

/* Built at native size and scaled down as one rigid unit — same technique
   as .build-sections-container — so it shrinks/grows in step with the
   rest of .shop-builds-inner (see container-type:inline-size on it)
   instead of staying pinned at native size while its surroundings scale
   independently. */
.info-tab-content {
  position: absolute;
  top: 0;
  left: 0;
  width: var(--shop-builds-content-native-w);
  transform: scale(calc(100cqw / var(--shop-builds-content-native-w)));
  transform-origin: top left;
}

/* ---------- info tab content (Calculated Stats explainer) ---------- */

.info-section {
  margin-bottom: 24px;
}

.info-section-text {
  margin: 36px 0 20px;
  font-size: 18px;
  line-height: 1.6;
  color: #130601;
}

.info-stat-value-na {
  font-style: italic;
  opacity: 0.6;
}

.info-stat-table {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 6px;
}

.info-stat-category {
  background-image: url("../frontend_assets/statistical_stats_tooltip_bg.png");
  background-size: cover;
  background-position: top center;
  border-radius: 8px;
  padding: 14px 16px;
  box-sizing: border-box;
  font-size: 16px;
}

.info-stat-category-header {
  font-family: "ValveOracle", sans-serif;
  font-weight: 700;
  font-size: 16px;
  padding-bottom: 8px;
  margin-bottom: 8px;
  border-bottom: 2px solid currentColor;
}

.info-stat-row {
  display: grid;
  grid-template-columns: 22px 1fr auto;
  align-items: center;
  gap: 10px;
  padding: 6px 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

.info-stat-row:last-child {
  border-bottom: none;
  padding-bottom: 0;
}

.info-stat-icon {
  width: 22px;
  height: 22px;
  object-fit: contain;
}

.info-stat-name {
  font-size: 16px;
  color: #000000;
}

.info-stat-value {
  min-width: 70px;
  font-size: 16px;
  font-weight: 700;
  color: #c1ad8e;
  text-align: right;
  white-space: nowrap;
}

.saved-builds-empty {
  padding: calc(24px * (100cqw / var(--shop-builds-content-native-w)));
  font-family: "ValveOracle", "Segoe UI", sans-serif;
  font-size: calc(16px * (100cqw / var(--shop-builds-content-native-w)));
  font-weight: 700;
  color: #000000;
  opacity: 1;
  text-align: center;
}

/* Left 2/3: every hero, always browsable (not just ones with saved
   builds yet) — click a portrait to see their builds in
   .saved-builds-hero-panel alongside. 8 columns, same tight grid look as
   the in-game hero-select screen. */
.saved-builds-heroes {
  flex: 0 0 66.6667%;
  display: grid;
  grid-template-columns: repeat(10, 1fr);
  gap: 4px;
  align-content: flex-start;
}

.saved-builds-hero-portrait {
  position: relative;
  /* Matches settings_bg_portrait.png's own aspect ratio (640/920) rather
     than the hero portrait images' (1168/1584) — the box now takes the
     background's shape, so background-size:cover below fills it exactly
     with no letterboxing/cropping mismatch to reconcile. Not
     overflow:hidden — the img/background already fill this box exactly
     on their own (nothing here would ever bleed past it), and
     .saved-builds-hero-badge (below) deliberately hangs half outside
     this box's own corner, which overflow:hidden would otherwise clip. */
  aspect-ratio: 640 / 920;
  cursor: pointer;
  box-sizing: border-box;
  background-image: url("../frontend_assets/settings_bg_portrait.png");
  background-size: cover;
  background-position: center;
}

.saved-builds-hero-portrait img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Same hover treatment as a shop item (.mod-box:hover) — lift + gentle
   sway, reusing that same @keyframes selected-rotate. */
.saved-builds-hero-portrait:hover {
  transform: translateY(-2px);
  animation: selected-rotate 3.6s ease-in-out infinite;
  z-index: 2;
}

/* Keeps the same lift+sway animation .saved-builds-hero-portrait:hover
   uses running continuously (not just while the mouse happens to be
   over it) — same "stays animated while selected" idea as
   .mod-box.selected. The selection indicator itself is the ::after
   overlay below (card_selected.png, same asset/technique
   .card-selected-overlay uses for a selected shop item) rather than a
   border/outline here. */
.saved-builds-hero-portrait.is-selected {
  transform: translateY(-2px);
  animation: selected-rotate 3.6s ease-in-out infinite;
  z-index: 2;
}

/* card_selected.png as an ::after overlay rather than this element's own
   background-image — the hero <img> (a real child, painted after any
   background-image) would otherwise completely cover it. Pseudo-element
   content paints after real children by default, so no z-index is
   needed to keep this above the <img>. */
.saved-builds-hero-portrait.is-selected::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: url("../frontend_assets/card_selected.png");
  background-size: 100% 100%;
  pointer-events: none;
}

/* While the search box has a query, any hero with no build matching it
   dims — only rendered onto portraits at all when a query is present
   (see renderSavedBuildsHeroGrid in app.js), so an empty search leaves
   every portrait at full opacity. */
.saved-builds-hero-portrait.is-dimmed {
  opacity: 0.5;
}

/* Saved-build count for this hero — only rendered at all when it's
   above 0 (see renderSavedBuildsHeroGrid in app.js), so there's no "0"
   badge cluttering every hero without a build yet. bottom:0/right:0 puts
   this badge's own bottom-right corner flush with the portrait's; the
   translate(20%, 20%) then nudges it out a bit further past that corner.
   z-index — the portrait itself (.saved-builds-hero-portrait) has no
   z-index of its own outside :hover, so this badge (an absolutely
   positioned child) escapes into the shared grid-level stacking context
   instead of being trapped under its own portrait, and outranks
   neighboring portraits there so it isn't hidden by whichever one
   happens to overlap it. */
.saved-builds-hero-badge {
  position: absolute;
  bottom: 0;
  right: 0;
  transform: translate(20%, 20%);
  z-index: 3;
  /* Scaled by the same site-wide ratio everything else in .shop-builds
     scales by (see container-type:inline-size on .shop-builds-inner),
     rather than staying pinned at a fixed size while its surroundings
     shrink/grow. Sized via plain width/height/font-size (not a nested
     scale() transform) so translate(20%, 20%) above keeps resolving
     against this box's own current size with no extra math needed. */
  width: calc(30px * (100cqw / var(--shop-builds-content-native-w)));
  height: calc(30px * (100cqw / var(--shop-builds-content-native-w)));
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: rgb(15 13 19);
  border-radius: 3px;
  font-family: "ValveOracle", "Segoe UI", sans-serif;
  font-size: calc(20px * (100cqw / var(--shop-builds-content-native-w)));
  font-weight: 700;
  color: #f2ead9;
  pointer-events: none;
}

/* Right 1/3: whichever hero is currently selected — name/portrait header
   plus a plain list of that hero's saved builds (no per-row portrait,
   unlike the old flat card grid this replaced — you already just picked
   the hero via its portrait on the left, so repeating it per row would
   be redundant). */
.saved-builds-hero-panel {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  /* This panel is a fluid flex item (its width comes from flex layout,
     not a fixed native design width), so its fixed-px sub-properties are
     scaled individually via calc() here and below — same site-wide ratio
     as .saved-builds-hero-badge — rather than wrapped as one native-width
     block and transform:scale'd (the technique .info-tab-content uses),
     which needs a stable native width to scale from. */
  gap: calc(12px * (100cqw / var(--shop-builds-content-native-w)));
  padding: calc(10px * (100cqw / var(--shop-builds-content-native-w)));
}

.saved-builds-hero-panel-header {
  display: flex;
  align-items: center;
  justify-content: center;
  padding-bottom: calc(10px * (100cqw / var(--shop-builds-content-native-w)));
  border-bottom: 2px solid rgb(37 33 45);
}

/* Holds the selected hero's logo (hero_icons/hero_logos/) — sized by
   the img rule below; the font/color rules here are unused while that's
   the only content, kept only as a plain fallback in case a future
   hero's logo file ever goes missing again. */
.saved-builds-hero-panel-name {
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: "ValveOracle", "Segoe UI", sans-serif;
  font-size: calc(16px * (100cqw / var(--shop-builds-content-native-w)));
  font-weight: 700;
  letter-spacing: 0.5px;
  color: #f2ead9;
}

.saved-builds-hero-panel-name img {
  height: calc(80px * (100cqw / var(--shop-builds-content-native-w)));
  width: calc(180px * (100cqw / var(--shop-builds-content-native-w)));
  object-fit: contain;
  display: block;
}

.saved-builds-hero-build-list {
  display: flex;
  flex-direction: column;
  gap: calc(6px * (100cqw / var(--shop-builds-content-native-w)));
}

.saved-builds-hero-build-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: calc(8px * (100cqw / var(--shop-builds-content-native-w)));
  height: calc(50px * (100cqw / var(--shop-builds-content-native-w)));
  margin-top: calc(10px * (100cqw / var(--shop-builds-content-native-w)));
  padding: 0 calc(34px * (100cqw / var(--shop-builds-content-native-w))) 0 calc(12px * (100cqw / var(--shop-builds-content-native-w)));
  box-sizing: border-box;
  background-color: transparent;
  background-image: url("../frontend_assets/shop_header_extra_large.png");
  background-size: 100% 100%;
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 6px;
  cursor: pointer;
}

.saved-builds-hero-build-row:hover {
  filter: brightness(1.1);
}

.saved-builds-hero-build-row.is-current {
  border-color: rgba(0, 255, 245, 0.6);
  background-color: rgb(15 13 19);
}

/* One per row (see heroMiniFile in app.js) — every row in this list is
   the same selected hero, per-row rather than shared once because that's
   what was asked for, not because the icon could ever actually differ
   row to row within a single hero's build list. */
.saved-builds-hero-build-mini-icon {
  width: calc(54px * (100cqw / var(--shop-builds-content-native-w)));
  height: calc(54px * (100cqw / var(--shop-builds-content-native-w)));
  flex-shrink: 0;
  object-fit: contain;
}

/* Same lift+sway hover treatment as a shop item's own icon
   (.mod-box:hover), applied to this row's mini icon specifically —
   while the row itself is hovered, or is the currently-loaded build. */
.saved-builds-hero-build-row:hover .saved-builds-hero-build-mini-icon,
.saved-builds-hero-build-row.is-current .saved-builds-hero-build-mini-icon {
  transform: translateY(-2px);
  animation: selected-rotate 3.6s ease-in-out infinite;
}

.saved-builds-hero-build-name {
  flex: 1;
  min-width: 0;
  font-family: "ValveOracle", "Segoe UI", sans-serif;
  font-size: calc(16px * (100cqw / var(--shop-builds-content-native-w)));
  color: #f2ead9;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Save date (month/day/year) — muted and fixed to its own content width
   so it never gets squeezed by a long build name, which truncates
   instead (see flex:1/min-width:0 on .saved-builds-hero-build-name
   above). */
.saved-builds-hero-build-date {
  flex-shrink: 0;
  font-family: "ValveOracle", "Segoe UI", sans-serif;
  font-size: calc(15px * (100cqw / var(--shop-builds-content-native-w)));
  color: #f2ead9;
  opacity: 0.5;
  white-space: nowrap;
}

.saved-builds-hero-build-delete {
  position: absolute;
  top: 50%;
  right: 6px;
  transform: translateY(-50%);
  width: calc(18px * (100cqw / var(--shop-builds-content-native-w)));
  height: calc(18px * (100cqw / var(--shop-builds-content-native-w)));
  border-radius: 3px;
  /* mask (not background-image + filter:invert) so the icon can be tinted
     to an exact color via background-color, rather than only inverted. */
  background-color: #c1ad8e;
  -webkit-mask-image: url("../frontend_assets/icon_close.svg");
  mask-image: url("../frontend_assets/icon_close.svg");
  -webkit-mask-size: 14px 14px;
  mask-size: 14px 14px;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  opacity: 1;
}

/* background-color now tints the masked icon itself (see above), so hover
   feedback uses a brightness filter instead of the old translucent-white
   highlight rect, which would otherwise paint over/replace that color. */
.saved-builds-hero-build-delete:hover {
  filter: brightness(1.3);
}

/* Curated builds (see js/curated-builds.js) show this instead of the
   delete button above — same corner-badge placement, just no icon since
   it carries text instead. */
.saved-builds-hero-build-author-badge {
  position: absolute;
  top: -12px;
  right: -12px;
  padding: calc(2px * (100cqw / var(--shop-builds-content-native-w)))
    calc(8px * (100cqw / var(--shop-builds-content-native-w)));
  border-radius: 8px;
  background: rgb(118 54 47);
  border: 1px solid rgba(255, 255, 255, 0.15);
  color: #f2ead9;
  font-family: "ValveOracle", "Segoe UI", sans-serif;
  font-size: calc(12px * (100cqw / var(--shop-builds-content-native-w)));
  font-weight: 700;
  white-space: nowrap;
}

/* Filters .saved-builds-hero-panel's build list by name as you type (see
   savedBuildsSearchInputEl in app.js) — lives in .build-actions-search
   (style.css, near .build-section-actions-row), the Saves-tab
   counterpart to the Builds tab's Save Build button; that shared row's
   own scaled unit handles this input's actual positioning/sizing, so
   this rule is just the input's own look (same shop_header_extra_large
   texture the old Builds-tab title editor used, before naming a build
   moved entirely into the Save Build modal). */
.saved-builds-search-input {
  display: block;
  height: 41px;
  /* shop_header_extra_large.png's own ratio (600x81) at this input's
     41px height (600 * 41/81). */
  width: 304px;
  padding: 0px 26px;
  box-sizing: border-box;
  font-family: "ValveOracle", "Segoe UI", sans-serif;
  font-size: 16px;
  letter-spacing: 0.5px;
  color: #f2ead9;
  background-color: transparent;
  background-image: url("../frontend_assets/shop_header_extra_large.png");
  background-size: auto 100%;
  background-position: center;
  border: none;
}

.saved-builds-search-input::placeholder {
  color: #f2ead9;
  opacity: 0.4;
}

.saved-builds-search-input:focus {
  filter: brightness(1.15);
  outline: 1px solid rgba(0, 255, 245, 0.5);
  outline-offset: 2px;
  border-radius: 2px;
}

/* Height is set by app.js (syncBuildSectionsScale) to match
   .build-sections-container's actual scaled rendered height, since that
   content is position:absolute below (out of flow) and its arbitrary,
   content-driven height can't be known from CSS alone the way
   .tier-grid's fixed 960px can. */
.build-sections-viewport {
  position: relative;
  width: 100%;
}

/* Same technique as .tier-grid (style.css ~line 607): a fixed native-px
   box scaled as one rigid unit via a container-query-relative transform,
   so the whole build area (sections, cards, gaps) shrinks proportionally
   with the shop grid instead of just wrapping into more rows. Absolutely
   positioned (like .tier-grid) so its native, pre-scale footprint doesn't
   reserve that much space in the actual page layout — only the
   post-transform visual size matters, which .build-sections-viewport's
   JS-measured height then exposes to normal flow. */
.build-sections-container {
  position: absolute;
  top: 0;
  left: 0;
  width: var(--build-sections-native-w);
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 32px;
  transform: scale(calc(100cqw / var(--build-sections-native-w)));
  transform-origin: top left;
}

.build-section {
  position: relative;
  box-sizing: border-box;
  /* Purely a safety floor below app.js's 1-column/1-row snap sizes —
     must not exceed them, or this would silently override the intended
     snap size and make a "1 column" section render wider than that. */
  min-width: 100px;
  min-height: 100px;
  border: 2px solid rgba(55, 58, 76, 0.6);
  border-radius: 4px;
  background-color: transparent;
  /* repeating-linear-gradient repeats procedurally relative to this box's
     own size, not a tiled raster image, so the diagonal hatch is always
     seamless no matter how the section is resized. */
  background-image: repeating-linear-gradient(-45deg, rgb(55 58 76 / 50%) 0px, rgb(55 58 76 / 50%) 1px, transparent 1px, #00000000 22px);
}

.build-section::before {
  content: "";
  position: absolute;
  inset: 4px;
  border: 1px solid rgba(81, 88, 60, 0.4);
  border-radius: 4px;
  pointer-events: none;
}

/* Content lives in its own scrolling wrapper (rather than .build-section
   itself scrolling) so .build-section-resize-handle, an absolutely
   positioned child of .build-section, stays pinned to the visible corner
   instead of scrolling away with the content when a section is resized
   smaller than what it holds. */
.build-section-scroll {
  height: 100%;
  display: flex;
  flex-direction: column;
  overflow: auto;
}

.build-section.optional {
  border-style: dashed;
  border-color: rgb(5 255 255 / 77%);
}

.build-section.drag-over-section {
  border-color: #00fff5;
}

.build-section-reorder-preview {
  flex-shrink: 0;
  box-sizing: border-box;
  border: 2px dashed rgba(0, 255, 245, 0.6);
  border-radius: 6px;
  background: rgba(0, 255, 245, 0.05);
}

.build-section-header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  background-color: rgb(15 13 19);
  border-bottom: 1px solid rgba(240, 236, 226, 0.15);
}

.build-section-drag-handle {
  flex-shrink: 0;
  width: 16px;
  height: 16px;
  cursor: grab;
  background-image: url("../frontend_assets/icon_move.svg");
  background-size: contain;
  background-position: center;
  background-repeat: no-repeat;
  filter: invert(1);
  opacity: 0.75;
}

.build-section-drag-handle:active {
  cursor: grabbing;
}

.build-section-title {
  /* Sized to its own content, not stretched — .build-section-description
     takes whatever space is left (see below), rather than the two
     splitting the header 50/50 regardless of how long either one is. */
  flex: 0 1 auto;
  /* min-width:0 overrides the flex item default of min-width:auto (which
     otherwise refuses to shrink narrower than the text's own intrinsic
     width, forcing the header to grow instead of truncating). */
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-family: "ValveOracle", "Segoe UI", sans-serif;
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 1px;
  color: #f2ead9;
  outline: none;
}

.build-section-title:focus {
  outline: 1px solid rgba(0, 255, 245, 0.5);
  outline-offset: 2px;
  border-radius: 2px;
}

/* Optional freeform note beside the title (e.g. "At least 1 for Green
   Spike") — same contentEditable/rename-on-blur pattern as the title
   itself (see handleSectionsFocusOut/updateSectionDescription in
   app.js), just visually secondary: smaller, italic, dimmer. */
.build-section-description {
  flex: 1 1 auto;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-family: "ValveOracle", "Segoe UI", sans-serif;
  font-size: 13px;
  font-style: italic;
  color: rgba(242, 234, 217, 0.6);
  outline: none;
}

.build-section-description:focus {
  outline: 1px solid rgba(0, 255, 245, 0.5);
  outline-offset: 2px;
  border-radius: 2px;
  color: #f2ead9;
}

.build-section-description:empty:not(:focus)::before {
  content: "Description";
  color: rgba(242, 234, 217, 0.35);
}

/* A single-item section is little more than one shop card wide — the
   name/description text just gets clipped by that width anyway, so hide
   both rather than show an ellipsis-truncated fragment of them. Toggled
   via .single-item on .build-section itself (set in buildSectionEl from
   section.items.length), so it re-evaluates on every render as items are
   added/removed rather than being fixed at section-creation time. */
.build-section.single-item .build-section-title,
.build-section.single-item .build-section-description {
  display: none;
}

/* .build-section-description normally has flex:1 1 auto, which is what
   pushes the settings/delete buttons to the header's right edge — with
   both text elements hidden above, that pushing mechanism is gone, so
   the buttons would otherwise collapse next to the drag handle instead
   of staying anchored right. margin-left:auto on the first of the two
   (settings) reclaims that same right-alignment for both, since it also
   carries deleteBtn along with it. */
.build-section.single-item .build-section-settings-btn {
  margin-left: auto;
}

.build-section-settings-btn {
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.build-section-settings-btn::after {
  content: "";
  width: 100%;
  height: 100%;
  background-image: url("../frontend_assets/icon_settings.svg");
  background-size: contain;
  background-position: center;
  background-repeat: no-repeat;
  filter: invert(1);
  opacity: 0.7;
}

.build-section-settings-btn:hover::after {
  opacity: 1;
}

/* Sits as a sibling of .build-section-scroll (see buildSectionEl in
   app.js) so a short section's own overflow:auto content area can't
   clip or scroll it away — positioned relative to .build-section itself,
   just under the header where the settings button lives. Same
   background/shadow/radius treatment as .save-build-modal (texture,
   no border), just sized to this dropdown's own much smaller footprint. */
.build-section-settings-dropdown {
  display: none;
  position: absolute;
  top: 38px;
  right: 8px;
  z-index: 10;
  flex-direction: column;
  gap: 10px;
  min-width: 190px;
  padding: 10px;
  background-image: url("../frontend_assets/settings_bg_tooltip.png");
  background-size: cover;
  background-position: center;
  border-radius: 8px;
  box-shadow: 9px 15px 20px 0px rgb(0 0 0 / 9%);
}

.build-section-settings-dropdown.is-open {
  display: flex;
}

.build-section-settings-title {
  font-family: "ValveOracle", "Segoe UI", sans-serif;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.5px;
  color: #f2ead9;
}

.build-section-settings-optional-row {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 6px;
  font-family: "ValveOracle", "Segoe UI", sans-serif;
  font-size: 13px;
  letter-spacing: 0.5px;
  color: #f2ead9;
  cursor: pointer;
  user-select: none;
}

/* The real click target — invisible, sized/positioned to sit exactly over
   the visual .build-section-settings-optional-box below and stacked above
   it via z-index. Not styled via appearance:none + its own ::after,
   because generated content doesn't reliably render on <input> (a
   replaced element) in real browsers — same reasoning as
   .search-scope-checkbox/.search-scope-box. */
.build-section-settings-optional-row input[type="checkbox"] {
  position: absolute;
  top: 1px;
  left: 0;
  width: 17.62px;
  height: 17.62px;
  margin: 0;
  z-index: 1;
  opacity: 0;
  cursor: pointer;
}

.build-section-settings-optional-box {
  position: relative;
  width: 17.62px;
  height: 17.62px;
  margin-top: 1px;
  flex-shrink: 0;
  box-sizing: border-box;
  background-color: rgb(15, 13, 19);
  border-radius: 3px;
}

/* Checked state shows a centered checkmark instead of the browser's
   native tick — a real <img> (see app.js), not a mask-image ::after.
   Same reasoning as .search-scope-checkmark: the mask technique checked
   out in every computed-style test but reportedly still wasn't actually
   visible for real users, so this drops it rather than keep chasing it.
   Driven by JS (a "change" listener, not the :checked selector this used
   before) for the same reason — one less thing that has to render
   correctly on its own for the checkmark to show up. filter forces it
   pure white; z-index explicitly outranks the checkbox (z-index: 1). */
.build-section-settings-optional-checkmark {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 12px;
  height: 12px;
  z-index: 2;
  filter: brightness(0) invert(1);
  pointer-events: none;
  display: none;
}

.build-section-settings-optional-checkmark.is-checked {
  display: block;
}

.build-section-settings-optional-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.build-section-settings-optional-desc {
  font-size: 11px;
  font-weight: 400;
  letter-spacing: 0.25px;
  color: #f2ead9;
  opacity: 0.5;
}

.build-section-settings-colors {
  display: grid;
  grid-template-columns: repeat(5, 28px);
  gap: 6px;
}

.build-section-color-swatch {
  width: 28px;
  height: 28px;
  box-sizing: border-box;
  border: 1px solid rgba(255, 255, 255, 0.4);
  border-radius: 3px;
  cursor: pointer;
}

.build-section-color-swatch.is-selected {
  border: 2px solid #f2ead9;
}

.build-section-delete-btn {
  /* icon_close.svg already draws its own circular badge (see
     .build-item-remove-btn for the same treatment) — this is just a
     positioning box, no separate background/border-radius. */
  flex-shrink: 0;
  width: 16.2px;
  height: 16.2px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.build-section-delete-btn::after {
  content: "";
  width: 100%;
  height: 100%;
  background-image: url("../frontend_assets/icon_close.svg");
  background-size: contain;
  background-position: center;
  background-repeat: no-repeat;
  filter: invert(1);
  opacity: 0.7;
}

.build-section-delete-btn:hover::after {
  opacity: 1;
}

.build-section-items {
  display: flex;
  flex: 1;
  flex-wrap: wrap;
  align-content: flex-start;
  gap: 4px;
  padding: 12px;
  /* box-sizing: border-box (global reset) means padding eats into
     min-height — 125px alone left only ~101px of content room, so an
     empty/new section collapsed shorter than a one-row-filled section,
     which naturally grows past that insufficient floor. 149px = one
     80x125 card + the 12px top/bottom padding, so both states match. */
  min-height: 149px;
}

.build-slot-placeholder {
  width: 80px;
  height: 125px;
  flex-shrink: 0;
  /* card_slot.png isn't the same aspect ratio as a .mod-box (80:125), so
     `cover` cropped its left/right edges to fill the box — `contain`
     shows the whole image, letterboxed top/bottom instead. */
  background-image: url("../frontend_assets/card_slot.png");
  background-size: contain;
  background-position: center;
  background-repeat: no-repeat;
  border-radius: 4px;
  opacity: 0.85;
  pointer-events: none;
}

.build-section-resize-handle {
  position: absolute;
  right: 2px;
  bottom: 2px;
  width: 16px;
  height: 16px;
  cursor: nwse-resize;
  background-image: url("../frontend_assets/icon_resize.svg");
  background-size: contain;
  background-position: center;
  background-repeat: no-repeat;
  filter: invert(1);
  opacity: 0.6;
}

.build-section-resize-handle:hover {
  opacity: 1;
}

.build-item-card {
  position: relative;
}

/* Moves the WHOLE card (mod-box + remove-btn together) as one rigid unit
   on hover, rather than relying on two separate elements each running
   their own independent instance of the same @keyframes animation and
   trusting them to stay in sync — a single shared transform on their
   common ancestor guarantees the button can't visually drift from the
   icon it's attached to. .mod-box:hover's own transform is neutralized
   right below so it doesn't ALSO fire and compound with this one when the
   pointer is directly over the icon itself. */
.build-item-card:hover {
  transform: translateY(-2px);
  animation: selected-rotate 3.6s ease-in-out infinite;
  z-index: 2;
}

.build-item-card .mod-box:hover {
  transform: none;
  animation: none;
}

.build-item-remove-btn {
  /* Same black-square-badge treatment as .mod-box-add-badge. */
  position: absolute;
  top: 2px;
  right: 2px;
  z-index: 5;
  width: 22px;
  height: 22px;
  display: none;
  align-items: center;
  justify-content: center;
  background-color: #000000;
  border-radius: 3px;
  cursor: pointer;
}

.build-item-remove-btn::after {
  content: "";
  width: 20px;
  height: 20px;
  background-image: url("../frontend_assets/icon_close.svg");
  background-size: contain;
  background-position: center;
  background-repeat: no-repeat;
  filter: invert(1);
  opacity: 0.85;
}

.build-item-card:hover .build-item-remove-btn {
  display: flex;
}

.mod-box.used {
  opacity: 0.35;
  filter: grayscale(0.6);
  cursor: not-allowed;
}

.mod-box.dragging,
.build-item-card.dragging {
  opacity: 0.4;
}

/* Reserves layout space for .category-tabs (below), which is scaled and
   taken out of flow — width/height computed from the SAME ratio
   #shop-panels itself scales by (--tooltip-display-h, JS-synced to its
   actual rendered height, over --panel-h, its native design height), so
   the tab stack visibly shrinks/grows in step with the panel beside it
   instead of staying pinned at native size. 450px/72px are the stack's
   own constant native height/width — always exactly one of the 3
   category tabs + the search button is "active" (216px) and the rest
   are 72px (see .category-tab.active below), so this total never
   actually varies, unlike e.g. .build-sections-container's arbitrary
   content-driven height — no JS measurement needed. */
.category-tabs-viewport {
  position: relative;
  flex-shrink: 0;
  width: calc(72px * (var(--tooltip-display-h) / var(--panel-h)));
  height: calc(450px * (var(--tooltip-display-h) / var(--panel-h)));
}

.category-tabs {
  position: absolute;
  top: 0;
  left: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  transform: scale(calc(var(--tooltip-display-h) / var(--panel-h))) translateX(4px);
  transform-origin: top left;
}

.category-tab {
  appearance: none;
  border: none;
  background-color: #17151b;
  background-image: var(--tab-grain);
  padding: 0;
  width: 72px;
  height: 72px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 14px 0 0 14px;
  transition: height 0.25s ease, background-color 0.15s ease;
  flex-shrink: 0;
}

.category-tab .tab-icon {
  width: 72px;
  height: 72px;
  object-fit: contain;
  pointer-events: none;
}

.category-tab.is-weapon {
  background-color: #e5b30b;
}

.category-tab.is-vitality {
  background-color: #a6cd3c;
}

.category-tab.is-spirit {
  background-color: #b766de;
}

.category-tab.is-hero {
  background-color: #4d4c9a;
}

.category-tab:not(.active):hover {
  filter: brightness(1.1);
}

.category-tab.active {
  height: 216px;
}

#tab-search {
  width: 72px;
  height: 72px;
  border: none;
  background-color: #aba48e;
  background-image: var(--tab-grain);
  cursor: pointer;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 14px 0 0 14px;
  flex-shrink: 0;
  transition: height 0.25s ease, filter 0.15s ease;
}

#tab-search img {
  width: 72px;
  height: 72px;
  object-fit: contain;
  pointer-events: none;
}

#tab-search:hover,
#tab-search.is-open {
  filter: brightness(1.1);
}

#tab-search.is-open {
  height: 216px;
}

/* ---------- shop panel ---------- */

#shop-panels {
  position: relative;
  width: var(--panel-w);
  max-width: 100%;
  container-type: inline-size;
}

.shop-panel {
  display: none;
  position: relative;
  width: 100%;
  aspect-ratio: 1076 / 960;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  border-radius: 6px;
  overflow: hidden;
}

.shop-panel.active {
  display: block;
}

/* Tier-quadrant grid, laid out at its native 1076x960 design size and
   then scaled down as one rigid unit to match the panel's actual
   rendered width (100cqw / 1076px, via #shop-panels being the query
   container). Keeps every hand-tuned offset/size in the tier system
   below proportionally consistent instead of drifting as the panel
   shrinks with the browser window. */
.tier-grid {
  position: absolute;
  top: 0;
  left: 0;
  width: 1076px;
  height: 960px;
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: auto auto;
  transform: scale(calc(100cqw / 1076px));
  transform-origin: top left;
}

/* Search panel: not a tiered 2x2 grid like the other categories — a
   vertical stack (fixed search box, then one grid section per
   category). Cropped to the background art's own aspect ratio
   (1825 x 1618) so the panel never grows past the background image;
   .search-results scrolls internally instead of pushing the panel
   taller. */
.shop-panel.category-search {
  aspect-ratio: 1825 / 1618;
  overflow: hidden;
  background-size: 100% auto;
  background-position: top center;
  background-repeat: no-repeat;
  background-color: #17151b;
}

.shop-panel.category-search.active {
  display: flex;
  flex-direction: column;
}

.search-input-wrap {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 20px;
  padding-top: 20px;
  flex-shrink: 0;
}

.search-input-inner {
  position: relative;
  /* shop_header_extra_large.png's own ratio (600x81) at this input's 41px
     height (600 * 41/81) — matches this input's width to its background's
     natural size at that height, so the background renders once instead
     of tiling (background-repeat's default) to fill a wider box. */
  width: 304px;
  max-width: calc(100% - 48px);
}

/* Same shop_header_extra_large.png treatment as .build-title-input, rather
   than the plain rounded-rect input box this used before. */
#item-search-input {
  width: 100%;
  height: 41px;
  box-sizing: border-box;
  font-size: 16px;
  padding: 0px 36px 0px 16px;
  font-family: "ValveOracle", "Segoe UI", sans-serif;
  letter-spacing: 0.5px;
  color: #f2ead9;
  background-color: transparent;
  background-image: url("../frontend_assets/shop_header_extra_large.png");
  background-size: auto 100%;
  background-position: center;
  border: none;
}

#item-search-input::placeholder {
  color: #f2ead9;
  opacity: 0.4;
}

#item-search-input:focus {
  filter: brightness(1.15);
  outline: 1px solid rgba(0, 255, 245, 0.5);
  outline-offset: 2px;
  border-radius: 2px;
}

/* Only shown once there's actually a query to clear. */
.search-clear-btn {
  display: none;
  position: absolute;
  top: 50%;
  right: 10px;
  transform: translateY(-50%);
  width: 18px;
  height: 18px;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  z-index: 2;
}

.search-clear-btn::after {
  content: "";
  width: 100%;
  height: 100%;
  display: block;
  /* mask (not background-image) so the SVG can be tinted to an exact
     color via background-color, rather than only inverted/dimmed. */
  background-color: #f2ead9;
  -webkit-mask-image: url("../frontend_assets/icon_close.svg");
  mask-image: url("../frontend_assets/icon_close.svg");
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  opacity: 0.6;
}

.search-clear-btn:hover::after {
  opacity: 1;
}

#item-search-input:not(:placeholder-shown) ~ .search-clear-btn {
  display: flex;
}

/* Sits to the right of the search bar, in the same row (see
   .search-input-wrap's flex layout above) — narrows which fields the
   query matches against instead of the old per-stat-name checkbox list
   (see applySearchFilter in app.js). */
.search-scope-toggles {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-shrink: 0;
}

.search-scope-chip {
  position: relative;
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  user-select: none;
}

/* The actual click/tap target — sized and positioned to sit exactly over
   the visible .search-scope-box (which is purely decorative) and stacked
   above it via z-index, rather than being an invisible 1x1px dot that
   relies on label-click-forwarding through a sibling. That indirection
   was unreliable: .search-scope-box gets a continuous hover animation
   (translateY + rotate, see :hover below) that shifts its hit region, so
   a mousedown/mouseup pair could straddle the animated box and land on
   two different elements, silently dropping the click. Making the
   checkbox itself the real, stationary hit target sidesteps that. */
.search-scope-checkbox {
  position: absolute;
  top: 50%;
  left: 0;
  transform: translateY(-50%);
  width: 28px;
  height: 22px;
  margin: 0;
  z-index: 1;
  opacity: 0;
  cursor: pointer;
}

.search-scope-box {
  position: relative;
  width: 28px;
  height: 22px;
  flex-shrink: 0;
  box-sizing: border-box;
  background-color: rgb(15, 13, 19);
  border-radius: 3px;
  transition: transform 0.1s ease;
}

/* Same hover treatment as a shop item (.mod-box:hover) — lift + gentle
   sway, reusing that same @keyframes selected-rotate rather than
   defining a near-identical one just for this. */
.search-scope-chip:hover .search-scope-box {
  transform: translateY(-2px);
  animation: selected-rotate 3.6s ease-in-out infinite;
}

/* Checked state shows a checkmark centered in the box instead of
   highlighting the box itself. A real <img> (see app.js), not a
   mask-image ::after — the mask approach rendered correctly in every
   computed-style check but reportedly still didn't actually show up for
   real users, so this drops that technique entirely rather than keep
   trying to diagnose it. filter forces it pure white regardless of the
   source SVG's own fill; z-index explicitly outranks the checkbox
   (z-index: 1) so it's unambiguously painted on top, not just invisible
   underneath it by coincidence of opacity. */
.search-scope-checkmark {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 16px;
  height: 16px;
  z-index: 2;
  filter: brightness(0) invert(1);
  pointer-events: none;
  display: none;
}

.search-scope-checkmark.is-checked {
  display: block;
}

.search-scope-checkbox:focus-visible ~ .search-scope-box {
  outline: 2px solid rgba(255, 255, 255, 0.6);
  outline-offset: 2px;
}

.search-scope-label {
  font-family: "ValveOracle", "Segoe UI", sans-serif;
  font-size: 16px;
  color: #f2ead9;
  white-space: nowrap;
}

.search-results {
  position: relative;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  margin: 18px 0;
}

/* Native-1076px-wide canvas scaled down as one rigid unit — same
   technique as .tier-grid — so .mod-box cards in search results render
   at the same size as the same items shown in their own category panel.
   Unlike .tier-grid this has no fixed native height (search results are
   a variable-length list, not a 2x2 grid), so .search-results (a
   position:relative scrolling container) derives its scrollable area
   directly from this positioned + transformed descendant's rendered
   bounds instead of needing a JS-measured height reservation. */
.search-content-scaled {
  position: absolute;
  top: 0;
  left: 0;
  width: 1076px;
  padding: 24px 24px 40px;
  box-sizing: border-box;
  transform: scale(calc(100cqw / 1076px));
  transform-origin: top left;
}

.search-section {
  margin-bottom: 28px;
}

.search-section-title {
  font-family: "ValveOracle", sans-serif;
  font-size: 18px;
  letter-spacing: 1px;
  /* text-transform:capitalize only uppercases first letters, it doesn't
     lowercase the rest — lowercase the whole string, then override just
     the first letter back to uppercase, so this reads as "Weapon" (or
     "Vitality"/"Spirit") regardless of the source label's own casing. */
  text-transform: lowercase;
  color: #f2ead9;
  margin: 0 0 12px;
  text-shadow: 0 2px 6px rgba(0, 0, 0, 0.6);
}

.search-section-title::first-letter {
  text-transform: uppercase;
}

.search-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.tier-quadrant {
  position: relative;
  padding: 20px;
  min-width: 0;
  min-height: 0;
}

.tier-quadrant.tier-800 {
  grid-column: 1;
  grid-row: 1;
  transform: translate(var(--tier-800-x), var(--tier-800-y));
}

.tier-quadrant.tier-1600 {
  grid-column: 2;
  grid-row: 1;
  transform: translate(var(--tier-1600-x), var(--tier-1600-y));
}

.tier-quadrant.tier-3200 {
  grid-column: 1;
  grid-row: 2;
  transform: translate(var(--tier-3200-x), var(--tier-3200-y));
}

.tier-quadrant.tier-6400 {
  grid-column: 2;
  grid-row: 2;
  transform: translate(var(--tier-6400-x), var(--tier-6400-y));
}

/* Sits directly on .tier-grid at the coordinates in
   TIER_PRICE_TAG_POSITIONS (app.js) — filling in the text that
   shop_bg_<cat>.png's own "empty tag" artwork no longer bakes in. */
.tier-price-tag {
  position: absolute;
  display: flex;
  align-items: center;
  gap: 4px;
  pointer-events: none;
}

.tier-price-tag-icon {
  height: 26px;
  width: auto;
  display: block;
}

.tier-price-tag-value {
  font-family: "RetailText", "ValveOracle", sans-serif;
  font-size: 30px;
  font-weight: 700;
  color: #83a67a;
  letter-spacing: -1px;
  white-space: nowrap;
}

.tier-price-tag-value.tier-800 {
  font-size: 24px;
}

/* Heroes tab's release-year tags — own class rather than reusing
   .tier-price-tag/.tier-price-tag-value (the shop panels' soul-cost
   tags), since these don't share their icon and are conceptually a
   different thing (a year, not a price) even though the layout technique
   (see buildHeroYearTag/HERO_YEAR_TAG_POSITIONS in app.js) is the same. */
.year-tag {
  position: absolute;
  display: flex;
  align-items: center;
  pointer-events: none;
}

.year-tag-value {
  font-family: "RetailText", "ValveOracle", sans-serif;
  font-size: 30px;
  font-weight: 700;
  color: #83a67a;
  letter-spacing: -1px;
  white-space: nowrap;
}

.year-tag-value.year-2025 {
  font-size: 24px;
}

.year-tag-value.year-2026 {
  font-size: 32px;
}

@media (max-width: 820px) {
  .shop-panel {
    aspect-ratio: auto;
    overflow: visible;
  }

  .tier-grid {
    position: static;
    width: auto;
    height: auto;
    transform: none;
    grid-template-columns: 1fr;
    grid-template-rows: repeat(4, auto);
  }

  .tier-quadrant.tier-800,
  .tier-quadrant.tier-1600,
  .tier-quadrant.tier-3200,
  .tier-quadrant.tier-6400 {
    grid-column: 1;
    transform: none;
  }

  .tier-quadrant.tier-800 {
    grid-row: 1;
  }
  .tier-quadrant.tier-1600 {
    grid-row: 2;
  }
  .tier-quadrant.tier-3200 {
    grid-row: 3;
  }
  .tier-quadrant.tier-6400 {
    grid-row: 4;
  }
}

.mods-container {
  display: grid;
  gap: 4px;
  align-content: start;
}

.tier-quadrant.tier-800 .mods-container {
  grid-template-columns: repeat(var(--tier-800-cols), 80px);
}
.tier-quadrant.tier-1600 .mods-container {
  grid-template-columns: repeat(var(--tier-1600-cols), 80px);
}
.tier-quadrant.tier-3200 .mods-container {
  grid-template-columns: repeat(var(--tier-3200-cols), 80px);
}
.tier-quadrant.tier-6400 .mods-container {
  grid-template-columns: repeat(var(--tier-6400-cols), 80px);
}

/* ---------- hero grid (Heroes tab) ---------- */

/* Exact same structure/technique as .tier-grid above (native 1076x960,
   scaled down as one rigid unit), just keyed by release-year quadrant
   instead of soul-cost tier. */
.hero-grid {
  position: absolute;
  top: 0;
  left: 0;
  width: 1076px;
  height: 960px;
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: auto auto;
  transform: scale(calc(100cqw / 1076px));
  transform-origin: top left;
}

.hero-quadrant {
  position: relative;
  padding: 20px;
  min-width: 0;
  min-height: 0;
}

.hero-quadrant.year-2025 {
  grid-column: 1;
  grid-row: 1;
  transform: translate(var(--year-2025-x), var(--year-2025-y));
}

.hero-quadrant.year-2024 {
  grid-column: 2;
  grid-row: 1;
  transform: translate(var(--year-2024-x), var(--year-2024-y));
}

.hero-quadrant.year-2026 {
  grid-column: 1;
  grid-row: 2;
  transform: translate(var(--year-2026-x), var(--year-2026-y));
}

.hero-quadrant.year-2027 {
  grid-column: 2;
  grid-row: 2;
  transform: translate(var(--year-2027-x), var(--year-2027-y));
}

@media (max-width: 820px) {
  .hero-grid {
    position: static;
    width: auto;
    height: auto;
    transform: none;
    grid-template-columns: 1fr;
    grid-template-rows: repeat(4, auto);
  }

  .hero-quadrant.year-2025,
  .hero-quadrant.year-2024,
  .hero-quadrant.year-2026,
  .hero-quadrant.year-2027 {
    grid-column: 1;
    transform: none;
  }

  .hero-quadrant.year-2025 {
    grid-row: 1;
  }
  .hero-quadrant.year-2024 {
    grid-row: 2;
  }
  .hero-quadrant.year-2026 {
    grid-row: 3;
  }
  .hero-quadrant.year-2027 {
    grid-row: 4;
  }
}

.hero-container {
  display: grid;
  gap: 4px;
  align-content: start;
}

.hero-quadrant.year-2025 .hero-container {
  grid-template-columns: repeat(var(--year-2025-cols), 80px);
}
.hero-quadrant.year-2024 .hero-container {
  grid-template-columns: repeat(var(--year-2024-cols), 80px);
}
.hero-quadrant.year-2026 .hero-container {
  grid-template-columns: repeat(var(--year-2026-cols), 80px);
}
.hero-quadrant.year-2027 .hero-container {
  grid-template-columns: repeat(var(--year-2027-cols), 80px);
}

/* ---------- item card ---------- */

.mod-box {
  position: relative;
  width: 80px;
  height: 125px;
  cursor: pointer;
  filter: drop-shadow(0 3px 6px rgba(0, 0, 0, 0.3));
  transition: transform 0.1s ease, opacity 0.15s ease;
}

.mod-box:hover {
  transform: translateY(-2px);
  animation: selected-rotate 3.6s ease-in-out infinite;
  z-index: 2;
}

.mod-box-add-badge {
  display: none;
  position: absolute;
  top: 4px;
  right: 4px;
  z-index: 6;
  width: 22px;
  height: 22px;
  align-items: center;
  justify-content: center;
  background-color: #000000;
  border-radius: 3px;
  cursor: pointer;
}

.mod-box-add-badge::after {
  content: "";
  width: 20px;
  height: 20px;
  background-image: url("../frontend_assets/icon_plus.svg");
  background-size: contain;
  background-position: center;
  background-repeat: no-repeat;
  filter: invert(1);
}

.mod-box:hover:not(.used) .mod-box-add-badge {
  display: flex;
}

.mod-box.selected {
  /* Independent transform properties (rotate/translate/scale) so the
     sway and the bob can run on their own animations with different
     durations, rather than one combined keyframe list where both
     would peak/trough at the same instant. */
  scale: 1.2;
  animation: selected-rotate 3.6s ease-in-out infinite, selected-float 2.4s ease-in-out infinite;
  z-index: 4;
}

@keyframes selected-rotate {
  0%,
  100% {
    rotate: -2deg;
  }
  50% {
    rotate: 2deg;
  }
}

@keyframes selected-float {
  0%,
  100% {
    translate: 0 0;
  }
  50% {
    translate: 0 -6px;
  }
}

.mod-box.search-hidden {
  display: none;
}

.search-section-title.search-hidden {
  display: none;
}

/* .is-upgrade-highlight — set by app.js (updateUpgradeHighlight) on
   whatever the hovered/selected item's tooltip lists under "Upgrades
   To" — stays exempt from dimming too, so that upgrade path reads
   clearly against the rest of the panel fading out. */
.shop-panel.dimming .mod-box:not(.is-hovered):not(.selected):not(.is-upgrade-highlight) {
  opacity: 0.5;
}

/* While something's selected, only its own card still responds to
   :hover (app.js's mouseenter handler already skips every other card's
   tooltip/highlight logic entirely) — this neutralizes the matching
   lift+sway CSS animation on those other cards too, so hovering them has
   no visible effect at all until the selection is cleared. */
.shop-panel.dimming .mod-box:not(.is-hovered):not(.selected):hover {
  transform: none;
  animation: none;
  z-index: auto;
}

/* Same "other items don't respond to hover while something's selected"
   rule, applied to the add-to-build badge specifically — except
   .is-upgrade-highlight items (this hovered item's own "Upgrades To"
   targets), which stay fully hoverable/addable like the selected item
   itself does. */
.shop-panel.dimming .mod-box:not(.is-hovered):not(.selected):not(.is-upgrade-highlight):hover .mod-box-add-badge {
  display: none;
}

/* Same selected/dimming treatment as .mod-box.selected/.shop-panel.dimming
   .mod-box above, for hero cards — no .is-upgrade-highlight equivalent
   since heroes don't have an "Upgrades To" concept. */
.hero-card.selected {
  scale: 1.2;
  animation: selected-rotate 3.6s ease-in-out infinite, selected-float 2.4s ease-in-out infinite;
  z-index: 4;
}

.shop-panel.dimming .hero-card:not(.is-hovered):not(.selected) {
  opacity: 0.5;
}

.shop-panel.dimming .hero-card:not(.is-hovered):not(.selected):hover {
  transform: none;
  animation: none;
  z-index: auto;
}

.card-background {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  border-radius: 4px;
}

.card-selected-overlay {
  display: none;
  position: absolute;
  inset: 0;
  background-image: url("../frontend_assets/card_selected.png");
  background-size: 82px 126px;
  background-position: center;
  border-radius: 4px;
  z-index: 3;
  pointer-events: none;
}

.mod-box.selected .card-selected-overlay {
  display: block;
}

/* Same overlay, same asset, for hero cards. */
.hero-card-selected-overlay {
  display: none;
  position: absolute;
  inset: 0;
  background-image: url("../frontend_assets/card_selected.png");
  background-size: 82px 126px;
  background-position: center;
  border-radius: 4px;
  z-index: 3;
  pointer-events: none;
}

.hero-card.selected .hero-card-selected-overlay {
  display: block;
}

.paper-texture {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  mix-blend-mode: overlay;
  opacity: 1;
  border-radius: 4px;
  pointer-events: none;
}

.mod-icon-container {
  position: absolute;
  top: 2px;
  left: 2px;
  right: 2px;
  height: 76px;
}

.mod-icon {
  width: 100%;
  height: 100%;
  background-size: contain;
  background-position: center;
  background-repeat: no-repeat;
}

.mod-name-label-container {
  /* Vertically centered between the bottom of .mod-icon-container
     (top: 2px + height: 76px = 78px) and the bottom of
     .card-background (inset: 0, i.e. the card's own bottom edge). */
  position: absolute;
  left: 4px;
  right: 4px;
  top: 78px;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
}

.mod-name-label {
  font-size: 13px;
  line-height: 1.2;
  color: #16130f;
  font-weight: 600;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.15);
}

/* Tier-6400 (soul cost 6400) name label colors, per category. Uses
   the card's own data attributes (rather than an ancestor tier/
   category class) so the same rule applies both in each category's
   tiered panel and in the Search page's flat item grids. */
.mod-box[data-category="weapon"][data-tier="6400"] .mod-name-label {
  color: #dbc198;
}

.mod-box[data-category="vitality"][data-tier="6400"] .mod-name-label {
  color: #abcbb4;
}

.mod-box[data-category="spirit"][data-tier="6400"] .mod-name-label {
  color: #dccdea;
}

/* ---------- hero card ---------- */

/* Same shell as .mod-box/.card-background/.paper-texture/.mod-icon-
   container/.mod-icon/.mod-name-label-container/.mod-name-label above,
   under dedicated classes rather than reused directly — see
   buildHeroCard's comment in app.js for why (avoiding the item-specific
   machinery those classes are swept into elsewhere). No .selected/.used/
   add-badge/search-hidden equivalents yet since hero cards don't
   participate in any of that (no tooltip/selection wiring on them yet
   either — "we'll add tooltips later"). */
.hero-card {
  position: relative;
  width: 80px;
  height: 125px;
  cursor: pointer;
  filter: drop-shadow(0 3px 6px rgba(0, 0, 0, 0.3));
  transition: transform 0.1s ease, opacity 0.15s ease;
}

.hero-card:hover {
  transform: translateY(-2px);
  animation: selected-rotate 3.6s ease-in-out infinite;
  z-index: 2;
}

.hero-card-background {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  border-radius: 4px;
}

.hero-card-paper {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  mix-blend-mode: overlay;
  opacity: 1;
  border-radius: 4px;
  pointer-events: none;
}

.hero-card-icon-container {
  position: absolute;
  top: 2px;
  left: 2px;
  right: 2px;
  height: 76px;
}

.hero-card-icon {
  width: 100%;
  height: 100%;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

.hero-card-name-container {
  /* Vertically centered between the bottom of .hero-card-icon-container
     (top: 2px + height: 76px = 78px) and the bottom of
     .hero-card-background (inset: 0, i.e. the card's own bottom edge). */
  position: absolute;
  left: 4px;
  right: 4px;
  top: 78px;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
}

.hero-card-name {
  width: 80%;
  height: 100%;
  object-fit: contain;
  /* drop-shadow() has no separate opacity control — an rgba() color is
     the way to dial one in (here, 50%). */
  filter: drop-shadow(2px 0px 3px rgba(0, 0, 0, 0.5));
}

.item-tag {
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  padding: 0px 8px;
  border-radius: 2px;
  letter-spacing: 0.5px;
}

.tag-active {
  /* Centered on the bottom edge of .mod-icon-container
     (top: 2px + height: 76px = 78px from the card top). */
  position: absolute;
  top: 78px;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 2;
  background: #14110f;
  color: #f0e6d2;
}

.tag-imbue {
  /* A rectangle spanning the mod-icon's width, sitting at its bottom
     edge (icon bottom = 78px from the card top). Base height 14px.
     When the item is also Active, the rectangle grows taller
     (growing upward, bottom edge staying pinned at 78px) and the
     text — via its 14px line-height — naturally stays toward the
     top, leaving room at the bottom for the Active badge (which
     sits at a higher z-index) to overlap. */
  position: absolute;
  left: 2px;
  right: 2px;
  top: 64px;
  height: 14px;
  line-height: 14px;
  text-align: center;
  z-index: 1;
  border-radius: 0;
  font-size: 11px;
  letter-spacing: 1px;
  background: #7d2f9c;
  color: #f4e4fb;
}

.mod-box[data-active="1"][data-imbue="1"] .tag-imbue {
  top: 56px;
  height: 22px;
}

/* ---------- tooltip ---------- */

/* Item tooltip now lives inside .tooltip-display (top-down, in place
   of the old cursor-following floating tooltip). One card is built
   once and its content/background images are swapped on hover. */
.tooltip-card {
  /* Built at its native size (matching .tooltip-display's own native
     width) and scaled down as one rigid unit — same technique as
     .tier-grid — so the 42/44/36px insets and every fixed font-size/
     icon-size inside it shrink together with .tooltip-display instead
     of overflowing and getting clipped once the panel (and therefore
     .tooltip-display) shrinks below native size. */
  display: none;
  position: absolute;
  top: 0;
  left: 0;
  width: var(--tooltip-display-native-w);
  /* No top/bottom padding of its own — that room is instead
     .tooltip-scroll-viewport's --tooltip-crop-top/-bottom inset (see
     :root and .tooltip-scroll-viewport in style.css). Left/right stay
     padded so text doesn't sit flush against the card's side edges. */
  padding: 0px 36px 0px 44px;
  box-sizing: border-box;
  transform: scale(calc(100cqw / var(--tooltip-display-native-w)));
  transform-origin: top left;
  filter: drop-shadow(0 3px 6px rgba(0, 0, 0, 0.3));
}

.tooltip-card.visible {
  display: block;
  height: var(--tooltip-card-height);
  /* Combined with (not layered on top of) .tooltip-card's own scale —
     a transform declared here replaces the base rule's entirely, so the
     scale has to be repeated alongside the y-offset or it'd be lost
     while the card is visible. */
  transform: scale(calc(100cqw / var(--tooltip-display-native-w))) translateY(var(--tooltip-card-y-offset));
}

/* ---------- hero tooltip (prototype) ---------- */

/* Same native-size-then-scale-down technique as .tooltip-card above —
   a separate card (not folded into .tooltip-card) since heroes don't
   have a cost/Calculated Stats/Notes section, just their own top bar +
   4 stacked stat sections. */
.hero-tooltip-card {
  display: none;
  position: absolute;
  top: 0;
  left: 0;
  width: var(--tooltip-display-native-w);
  padding: 0px 36px 0px 44px;
  box-sizing: border-box;
  transform: scale(calc(100cqw / var(--tooltip-display-native-w)));
  transform-origin: top left;
  filter: drop-shadow(0 3px 6px rgba(0, 0, 0, 0.3));
}

.hero-tooltip-card.visible {
  display: block;
}

.hero-tooltip-top {
  width: 100%;
  background-image: url("../frontend_assets/tooltip_bg_hero_top.png");
  background-size: 100% 100%;
  background-repeat: no-repeat;
  background-position: top center;
  background-color: #2a2432;
  padding: 14px 18px;
  box-sizing: border-box;
  border-radius: 8px 8px 0 0;
}

.hero-tooltip-name {
  display: block;
  height: 44px;
  width: auto;
  max-width: 100%;
  margin: 0 auto;
  object-fit: contain;
}

.hero-tooltip-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 8px;
}

.hero-tooltip-tag-pill {
  font-family: "ValveOracle", "Segoe UI", sans-serif;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.5px;
  color: #f2ead9;
  background: rgba(0, 0, 0, 0.35);
  border-radius: 3px;
  padding: 3px 9px;
}

/* Scoped separately from the base .hero-tooltip-tag-pill size (shared
   with .hero-tooltip-top's own hero-wide tags, e.g. Tank/Brawler) so this
   only resizes the weapon card's own tags. */
.hero-tooltip-weapon-tag-pill {
  font-size: 18px;
}

.hero-tooltip-section {
  width: 100%;
  margin-top: 16px;
}

/* No aspect-ratio (unlike .tooltip-card-top, which this was originally
   modeled on) — with aspect-ratio:500/115 the box was locked to ~91px
   tall regardless of padding (way more than a single centered line of
   text + padding needs), so padding-top/-bottom couldn't actually shrink
   it. background-size:auto (not 100% 100%) + top-aligned position crops
   the background art's own leftover height off the bottom instead,
   same technique .tooltip-card-calculated-top uses. */
.hero-tooltip-section-title {
  width: 100%;
  background-size: 100% auto;
  background-repeat: no-repeat;
  background-position: top center;
  background-color: #2a2432;
  padding: 8px 18px;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  /* space-between only matters for the 3 stat sections, which have a
     second child (the category icon, right-aligned) — Character Info's
     title has just the one text child, so this is a no-op there. */
  justify-content: space-between;
  font-family: "ValveOracle", sans-serif;
  font-weight: 700;
  font-size: 20px;
  color: #ffffff;
  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
  border-radius: 8px 8px 0 0;
  overflow: hidden;
}

.hero-tooltip-section-title-icon {
  height: 28px;
  width: auto;
  object-fit: contain;
  flex-shrink: 0;
}

.hero-tooltip-section-body {
  width: 100%;
  height: auto;
  background-size: 100% auto;
  background-repeat: no-repeat;
  background-position: top center;
  background-color: #1c1720;
  padding: 18px;
  box-sizing: border-box;
  border-radius: 0 0 8px 8px;
  color: #f0ece2;
  font-size: 13px;
  line-height: 1.5;
}

/* Character info's title/body have no tooltip_bg_<cat>_*.png of their own
   (not tied to a weapon/vitality/spirit category) — plain dark card,
   same treatment as .tooltip-card-notes. */
.hero-tooltip-info-title {
  background-image: url("../frontend_assets/tooltip_bg_hero_top.png");
}

.hero-tooltip-info-body {
  background-image: url("../frontend_assets/tooltip_bg_hero.png");
}

/* Character Info values are free text off the wiki (e.g. Mo & Krill's
   pronouns "They/Them; He/Him (Mo); He/Him (Krill)", or a multi-name
   Internal Names list) rather than the short numeric stats
   .hero-tooltip-stat-value is otherwise used for — nowrap there just runs
   them off the edge of the tooltip instead of wrapping. min-width:0
   overrides the flex item default of min-width:auto, which otherwise
   refuses to shrink narrower than the text's own unwrapped width even
   with white-space:normal set. */
.hero-tooltip-info-body .hero-tooltip-stat-row {
  align-items: flex-start;
}

.hero-tooltip-info-body .hero-tooltip-stat-label {
  flex-shrink: 0;
}

.hero-tooltip-info-body .hero-tooltip-stat-value {
  white-space: normal;
  min-width: 0;
  text-align: right;
  word-break: break-word;
}

/* Mirrors deadlock.wiki's own .weaponcard-container widget (see
   buildHeroWeaponCardHtml in app.js) — sits at the top of the Weapon
   Stats body, above the regular stat rows. */
.hero-tooltip-weapon-card {
  position: relative;
  margin-bottom: 12px;
  padding-bottom: 12px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.15);
}

/* Positioned behind .hero-tooltip-weapon-info (lower z-index) and taken
   out of flow, so the two can overlap — the image reads as part of the
   card's own background rather than a boxed-off column next to the text. */
.hero-tooltip-weapon-image {
  position: absolute;
  top: 0;
  right: 0;
  z-index: 0;
  width: 210px;
  height: 216px;
  background-size: contain;
  background-position: center;
  background-repeat: no-repeat;
  border-radius: 4px;
}

.hero-tooltip-weapon-info {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.hero-tooltip-weapon-name {
  font-family: "ValveOracle", sans-serif;
  font-weight: 700;
  font-size: 28px;
  color: #ffffff;
  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}

.hero-tooltip-weapon-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.hero-tooltip-weapon-dps {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 20px;
  font-weight: 700;
  color: #f2ead9;
}

.hero-tooltip-weapon-falloff {
  font-size: 16px;
  color: #f2ead9;
  opacity: 0.7;
}

.hero-tooltip-weapon-notes {
  margin-bottom: 12px;
  padding-bottom: 12px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.15);
  font-size: 16px;
  font-style: italic;
  color: #f2ead9;
  opacity: 0.8;
}

.hero-tooltip-stat-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 4px 0;
}

.hero-tooltip-stat-row + .hero-tooltip-stat-row {
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}

.hero-tooltip-stat-label-group {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

.hero-tooltip-stat-icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  object-fit: contain;
}

.hero-tooltip-stat-label {
  font-size: 18px;
  opacity: 0.7;
}

.hero-tooltip-stat-value {
  font-size: 18px;
  font-weight: 700;
  color: #f2ead9;
  white-space: nowrap;
}

.hero-tooltip-stat-value-wrap {
  position: relative;
  display: inline-block;
}

/* Boon Scaling (deadlock.wiki's infobox-h-spiritboon — how much a stat
   grows with character level) — was previously just a trailing "+X" on
   the value text; now its own badge, same idea as .tooltip-stat-scaling
   for item stat boxes but green (vs. that one's purple) and anchored to
   the top-right corner of the value instead of the top-left of a box. */
.hero-tooltip-stat-boon {
  position: absolute;
  top: -16px;
  right: 15px;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 0px 6px;
  border-radius: 8px;
  background: #2f9c4a;
  color: #e0fbdd;
  font-size: 14px;
  font-weight: 700;
  white-space: nowrap;
}

.hero-tooltip-stat-boon-icon {
  width: 16px;
  height: 18px;
  object-fit: contain;
}

.tooltip-card-top {
  width: 100%;
  aspect-ratio: 500 / 115;
  background-size: 100% 100%;
  background-repeat: no-repeat;
  background-position: center;
  background-color: #2a2432;
  padding: 14px 18px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  justify-content: center;
  border-radius: 8px 8px 0 0;
}

.tooltip-card-name {
  font-family: "ValveOracle", sans-serif;
  font-weight: 700;
  font-size: 26px;
  color: #ffffff;
  line-height: 1.15;
  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}

.tooltip-card-cost {
  display: flex;
  align-items: center;
  gap: 5px;
  margin-top: 6px;
  font-size: 22px;
  font-weight: 700;
  color: #00fff5;
  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}

.tooltip-soul-icon {
  width: auto;
  height: 24px;
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2));
}

.tooltip-card-body {
  /* Height follows content (padding + text), not a fixed aspect
     ratio. The background is sized to the card's width at the
     image's native ratio (never stretched/squished) and top-aligned,
     so a short body simply crops the bottom of the texture off
     instead of distorting it. */
  width: 100%;
  height: auto;
  background-size: 100% auto;
  background-repeat: no-repeat;
  background-position: top center;
  background-color: #1c1720;
  padding: 18px;
  box-sizing: border-box;
  border-radius: 0 0 8px 8px;
  color: #f0ece2;
  font-size: 13px;
  line-height: 1.5;
}

.tooltip-placeholder {
  opacity: 0.7;
}

/* Empirically-derived "calculated" souls-per-unit breakdown — its own
   standalone rounded card (same margin-top:16px separation as
   .tooltip-card-notes below), but built from its own top+body image pair
   (tooltip_bg_actual_top.png/tooltip_bg_actual.png, both the exact same
   500x115 / 500-wide dimensions as .tooltip-card-top/-body) rather than
   reusing the item-category art, since the calculated-stats math applies
   the same way regardless of which shop category the item is in. */
.tooltip-card-calculated {
  position: relative;
  display: none;
  width: 100%;
  margin-top: 16px;
}

.tooltip-card-calculated.visible {
  display: block;
}

/* Same typography as .tooltip-card-name (ValveOracle) rather than its own
   size, and top-left positioned (no vertical centering) like plain block
   text — a single title line doesn't need .tooltip-card-top's
   flex-column-centered treatment. background-size switches from
   100% 100% (stretch-to-fit, .tooltip-card-top's approach) to 100% auto +
   top-aligned so the now-shorter bar crops the image's leftover height
   off the bottom instead of squashing it, same technique
   .tooltip-card-body already uses for its own (taller) art. */
.tooltip-card-calculated-top {
  width: 100%;
  background-image: url("../frontend_assets/tooltip_bg_actual_top.png");
  background-size: 100% auto;
  background-repeat: no-repeat;
  background-position: top center;
  background-color: #2a2432;
  padding: 14px 18px;
  box-sizing: border-box;
  font-family: "ValveOracle", sans-serif;
  font-weight: 700;
  font-size: 18px;
  color: #ffffff;
  line-height: 1.15;
  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
  border-radius: 8px 8px 0 0;
  overflow: hidden;
}

/* Anchored to .tooltip-card-calculated (see above) rather than
   .tooltip-card-calculated-top, so its -4px/-4px offset can poke outside
   the top bar's corner instead of being clipped by -top's own
   overflow:hidden. Same hover treatment as a shop item (.mod-box:hover). */
.tooltip-calculated-help-btn {
  position: absolute;
  top: -4px;
  right: -4px;
  width: 30px;
  height: 30px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: rgb(28 28 63);
  border: none;
  border-radius: 4px;
  font-family: "ValveOracle", "Segoe UI", sans-serif;
  font-size: 18px;
  font-weight: 700;
  color: #ffffff;
  cursor: pointer;
  z-index: 1;
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.25));
}

.tooltip-calculated-help-btn:hover {
  transform: translateY(-2px);
  animation: selected-rotate 3.6s ease-in-out infinite;
  z-index: 2;
}

.tooltip-card-calculated-body {
  width: 100%;
  height: auto;
  background-image: url("../frontend_assets/tooltip_bg_actual.png");
  background-size: 100% auto;
  background-repeat: no-repeat;
  background-position: top center;
  background-color: #1c1720;
  padding: 0 18px 18px 18px;
  box-sizing: border-box;
  border-radius: 0 0 8px 8px;
  /* padding-top:0 above means .tooltip-section's own margin-top:10px (the
     first stat row's) would otherwise collapse straight through this
     element's top edge and render as a visible gap ABOVE the card instead
     of space inside it — overflow:hidden establishes a new block
     formatting context so that margin stays contained here instead. */
  overflow: hidden;
  color: #f0ece2;
  font-size: 13px;
  line-height: 1.5;
}

/* .tooltip-section's shared margin-top:10px (see below) is scoped out
   here rather than removed globally — ability descriptions elsewhere
   still rely on it for spacing between multiple stacked sections, but
   within this card it just pushed the first (and typically only visual
   grouping of) stat row down from the now-flush top bar. */
.tooltip-card-calculated-body .tooltip-section {
  margin-top: 0;
}

/* Each priced stat reuses .tooltip-section/-header/-type/-body as-is (see
   buildStatisticalStatsHtml) — only the title's color is overridden inline
   per stat, from STATISTICAL_STAT_CATEGORY_COLORS. .tooltip-section-desc
   itself is scoped here (not changed globally — it's shared with ability
   descriptions elsewhere, which still need normal flowing paragraph text)
   to lay its two child spans out as calculation-left/result-right instead
   of one flowing sentence. */
.tooltip-card-calculated-body .tooltip-section-desc {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}

.tooltip-calculated-calc-expr {
  color: #cfc7d6;
  opacity: 0.5;
}

.tooltip-calculated-calc-result {
  font-weight: 700;
  white-space: nowrap;
}

/* Border/margin separating this from the priced stats above lives on the
   wrap (not .tooltip-calculated-total itself) so it encloses both the
   label+value row and the desc line below it as one unit. */
.tooltip-calculated-total-wrap {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid rgba(255, 255, 255, 0.15);
}

.tooltip-calculated-total {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.tooltip-calculated-total-label {
  font-family: "ValveOracle", sans-serif;
  font-weight: 700;
  font-size: 16px;
  color: #ffffff;
}

/* Its own line below the label+value row (not sharing a flex group with
   .tooltip-calculated-total-label) so its font-size/line-height can't
   shift where .tooltip-calculated-total-value sits vertically. */
.tooltip-calculated-total-desc {
  margin-top: 4px;
  font-size: 14px;
  font-style: italic;
  color: #f0ece2;
  opacity: 0.6;
}

.tooltip-calculated-total-value {
  font-family: "ValveOracle", sans-serif;
  font-weight: 700;
  font-size: 16px;
  color: #00fff5;
  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}

/* Separate card below the main tooltip — same bottom background art as
   .tooltip-card-body, but its own standalone rounded card (all 4
   corners, not continuing .tooltip-card-top's shape) rather than a
   continuation of the main card, since it's conditionally shown/hidden
   independently of it. Flows naturally below .tooltip-card-body since
   it's a normal-flow sibling inside the same (already scaled)
   .tooltip-card — no separate positioning/scaling needed. */
.tooltip-card-notes {
  display: none;
  width: 100%;
  margin-top: 16px;
  background-size: 100% auto;
  background-repeat: no-repeat;
  background-position: top center;
  background-color: #1c1720;
  padding: 18px;
  box-sizing: border-box;
  border-radius: 8px;
  /* Clips .tooltip-card-notes-title's full-bleed corners (below) to this
     card's own rounded corners, same relationship as .build-section /
     .build-section-header. */
  overflow: hidden;
  color: #f0ece2;
  font-size: 13px;
  line-height: 1.5;
}

.tooltip-card-notes.visible {
  display: block;
}

/* Same rgba(0,0,0,0.4) full-bleed header treatment as
   .tooltip-section-header — extends past .tooltip-card-notes' own 18px
   padding on the top/left/right (the negative margins cancel exactly
   that padding) so the bar spans the card edge-to-edge instead of
   sitting inset like plain text. */
.tooltip-card-notes-title {
  font-family: "ValveOracle", sans-serif;
  font-weight: 700;
  font-size: 18px;
  color: #ffffff;
  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
  margin: -18px -18px 12px -18px;
  padding: 6px 18px;
  background: rgba(0, 0, 0, 0.4);
}

.tooltip-card-notes-list {
  margin: 0;
  padding-left: 18px;
  font-size: 16px;
}

.tooltip-card-notes-list li {
  margin-bottom: 6px;
}

.tooltip-card-notes-list li:last-child {
  margin-bottom: 0;
}

/* Inline clickable reference to another item, auto-detected in note text
   (see renderNoteHtml in app.js) — same click-to-jump behavior and hover
   affordance as .tooltip-upgrade-item-clickable, just inline within a
   sentence instead of a standalone block. */
.tooltip-note-link {
  cursor: pointer;
  color: #ffffff;
  font-weight: 700;
}

.tooltip-note-link:hover {
  color: #00fff5;
  text-decoration: underline;
}

.tooltip-note-link-icon {
  width: 18px;
  height: 18px;
  vertical-align: -4px;
  margin: 0 2px 0 1px;
  object-fit: contain;
}

/* ---------- tooltip: innate stats ---------- */

.tooltip-innate-stats {
  margin-bottom: 8px;
}

.tooltip-innate-stat {
  color: #d8d2e0;
  font-size: 18px;
  line-height: 1.6;
}

.tooltip-innate-stat strong {
  color: #ffffff;
}

/* ---------- tooltip: ability sections ---------- */

.tooltip-section {
  margin-top: 10px;
}

.tooltip-section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: calc(100% + 36px);
  margin-left: -18px;
  padding: 6px 18px;
  background: rgba(0, 0, 0, 0.4);
}

.tooltip-section-type {
  font-weight: 700;
  font-size: 18px;
  color: #ffffff;
}

.tooltip-section-cooldown {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-weight: 700;
  font-size: 18px;
  color: #ffffff;
}

.tooltip-section-cooldown-icon {
  width: 14px;
  height: 14px;
}

.tooltip-section-body {
  padding-top: 8px;
}

.tooltip-section-desc {
  margin-bottom: 4px;
  font-size: 16px;
  line-height: 1.5;
  color: #ece6f0;
}

.tooltip-section-desc + .tooltip-section-desc {
  margin-top: 8px;
}

.tooltip-section-desc strong {
  color: #ffffff;
}

.tooltip-inline-icon {
  width: 18px;
  height: 18px;
  vertical-align: -4px;
  margin: 0 1px;
}

.tooltip-section-note {
  margin-top: 6px;
  font-size: 16px;
  font-style: italic;
  color: #a99fb5;
}

/* ---------- tooltip: status effect chips ---------- */

.tooltip-status-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 10px;
}

.tooltip-status-chip {
  flex: 1 1 auto;
  min-width: 90px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 3px;
  padding: 8px 10px;
  border-radius: 4px;
  background: rgba(0, 0, 0, 0.25);
}

.tooltip-status-chip-icon {
  width: 18px;
  height: 18px;
}

.tooltip-status-chip-name {
  font-size: 12px;
  font-weight: 700;
  color: #ffffff;
}

.tooltip-status-chip-caption {
  font-size: 10px;
  color: #a99fb5;
}

/* ---------- tooltip: stat boxes ---------- */

.tooltip-stat-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 10px;
}

.tooltip-footer-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  margin-top: 8px;
}

.tooltip-stat-box {
  position: relative;
  flex: 1 1 auto;
  min-width: 90px;
  padding: 8px 10px;
  border-radius: 4px;
  background: rgba(0, 0, 0, 0.25);
}

.tooltip-stat-box-footer {
  flex: 1 1 auto;
  min-width: 120px;
  display: flex;
  align-items: baseline;
  gap: 6px;
}

.tooltip-stat-icon-value {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
}

.tooltip-stat-icon {
  width: 22px;
  height: 22px;
}

.tooltip-stat-value {
  font-size: 22px;
  font-weight: 700;
}

.tooltip-stat-color-white {
  color: #f0ece2;
}

.tooltip-stat-color-purple {
  color: #cfa0f5;
}

.tooltip-stat-color-orange {
  color: #ffb84d;
}

.tooltip-stat-color-green {
  color: #7ee08a;
}

.tooltip-stat-color-red {
  color: #ff6b6b;
}

.tooltip-stat-label {
  margin-top: 3px;
  font-size: 16px;
  color: #cfc7d6;
  text-align: center;
}

.tooltip-stat-box-footer .tooltip-stat-label {
  margin-top: 0;
}

.tooltip-stat-conditional {
  margin-top: 2px;
  font-size: 14px;
  font-style: italic;
  color: #a99fb5;
}

.tooltip-stat-scaling {
  position: absolute;
  top: -12px;
  left: -12px;
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 1px 10px;
  border-radius: 8px;
  background: #7d2f9c;
  color: #f4e4fb;
  font-size: 14px;
  font-weight: 700;
}

.tooltip-stat-scaling-icon {
  width: 24px;
  height: 16px;
}

/* ---------- tooltip: upgrades ---------- */

.tooltip-upgrades {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid rgba(255, 255, 255, 0.12);
}

/* An item can show both an "Upgrades From" and "Upgrades To" block (e.g.
   a mid-tier item). Only the first needs the divider line separating it
   from the content above — back-to-back blocks just need spacing. */
.tooltip-upgrades + .tooltip-upgrades {
  margin-top: 6px;
  padding-top: 0;
  border-top: none;
}

.tooltip-upgrades-label {
  font-size: 14px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: #a99fb5;
  white-space: nowrap;
}

.tooltip-upgrade-items {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
}

.tooltip-upgrade-item {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 14px;
  font-weight: 700;
  color: #ffffff;
}

.tooltip-upgrade-item-clickable {
  cursor: pointer;
}

.tooltip-upgrade-item-clickable:hover {
  color: #00fff5;
  text-decoration: underline;
}

.tooltip-upgrade-icon {
  width: 32px;
  height: 32px;
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
}

/* Below 1000px the site-wide scale-down (see e.g. .category-tabs,
   .tier-grid) shrinks everything past the point of being readable, and
   there's no separate mobile layout — rather than build/maintain one,
   this swaps the whole shop out for a plain "come back on a bigger
   screen" message. .shop-graphs is a sibling of .shop-builds-wrap (not a
   descendant), so it needs listing separately to actually disappear too. */
.too-small-message {
  display: none;
}

@media (max-width: 999px) {
  .shop-layout,
  .shop-builds-wrap,
  .shop-graphs {
    display: none !important;
  }

  .too-small-message {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 70vh;
    margin: 0 auto;
    padding: 20px;
    max-width: 260px;
    box-sizing: border-box;
    font-family: "ValveOracle", "Segoe UI", sans-serif;
    font-size: 20px;
    line-height: 1.5;
    color: #bcb49c;
    text-align: center;
    text-wrap: balance;
  }
}
