.detail-container {
  max-width: 1100px;
  margin: 0 auto;
  padding: 20px 16px 48px;
}

/* ── Sidebar (infobox) + main card, side by side ── */
.detail-layout {
  display: flex;
  align-items: flex-start;
  gap: 0;
}

/* ── Back button + Prev/Next row: sit side by side to save vertical
   space (mainly matters on mobile, where this is the first thing above
   the fold). Grouped together on the left (not space-between) - pinning
   them to opposite corners left a large empty gap on wide screens that
   read as broken even though it was technically "one row" as intended.
   Wraps to two lines gracefully if a narrow viewport can't fit both. ── */
.detail-nav-row {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  flex-wrap: wrap;
  gap: 12px 20px;
  margin-bottom: 20px;
}

/* ── Back button ── */
.back-button {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 16px;
  background-color: var(--accent);
  color: black;
  text-decoration: none;
  border-radius: 4px;
  font-weight: bold;
  transition: background-color 0.2s ease;
}
.back-button:hover { background-color: var(--accent-dim); }
.back-arrow {
  display: inline-block;
  width: 0; height: 0;
  border-top: 6px solid transparent;
  border-bottom: 6px solid transparent;
  border-right: 10px solid currentColor;
  flex-shrink: 0;
}

#content {
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 220ms ease, transform 220ms ease;
}

#content.detail-content-ready {
  opacity: 1;
  transform: translateY(0);
}

#content.detail-content-loading {
  opacity: 0;
  transform: translateY(6px);
}

/* ── Unified card ── */
.item-card {
  border: 1px solid #2a2a2a;
  border-radius: 8px;
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
  overflow: hidden;
  background: #161616;
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: auto auto;
  margin-bottom: 0;
  margin-left: -1px;
  flex: 1;
  min-width: 0;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
}

/* ── Infobox: standalone sidebar card, sibling of .item-card, flush against it ── */
.infobox {
  width: var(--infobox-w, 386px);
  flex-shrink: 0;
  border: 1px solid #2a2a2a;
  border-radius: 8px;
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
  overflow: hidden;
  background: #161616;
  font-size: 13px;
  display: flex;
  flex-direction: column;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
}

.infobox-image {
  background: #121212;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  border-bottom: 1px solid var(--divider);
  cursor: pointer;
  aspect-ratio: 1 / 1;
  overflow: hidden;
}
.infobox-image img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  display: block;
}

.infobox-rows { display: flex; flex-direction: column; flex: 1; }
.infobox-row { display: flex; align-items: stretch; border-bottom: 1px solid var(--border-subtle); flex: 1; }
.infobox-row:last-child { border-bottom: none; }
.infobox-budget {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 10px 12px;
  border-bottom: 2px solid var(--accent);
  background: #121212;
  text-align: center;
}
.infobox-budget-main {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-size: 15px;
  color: var(--text);
  font-weight: 600;
  width: 100%;
}
.infobox-budget-main img {
  width: 32px;
  height: 32px;
  object-fit: contain;
}
/* The zap on the POWER card's Produces/Requires rows trails the figure as a
   unit mark, so it is deliberately smaller than the 32px icons the Budget /
   Shelter / Weather cards lead with - at 32px it competes with the number
   instead of annotating it. */
.infobox-budget-main img.power-icon--zap {
  width: 20px;
  height: 20px;
}
/* The zap hugs the figure it annotates (2px) rather than floating at the row's
   full 8px gap, so it reads as part of the phrase - "Produces: 100⚡" - while
   the row gap still separates a trailing wiring qualifier. */
.power-phrase {
  display: inline-flex;
  align-items: center;
  gap: 2px;
}
/* The zap-to-wiring step deliberately matches the zap-to-figure step inside
   .power-phrase, so the bolt sits symmetrically between its neighbours and
   reads as one more character in the phrase rather than as a trailing badge.

   Both steps are wider than the number suggests: the icon is a 20px box whose
   artwork only fills the middle ~10.5px - rebox.js leaves a uniform margin on
   all sides - so the visible gap on each side is ~4.7px plus these values.
   Only applied when a wiring qualifier is actually present. */
.infobox-budget-main--wiring {
  gap: 2px;
}
.infobox-budget-label {
  font-size: 10px;
  font-weight: 700;
  color: var(--accent);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.infobox-budget-tier {
  font-size: 10px;
  color: var(--text-muted);
}

.infobox-build {
  display: flex;
  align-items: stretch;
  padding: 10px 4px;
  border-bottom: 2px solid var(--accent);
  background: #121212;
  text-align: center;
}
.infobox-build-item {
      flex: 1 1 0;
      min-width: 0;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      gap: 3px;
      padding: 0 4px;
      font-size: 12px;
      font-weight: 600;
      color: var(--text);
      text-transform: uppercase;
      white-space: nowrap;
      letter-spacing: 0.01em;
      text-align: center;
    }
.infobox-build-item img {
  width: 15px;
  height: 15px;
  object-fit: contain;
}
.infobox-build-item.not-buildable {
  color: var(--text-muted);
}

.infobox-label {
  font-weight: 700;
  color: var(--accent);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 8px 9px;
  width: 70px;
  flex-shrink: 0;
  border-right: 1px solid var(--border-subtle);
  background: var(--card-bg);
  display: flex;
  align-items: center;
  align-self: stretch;
  line-height: 1.2;
}

.infobox-value {
  padding: 8px 10px;
  color: var(--text);
  flex: 1;
  min-width: 0;
  word-break: break-all;
  overflow-wrap: anywhere;
  line-height: 1.4;
  display: flex;
  align-items: center;
  gap: 7px;
  flex-wrap: wrap;
}

/* ── Right column: content blocks ── */
.right-panel {
  grid-column: 1;
  grid-row: 1;
  display: flex;
  flex-direction: column;
}
.right-panel-title {
  padding: 16px 18px 14px;
  text-align: center;
  color: var(--accent);
  font-size: 30px;
  font-weight: 700;
  line-height: 1.1;
  margin: 0;
  border-bottom: 1px solid var(--border-subtle);
}
.right-panel-block {
  padding: 14px 18px;
}
.block-category {
  padding-bottom: 1.25rem;
}
.block-conditions {
  padding-top: 0;
}
.right-panel-block + .right-panel-block {
  border-top: 1px solid var(--border-subtle);
}
.right-panel-block h2 {
  margin: 0 0 10px 0;
  color: var(--accent);
  font-size: 15px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.entm-description {
  text-align: left;
  color: #b0b0b0;
  font-size: 0.9rem;
  line-height: 1.5;
  white-space: pre-line;
}

/* ── Storefront row (full width, row 2) ── */
.storefront-row {
  grid-column: 1;
  grid-row: 2;
  border-top: 1px solid var(--border-subtle);
  padding: 14px 18px;
}
.storefront-row h2 {
  margin: 0 0 12px 0;
  color: var(--accent);
  font-size: 15px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  text-align: left;
}

.infobox-value-col {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  min-width: 0;
  width: 100%;
}

.infobox-value-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  row-gap: 4px;
  gap: 7px;
  min-width: 0;
  width: 100%;
}
.infobox-value-row:has(.info-icon) {
  flex-wrap: nowrap;
}

/* Monospace technical value (model paths, editor/form IDs). min-width: 0
   + overflow-wrap keep it wrapping correctly inside a flex row instead
   of overflowing/getting clipped by an ancestor's overflow: hidden. */
.infobox-mono {
  font-family: monospace;
  font-size: 11px;
  word-break: normal;
  overflow-wrap: anywhere;
  min-width: 0;
  flex: 1 1 auto;
}

.infobox-value .budget-icon {
  width: 20px;
  height: 20px;
  object-fit: contain;
  flex-shrink: 0;
}

/* ── Shared "?" / "✕" icon glyphs ──
   Both are inline SVGs (makeQuestionIcon() here, makeCloseIcon() in
   camp-shared.js) rather than text characters - see the comments there.
   Stroke styling lives in style.css (.icon-svg) so both pages share it;
   sizing comes from the width/height passed at creation, and centering
   comes from the containing element's flex centering
   (display:inline-flex + align/justify:center), which now has an exact,
   known-size box to center instead of font-dependent glyph ink. */

/* Small circular "?" marker that a detail tooltip is attached to (Model
   full paths, Material Swaps details) - see attachDetailTooltip in
   item-view.js. */
.info-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  border: 1px solid var(--text-muted);
  color: var(--text-muted);
  cursor: pointer;
  user-select: none;
  flex-shrink: 0;
}
@media (hover: hover) {
  .info-icon:hover { background: var(--text-muted); color: #1a1a1a; }
}
.h2--with-icon {
  display: flex;
  align-items: center;
  gap: 6px;
}
.info-icon--h2 {
  margin: 0;
  color: inherit;
  border-color: inherit;
  width: 15px;
  height: 15px;
}
@media (hover: hover) {
  .info-icon--h2:hover { background: var(--accent); color: #1a1a1a; }
}
/* Keyboard focus: the "?" icons are tab stops now, so show where focus is
   (and mirror the hover invert so the popup and the icon read as connected). */
.info-icon:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  background: var(--text-muted);
  color: #1a1a1a;
}
.info-icon--h2:focus-visible {
  background: var(--accent);
  color: #1a1a1a;
}

/* ── Detail tooltip (Model paths / Material Swaps popovers) ──
   Positioned in JS directly above/below the "?" icon. Stays open on
   hover of the box itself (see attachDetailTooltip) so its monospace
   content can be selected/copied. */
.detail-tooltip {
  position: fixed;
  /* Above the plan-sources overlay (10000) and the map lightbox (10001):
     tooltips now attach to icons inside those overlays too (the
     non-tradable lock beside a plan name), and a popover that renders
     behind the surface it describes is invisible. */
  z-index: 10002;
  background: #121212;
  border: 1px solid #333;
  border-radius: 6px;
  padding: 8px 10px;
  max-width: 90vw;
  box-shadow: 0 8px 20px rgba(0,0,0,0.7);
  opacity: 0;
  pointer-events: none;
  transform: translateY(-4px);
  transition: opacity 0.15s ease, transform 0.15s ease;
  user-select: text;
}
.detail-tooltip.show {
  opacity: 1;
  pointer-events: auto;
  transform: translateY(0);
}
.detail-tooltip--sticky {
  cursor: default;
}
.detail-tooltip--assignable {
  max-width: 380px;
}
.detail-tooltip--assignable .detail-tooltip-line {
  word-break: normal;
  overflow-wrap: normal;
  text-align: center;
}
.detail-tooltip-line {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px;
  font-family: monospace;
  font-size: 11px;
  color: var(--text);
  word-break: break-all;
  overflow-wrap: anywhere;
  line-height: 1.5;
}
.detail-tooltip-line--word-wrap {
  word-break: normal;
  overflow-wrap: normal;
  text-align: center;
}
.detail-tooltip-line + .detail-tooltip-line {
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px solid var(--border-subtle);
}
.detail-tooltip-swap {
  flex-wrap: nowrap;
  white-space: nowrap;
}

/* ── Interactive path pill (click-to-copy inside model-path tooltips) ── */
.path-pill {
  font-family: monospace;
  font-size: 11px;
  color: #d1d5db;
  padding: 3px 8px;
  border-radius: 4px;
  background: rgba(255,255,255,0.03);
  transition: background-color 0.15s ease, color 0.15s ease;
  cursor: pointer;
  word-break: break-word;
  line-height: 1.5;
  position: relative;
}
@media (hover: hover) {
  .path-pill:hover {
    background: rgba(229,193,88,0.15);
    color: var(--accent);
  }
}
.path-pill.copied {
  background: #2e7d32;
  color: transparent;
}
.path-pill.copied::after {
  content: 'Copied!';
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-family: monospace;
  font-size: 11px;
  font-weight: 600;
}

/* ── Material swap tooltip: dynamic width ── */
.detail-tooltip--swap {
  width: max-content;
  min-width: 280px;
  max-width: min(90vw, 1100px);
}

#detail-mobile-tooltip.detail-tooltip--swap {
  width: 90vw;
  max-width: 90vw;
  text-align: left;
}

#detail-mobile-tooltip.detail-tooltip--swap .swap-header,
#detail-mobile-tooltip.detail-tooltip--swap .swap-mapping-row {
  grid-template-columns: 1fr auto 1fr;
}

/* ── Material swap pill-bar layout ── */
.swap-header {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  gap: 0.75rem;
  padding: 0 0.5rem 4px;
  margin-bottom: 4px;
  border-bottom: 1px solid #333;
}
.swap-header-label {
  font-size: 9px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
  text-align: center;
}
.swap-mapping-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: 0.75rem;
  padding: 2px 0.5rem;
  border-radius: 4px;
}
.swap-mapping-row + .swap-mapping-row {
  margin-top: 2px;
}
.swap-arrow {
  font-size: 9px;
  color: var(--text-muted);
}

/* ── Swap pill: path + optional remap sub-pill ── */
.swap-pill {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  padding: 3px 8px;
  border-radius: 4px;
  background: rgba(255,255,255,0.03);
  transition: background-color 0.15s ease;
  cursor: pointer;
  position: relative;
}
@media (hover: hover) {
  .swap-pill:hover { background: rgba(229,193,88,0.12); }
}
.swap-pill.copied {
  background: #2e7d32;
}
.swap-pill.copied > * { visibility: hidden; }
.swap-pill.copied::after {
  content: 'Copied!';
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-family: monospace;
  font-size: 11px;
  font-weight: 600;
}

.path-text {
  font-family: monospace;
  font-size: 11px;
  color: #d1d5db;
  word-break: break-word;
  line-height: 1.5;
}

.remap-line {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-family: monospace;
  font-size: 10px;
  color: #888;
  margin-top: 2px;
  white-space: nowrap;
  cursor: pointer;
  padding: 2px 6px;
  border-radius: 4px;
  background: rgba(255,255,255,0.04);
  border: 1px solid rgba(255,255,255,0.08);
  transition: color 0.15s ease, background-color 0.15s ease, border-color 0.15s ease;
  position: relative;
  align-self: flex-start;
}
@media (hover: hover) {
  .remap-line:hover {
    color: var(--accent);
    background: rgba(229,193,88,0.1);
    border-color: rgba(229,193,88,0.25);
  }
}
.remap-label {
  color: #666;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.remap-line .remap-val {
  color: #ccc;
}
.remap-line.copied {
  color: transparent;
  background: #2e7d32;
  border-color: #2e7d32;
}
.remap-line.copied .remap-label,
.remap-line.copied .remap-val { color: transparent; }
.remap-line.copied::after {
  content: 'Copied!';
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-family: monospace;
  font-size: 10px;
  font-weight: 600;
}

/* Mobile detail tooltip - bottom-of-screen popup */
#detail-mobile-tooltip {
  position: fixed;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  /* Shrink-wrap the text rather than always spanning the screen: a two-word
     popup ("Tradable Plan") spanning 90vw reads as a bar unrelated to what was
     tapped, and its 13px body looks lost inside the box. The cap stays at 95vw
     so genuinely long content - material-swap paths - still fills the row and
     wraps instead of overflowing the viewport. */
  width: max-content;
  max-width: 95vw;
  background: #121212;
  color: var(--text);
  padding: 10px 14px;
  border-radius: 6px;
  font-size: 13px;
  /* Same reason as .detail-tooltip: the tap target can live inside a modal. */
  z-index: 10002;
  text-align: center;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.25s ease;
  border: 1px solid #333;
  box-shadow: 0 8px 20px rgba(0,0,0,0.7);
}
#detail-mobile-tooltip.show {
  opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
  .detail-tooltip,
  #detail-mobile-tooltip {
    transition: none;
  }
  .detail-tooltip {
    transform: none; /* drop the 4px slide-in; #detail-mobile-tooltip's translateX is its centering, so it stays */
  }
}
#detail-mobile-tooltip .detail-tooltip-line {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px;
  font-family: monospace;
  font-size: 11px;
  color: var(--text);
  justify-content: center;
  word-break: break-all;
  overflow-wrap: anywhere;
}
#detail-mobile-tooltip .detail-tooltip-line + .detail-tooltip-line {
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px solid var(--border-subtle);
}
#detail-mobile-tooltip .detail-tooltip-line--word-wrap {
  word-break: normal;
  overflow-wrap: normal;
  text-align: center;
}
#detail-mobile-tooltip .arrow {
  display: inline-block;
  width: 0;
  height: 0;
  border-top: 5px solid transparent;
  border-bottom: 5px solid transparent;
  border-left: 8px solid var(--accent);
  margin: 0 2px;
  vertical-align: middle;
  flex-shrink: 0;
}
@media (max-width: 400px) {
  #detail-mobile-tooltip {
    max-width: 95vw;
    font-size: 12px;
  }
}

/* Same triangle used between Build Menu Category pills, reused here
   between a material swap's source and target path. */
.arrow {
  display: inline-block;
  width: 0;
  height: 0;
  border-top: 5px solid transparent;
  border-bottom: 5px solid transparent;
  border-left: 8px solid var(--accent);
  margin: 0 2px;
  vertical-align: middle;
  flex-shrink: 0;
}

/* ── Title ── */
.item-title {
  margin: 0 0 14px 0;
  color: var(--accent);
  font-size: 34px;
  line-height: 1.1;
  font-weight: 700;
}

.detail-inline-wrap {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.detail-pill {
  background: #222222;
  border: 2px solid #2a2a2a;
  border-radius: 20px;
  padding: 6px 14px;
  font-size: 14px;
  color: #e0e0e0;
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  line-height: 1.3;
  word-break: break-word;
  max-width: 420px;
}
.detail-pill-main {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 34px;
}
.detail-pill img {
  width: 34px;
  height: 34px;
  object-fit: contain;
  flex-shrink: 0;
}
.detail-pill-flavor {
  font-size: 11px;
  color: var(--text-muted);
  padding-left: 2px;
  line-height: 1.3;
}

/* Grey "+N" chip appended to a plan pill when its sources span more than 2
   icon types; the pill itself opens the plan-sources modal. */
.detail-pill-more {
  display: inline-block;
  color: var(--text-muted);
  background: #333;
  border: 1px solid #444;
  border-radius: 4px;
  padding: 1px 6px;
  font-size: 12px;
  font-weight: bold;
  flex-shrink: 0;
  white-space: nowrap;
}

/* Plan pills open the plan-sources modal on click, so give them a hint.
   Needs .detail-pill.detail-pill--plan (0,2,0) to survive the later
   `.detail-pill { border: none; }` rule, and the faint default border so the
   accent border-color swap is visible - same as the shared-item hover. */
.detail-pill.detail-pill--plan,
.detail-pill.detail-pill--entm {
  cursor: pointer;
  border: 1px solid #3a3a3a;
  transition: border-color 0.15s ease;
}
@media (hover: hover) {
  .detail-pill.detail-pill--plan:hover,
  .detail-pill.detail-pill--entm:hover {
    border-color: var(--accent);
  }
}

.detail-connector {
  color: var(--text-muted);
  font-weight: 500;
  font-size: 12px;
  white-space: nowrap;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.detail-connector-row {
  display: flex;
  align-items: center;
  margin: 8px 0;
  padding-left: 14px;
}

/* Used where a plain AND/OR connects two pills (Placement Conditions).
   Always a plain left-aligned stacked list - each pill on its own line with
   AND/OR shown as small left-aligned text between them. */
.detail-inline-wrap--conditions,
.detail-inline-wrap--conditions.is-wrapped {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-start;
  gap: 8px;
  max-width: 100%;
}

.detail-arrow {
  display: inline-block;
  width: 0; height: 0;
  border-top: 7px solid transparent;
  border-bottom: 7px solid transparent;
  border-left: 11px solid var(--accent);
  flex-shrink: 0;
}

.placement-note {
  font-size: 13px;
  color: var(--text-muted);
  margin-bottom: 8px;
}

/* ── Player Buffs block ── */
.block-buffs {
  padding-top: 14px;
}

/* A single "Label: content" line: Activation, a single-buff summary,
   a spell name above its detail list, or the "Buff: Grants one/all"
   lead-in before a bulleted list. */
.buff-line {
  color: #cccccc;
  font-size: 0.85rem;
  margin-bottom: 0.15rem;
}
.buff-line strong {
  color: #e0e0e0;
}
/* Activation always precedes another content line (never a list
   directly), so it gets more breathing room than the tighter default
   used when a line is immediately followed by bullets. */
.buff-line--spaced {
  margin-bottom: 0.5rem;
}

.buff-options-list {
  list-style: disc;
  padding-left: 1.1rem;
  margin: 0 0 0.5rem;
}
.buff-option {
  margin-bottom: 0.2rem;
  color: #ccc;
  font-size: 0.85rem;
}
.buff-option:has(.buff-details-list) {
  margin-bottom: 0.45rem;
}
.buff-details-list {
  list-style: none;
  padding-left: 1rem;
  margin: 0.2rem 0 0;
}
.buff-details-list li {
  font-size: 0.85rem;
  color: #b0b0b0;
}
.buff-details-list li::before {
  content: '○ ';
  color: #888;
}
/* Footnote section: one full line of whitespace separates it from the
   buff content above; each footnote stacks on its own line. */
.buff-footnotes {
  margin-top: 1rem;
}
.buff-footnote {
  font-size: 0.8rem;
  color: var(--text-muted);
  font-style: italic;
  margin: 0;
}
.buff-footnote + .buff-footnote {
  margin-top: 0.05rem;
}

/* Small superscript citation marker, e.g. the "1" linking the
   Activation line to its footnote, or "2" marking a Homebody Perk
   requirement inline instead of spelling it out each time. Dimmed
   amber so it reads as a subtle reference, not a stat value. */
.buff-citation {
  font-size: 0.7em;
  color: rgba(201, 168, 105, 0.65);
  margin-left: 0.1em;
}
.buff-footnote .buff-citation {
  margin-left: 0;
  margin-right: 0.1em;
}

/* Detail list for the single-spell/multi-detail case: no longer
   nested inside a bulleted <li>, so it needs its own top margin
   instead of inheriting the small gap .buff-option provides. */
.buff-details-list--flat {
  margin-top: 0.3rem;
  margin-bottom: 0.75rem;
}



.error-message {
  padding: 16px;
  background: #8b2c2c;
  color: #ff6b6b;
  border-radius: 6px;
  border: 2px solid #ff6b6b;
  margin: 16px 0;
}

/* ── Max Build Count ── */
.max-build-row {
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}
.max-build-stat {
  display: flex;
  align-items: center;
  gap: 10px;
  background: #1e1e1e;
  border: 1px solid #444;
  border-radius: 6px;
  padding: 8px 16px;
  font-size: 14px;
  color: var(--text);
}
.max-build-stat img {
  width: 30px;
  height: 30px;
  object-fit: contain;
}
.max-build-stat-label {
  font-size: 11px;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  line-height: 1;
}
.max-build-stat-value {
  font-size: 16px;
  font-weight: 700;
  color: var(--text);
  line-height: 1;
}

/* ── Shares max build count with ── */
.shared-label {
  font-size: 12px;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 700;
  margin-bottom: 8px;
}
.shared-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.shared-item {
  width: 80px;
  background: #1a1a1a;
  border: 1px solid #444;
  border-radius: 6px;
  padding: 6px;
  cursor: pointer;
  text-decoration: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  transition: border-color 0.15s;
  box-sizing: border-box;
}
@media (hover: hover) {
  .shared-item:hover { border-color: var(--accent); }
}
.shared-item img {
  width: 52px;
  height: 52px;
  object-fit: contain;
}
.shared-item-name {
  font-size: 9px;
  color: var(--text-muted);
  text-align: center;
  line-height: 1.3;
  word-break: break-word;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  text-overflow: ellipsis;
}
.shared-view-more {
  width: 80px;
  min-height: 0;
  align-self: stretch;
  background: #1a1a1a;
  border: 1px dashed #666;
  border-radius: 6px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  color: var(--text-muted);
  text-align: center;
  padding: 6px;
  transition: border-color 0.15s, color 0.15s;
  box-sizing: border-box;
}
@media (hover: hover) {
  .shared-view-more:hover { border-color: var(--accent); color: var(--accent); }
}

/* ── Search bar + pager row used inside "view more" style modals ── */
.modal-search-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 12px;
}
.modal-search-input {
  flex: 1;
  min-width: 0;
  box-sizing: border-box;
  padding: 6px 10px;
  background: #111;
  border: 1px solid #444;
  border-radius: 4px;
  color: var(--text);
  font-size: 13px;
  outline: none;
}
.modal-pager {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}
.modal-pager-btn {
  width: 24px;
  height: 24px;
  background: #1a1a1a;
  border: 1px solid #444;
  border-radius: 4px;
  color: var(--text);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  line-height: 1;
  transition: border-color 0.15s, color 0.15s;
}
.modal-pager-btn:disabled {
  opacity: 0.35;
  cursor: default;
}
@media (hover: hover) {
  .modal-pager-btn:not(:disabled):hover { border-color: var(--accent); color: var(--accent); }
}
.modal-pager-label {
  font-size: 11px;
  color: var(--text-muted);
  white-space: nowrap;
}
.shared-none {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 80px;
  height: 80px;
  background: #1a1a1a;
  border: 1px solid #333;
  border-radius: 6px;
  color: #555;
  font-size: 13px;
  box-sizing: border-box;
}
/* ── Prev / Next nav ── */
.item-nav {
  display: flex;
  gap: 10px;
}
.item-nav-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 16px;
  background-color: #5a4a00;
  color: #8a7a40;
  text-decoration: none;
  border-radius: 4px;
  font-weight: bold;
  pointer-events: none;
  user-select: none;
  transition: background-color 0.2s ease;
}
.item-nav-btn.active {
  background-color: var(--accent);
  color: black;
  pointer-events: auto;
  cursor: pointer;
}
@media (hover: hover) {
  .item-nav-btn.active:hover { background-color: var(--accent-dim); }
}
.nav-arrow-left {
  display: inline-block;
  width: 0; height: 0;
  border-top: 6px solid transparent;
  border-bottom: 6px solid transparent;
  border-right: 10px solid currentColor;
  flex-shrink: 0;
}
.nav-arrow-right {
  display: inline-block;
  width: 0; height: 0;
  border-top: 6px solid transparent;
  border-bottom: 6px solid transparent;
  border-left: 10px solid currentColor;
  flex-shrink: 0;
}

/* ── Image fade-in on load ── */
img.lazy-fade {
  opacity: 0;
  transition: opacity 0.25s ease;
}
img.lazy-fade.loaded {
  opacity: 1;
}

/* Never paint an <img>'s alt text. A browser only renders it as fallback -
   while a remote image is still loading (Firefox), or after it has failed
   (all browsers) - where it appears as a flat wedge of colored text in the
   image's slot, "neon blue" on this dark theme when the image sits in a
   link. The visual label beside the image already carries the name, and
   screen readers still read the alt attribute regardless of this rule. */
img {
  color: transparent;
  font-size: 0;
}

.storefront-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
}

.storefront-thumb {
  cursor: pointer;
  border: 2px solid #444;
  border-radius: 6px;
  overflow: hidden;
  background: #1a1a1a;
  position: relative;
  transition: border-color 0.15s, transform 0.15s, opacity 0.25s ease;
}
/* While a thumb is still resolving (manifest lookup or fallback chain),
   a spinner sits in the fixed 1:1 box instead of a broken-image flash. */
.storefront-thumb.is-loading::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 24px;
  height: 24px;
  margin: -12px 0 0 -12px;
  border: 2px solid rgba(255,255,255,0.15);
  border-top-color: rgba(255,255,255,0.7);
  border-radius: 50%;
  animation: thumb-spin 0.7s linear infinite;
  pointer-events: none;
}
@keyframes thumb-spin {
  to { transform: rotate(360deg); }
}
/* Failed thumbs fade (and shrink slightly) instead of vanishing, so the
   grid reflows under a disappearing tile rather than a sudden gap. */
.storefront-thumb.is-leaving {
  opacity: 0;
  transform: scale(0.92);
  pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
  .storefront-thumb.is-loading::after {
    animation: none;
    border-top-color: rgba(255,255,255,0.15);
  }
  .storefront-thumb.is-leaving {
    transition: none;
  }
}
@media (hover: hover) {
  .storefront-thumb:hover {
    border-color: var(--accent);
    transform: scale(1.03);
  }
}
.storefront-thumb img {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  object-fit: contain;
  padding: 10px;
  box-sizing: border-box;
}

/* ── Lightbox ── */
.lightbox-overlay {
     position: fixed;
     inset: 0;
     background: rgba(0,0,0,0.9);
     backdrop-filter: blur(8px);
     -webkit-backdrop-filter: blur(8px);
     /* Must outrank every other overlay. The lightbox is always the last
        thing opened - item and storefront galleries, and the plan map opened
        from the plan-sources modal - so it always needs to be on top. The
        plan-sources overlay sits at 10000 because it can itself open over an
        assignable modal, which made the map land underneath it. */
     z-index: 10001;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 2rem 1.5rem 1.5rem;
  cursor: default;
  animation: lbFadeIn 0.15s ease;
}
.lightbox-overlay.closing {
  animation: lbFadeOut 0.15s ease forwards;
  pointer-events: none;
}
@keyframes lbFadeIn { from { opacity:0; } to { opacity:1; } }

/* ── Top bar: title + counter + close ── */
.lightbox-header {
  position: relative;
  z-index: 10;
  width: 100%;
  max-width: 900px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-shrink: 0;
  cursor: default;
}
.lightbox-title {
  color: #fff;
  font-size: 14px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.lightbox-counter {
  color: rgba(255,255,255,0.5);
  font-size: 13px;
  white-space: nowrap;
  flex-shrink: 0;
}
.lightbox-close {
  width: 36px;
  height: 36px;
  margin-left: auto;
  background: rgba(255,255,255,0.08);
  color: #ccc;
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: 50%;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  flex-shrink: 0;
  transition: background-color 150ms ease, color 150ms ease;
}
.lightbox-close:hover {
  background: rgba(229, 193, 88, 0.85);
  color: #000;
}
.lightbox-header,
.lightbox-nav {
  transition: opacity 300ms ease;
}
.lightbox-header.auto-hidden,
.lightbox-nav.auto-hidden {
  opacity: 0;
  pointer-events: none;
}
.lightbox-spinner {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 36px;
  height: 36px;
  border: 3px solid rgba(255,255,255,0.15);
  border-top-color: rgba(255,255,255,0.7);
  border-radius: 50%;
  animation: lb-spin 0.7s linear infinite;
  pointer-events: none;
  z-index: 1;
}
@keyframes lb-spin { to { transform: translate(-50%, -50%) rotate(360deg); } }

/* ── Main stage: centered image ── */
.lightbox-stage {
  position: relative;
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  max-width: 900px;
  min-height: 0;
  /* Intentionally not clipped: a zoomed image should be able to visibly
     extend past this box's edges rather than being cut off at the stage
     boundary. The lightbox overlay itself covers the full viewport, so
     there's nothing meaningful for this to clip against anyway. */
  overflow: visible;
  cursor: pointer;
}
.lightbox-image-viewport {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  max-width: min(80vw, 800px);
  max-height: 75vh;
  min-width: 0;
  min-height: 0;
  cursor: zoom-in;
  touch-action: none;
}
.lightbox-image-viewport.can-zoom-in {
  cursor: zoom-in;
}
.lightbox-image-viewport.at-max-zoom {
  cursor: zoom-out;
}
.lightbox-image-viewport.zoomed-active {
  touch-action: none;
}
.lightbox-image-viewport.dragging {
  cursor: grabbing;
}
.lightbox-image-viewport img {
  min-width: 0;
  min-height: 0;
  max-width: min(90vw, 1024px); 
  max-height: 85vh; 
  object-fit: contain;
  display: block;
  border-radius: 4px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.5);
  transform-origin: center center;
  transition: transform 180ms ease;
  will-change: transform;
  user-select: none;
  -webkit-user-drag: none;
  image-rendering: auto;
}

/* ── Navigation arrows: float at screen edges ── */
.lightbox-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 48px;
  height: 48px;
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: 50%;
  color: #ccc;
  cursor: pointer;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  line-height: 0;
  transition: background-color 150ms ease, color 150ms ease, opacity 300ms ease;
}
.lightbox-nav svg {
  display: block;
}
.lightbox-nav:hover {
  background: rgba(229, 193, 88, 0.85);
  color: #000;
}
.lightbox-nav-prev { left: 2rem; }
.lightbox-nav-next { right: 2rem; }

@media (max-width: 600px) {
  .lightbox-nav { display: none; }
}

/* ── Assignable/Display Info ── */
.assignable-label {
  font-size: 12px;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 700;
  margin-bottom: 8px;
}
.assignable-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.assignable-item {
  width: 80px;
  background: #1a1a1a;
  border: 1px solid #444;
  border-radius: 6px;
  padding: 6px;
  cursor: pointer;
  text-decoration: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  transition: border-color 0.15s;
  box-sizing: border-box;
}
@media (hover: hover) {
  .assignable-item:hover { border-color: var(--accent); }
}
.assignable-item img {
  width: 52px;
  height: 52px;
  object-fit: contain;
}
.assignable-item-name {
  font-size: 9px;
  color: var(--text-muted);
  text-align: center;
  line-height: 1.3;
  word-break: break-word;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  text-overflow: ellipsis;
}
.assignable-view-more {
  width: 80px;
  min-height: 0;
  align-self: stretch;
  background: #1a1a1a;
  border: 1px dashed #666;
  border-radius: 6px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  color: var(--text-muted);
  text-align: center;
  padding: 8px;
  transition: border-color 0.15s, color 0.15s;
  box-sizing: border-box;
}
@media (hover: hover) {
  .assignable-view-more:hover { border-color: var(--accent); color: var(--accent); }
}

/* ── Assignable items modal ── */
.assignable-modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.75);
  z-index: 9998;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  animation: lbFadeIn 0.15s ease;
  overflow: hidden;
}
.assignable-modal-overlay.closing {
  animation: lbFadeOut 0.15s ease forwards;
  pointer-events: none;
}
/* Applied when this modal is opening as an immediate replacement for one
   that just closed instantly (view-more <-> item detail navigation). The
   backdrop itself skips its fade here and stays fully opaque throughout -
   if it replayed the normal entrance from opacity:0, there'd be a brief
   flash of the unmasked page underneath before it faded back in. The card
   (wrap) still gets a quick, subtle entrance below so the swap doesn't feel
   like an abrupt pop, just without touching the backdrop. */
.assignable-modal-overlay.no-open-anim {
  animation: none;
}
.assignable-modal-overlay.no-open-anim .assignable-modal-wrap {
  animation: assignableModalSwapIn 0.14s ease;
}
@keyframes assignableModalSwapIn {
  from { opacity: 0; transform: scale(0.97); }
  to { opacity: 1; transform: scale(1); }
}
@keyframes lbFadeOut { from { opacity: 1; } to { opacity: 0; } }
.assignable-modal-wrap {
  position: relative;
  max-width: 560px;
  width: 90vw;
  /* dvh keeps this honest on mobile browsers whose chrome (address bar,
     etc.) eats into vh - falls back to vh where dvh isn't supported. */
  max-height: 85vh;
  max-height: min(85vh, 85dvh);
  /* Short content (few tiles) shouldn't collapse the card to a sliver;
     min() keeps this safely below the max-height so the centered flex
     overlay can never clip the top on short screens. */
  min-height: min(360px, 55vh);
  display: flex;
  flex-direction: column;
  cursor: default;
  animation: assignableModalIn 0.18s ease;
  background: #1e1e1e;
  border-radius: 8px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.05);
  /* Header and body below both paint the same #1e1e1e background and
     have no radius of their own - this clips them to the wrap's rounded
     corners so the two pieces still read as one seamless card. */
  overflow: hidden;
}
.assignable-modal-overlay.closing .assignable-modal-wrap {
  animation: assignableModalOut 0.15s ease forwards;
}
@keyframes assignableModalIn {
  from { opacity: 0; transform: scale(0.94); }
  to { opacity: 1; transform: scale(1); }
}
@keyframes assignableModalOut {
  from { opacity: 1; transform: scale(1); }
  to { opacity: 0; transform: scale(0.94); }
}
.assignable-modal {
  background: #1e1e1e;
  border: none;
  padding: 0 20px 20px 20px;
  /* Scrolls independently of the header below - this is what lets the
     ✕ sit flush in the wrap's corner regardless of scrollbar width, and
     what lets the header stay put while only the body shrinks/scrolls
     on short viewports. */
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  box-sizing: border-box;
  scrollbar-width: thin;
  scrollbar-color: var(--accent) #1e1e1e;
}
/* Custom scrollbar, same pattern as the FAQ modal (.modal-body): native
   Edge scrollbars render white on these dark surfaces while Firefox picks
   up the OS dark styling, so this keeps every modal consistent. Same
   8px width and accent-on-dark contrast as the FAQ - no thinner, so no
   accessibility regression versus the established pattern. */
.assignable-modal::-webkit-scrollbar,
.ps-scroll-body::-webkit-scrollbar {
  width: 8px;
}
.assignable-modal::-webkit-scrollbar-track,
.ps-scroll-body::-webkit-scrollbar-track {
  background: #1e1e1e;
  border-radius: 4px;
}
.assignable-modal::-webkit-scrollbar-thumb,
.ps-scroll-body::-webkit-scrollbar-thumb {
  background-color: var(--accent);
  border-radius: 4px;
  border: 2px solid #1e1e1e;
}
.assignable-modal-header {
  display: flex;
  align-items: center;
  padding: 12px 16px 10px 20px;
  gap: 8px;
  flex-shrink: 0;
  background: #1e1e1e;
}
.assignable-modal-title {
  color: var(--accent);
  font-size: 15px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin: 0;
  line-height: 28px;
  white-space: nowrap;
}
.assignable-modal-close {
  width: 28px;
  height: 28px;
  background: #2b2b2b;
  color: var(--text-muted);
  border: 1px solid #444;
  border-radius: 50%;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  flex-shrink: 0;
  transition: background-color 150ms ease, color 150ms ease, border-color 150ms ease;
}
.assignable-modal-close:hover {
  background: var(--accent);
  color: black;
  border-color: var(--accent);
}
/* One keyboard-focus look for every close/back control on this page: the
   plan-sources and entitlement ✕ and the lightbox ✕ share the assignable
   modal's treatment (accent fill, black glyph), since focus lands on the ✕
   by default when those dialogs open. */
.assignable-modal-close:focus-visible,
.assignable-modal-back:focus-visible,
.plan-sources-close:focus-visible,
.lightbox-close:focus-visible {
  background: var(--accent);
  color: black;
  border-color: var(--accent);
  outline: none;
}
.assignable-modal-back {
  width: 28px;
  height: 28px;
  background: #2b2b2b;
  color: var(--text-muted);
  border: 1px solid #444;
  border-radius: 50%;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  flex-shrink: 0;
  transition: background-color 150ms ease, color 150ms ease, border-color 150ms ease;
}
.assignable-modal-back .back-arrow { transform: translateX(-1.5px); }

/* ── Lightbox header back arrow - match close button sizing ── */
.lightbox-header .assignable-modal-back {
  width: 36px;
  height: 36px;
}
.lightbox-header .assignable-modal-back .back-arrow {
  border-top: 8px solid transparent;
  border-bottom: 8px solid transparent;
  border-right: 12px solid currentColor;
  transform: translateX(-1px);
}
.assignable-modal-back:hover {
  background: var(--accent);
  color: black;
  border-color: var(--accent);
}

/* ── Modal tabs ── */
.assignable-modal-tabs {
  display: flex;
  gap: 2px;
  margin-bottom: 14px;
  border-bottom: 1px solid var(--border-subtle);
}
.assignable-modal-tab {
  padding: 8px 12px;
  background: none;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-weight: 600;
  position: relative;
  transition: color 0.15s;
}
@media (hover: hover) {
  .assignable-modal-tab:hover { color: var(--accent); }
}
.assignable-modal-tab.active {
  color: var(--accent);
}
.assignable-modal-tab::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 2px;
  background-color: var(--accent);
  border-radius: 1px;
  opacity: 0;
  transform: scaleX(0.6);
  transition: opacity 0.15s ease, transform 0.15s ease;
}
.assignable-modal-tab.active::after {
  opacity: 1;
  transform: scaleX(1);
}

.assignable-modal-tab-content {
  display: none;
}
.assignable-modal-tab-content.active {
  display: block;
  animation: assignableTabContentIn 0.16s ease-out;
}
@keyframes assignableTabContentIn {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .assignable-modal-tab-content.active { animation: none; }
}

/* ── Header inside the view-more modal (tabs + filters + search) ── */
.modal-sticky-header {
  position: sticky;
  top: 0;
  z-index: 10;
  background: #1e1e1e;
  padding-top: 0;
  padding-bottom: 8px;
  margin-bottom: 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

/* ── ItemType filter row ── */
.modal-filter-row {
  display: flex;
  gap: 0.5rem;
  overflow-x: auto;
  white-space: nowrap;
  margin-bottom: 10px;
  padding-bottom: 2px;
  scrollbar-width: none;
}
.modal-filter-row::-webkit-scrollbar { display: none; }
@media (min-width: 768px) {
  .assignable-modal-wrap { max-width: 800px; }
  .modal-filter-row {
    flex-wrap: wrap;
    overflow-x: visible;
    white-space: normal;
  }
}
.modal-filter-chip {
  display: inline-flex;
  align-items: center;
  background: #2b2b2b;
  color: #a0a0a0;
  border: none;
  border-radius: 16px;
  padding: 0.25rem 0.75rem;
  font-size: 0.8125rem;
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
  transition: background 0.15s, color 0.15s;
  flex-shrink: 0;
}
@media (hover: hover) {
  .modal-filter-chip:hover { background: #3a3a3a; color: #ccc; }
}
.modal-filter-chip.active {
  background: var(--accent);
  color: #000;
}
@media (hover: hover) {
  .modal-filter-chip.active:hover { background: #E5C158; }
}

/* Grid (not inline-flex) so rows share fixed column tracks: the whole
   block of tracks is centered via justify-content, but auto-placement
   fills each row left-to-right, so an incomplete final row lands in
   the same left-most columns as the rows above it instead of being
   centered on its own. */
.assignable-modal .assignable-grid,
.assignable-modal .shared-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, 96px);
  justify-content: center;
  gap: 8px;
  width: 100%;
  margin: 8px 0 0;
}

.assignable-none {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 80px;
  height: 80px;
  background: #1a1a1a;
  border: 1px solid #333;
  border-radius: 6px;
  color: #555;
  font-size: 13px;
  box-sizing: border-box;
}

/* ── Model Renders (non-thumbnail render images) ── */
.model-renders-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
  margin-top: 14px;
}

@media (max-width: 600px) {
  .detail-layout { flex-direction: column; align-items: stretch; }
  .infobox {
    width: 100%;
    box-sizing: border-box;
    border-radius: 8px 8px 0 0;
  }
  .item-card {
    margin-left: 0;
    margin-top: -1px;
    border-radius: 0 0 8px 8px;
  }
  .right-panel-title { font-size: 22px; }
  .detail-pill { max-width: 100%; }
  .storefront-thumb { aspect-ratio: 1; }
  .storefront-thumb img { width: 100%; height: 100%; }
  .info-icon {
    vertical-align: middle;
    margin-bottom: 0;
  }
}

/* ── Tablet: stack layout vertically, center infobox.
   Landscape tablets (900–1024px wide, e.g. older 1024×768 iPads)
   are deliberately excluded - they have enough horizontal room for
   the side-by-side layout, so they fall through to the default flex
   row (.detail-layout's base rule) instead of stacking and wasting
   width. Narrower landscape widths (601–899px, e.g. phones in
   landscape) still stack - a fixed 386px infobox would leave too
   little room for item-card at that width. ── */
@media (min-width: 601px) and (max-width: 1024px) and (orientation: portrait),
       (min-width: 601px) and (max-width: 899px) and (orientation: landscape) {
  .detail-layout {
    flex-direction: column;
    align-items: center;
  }
  .infobox {
    width: 100%;
    max-width: 400px;
    margin: 0 auto 24px;
    border-radius: 8px;
    border-top-right-radius: 8px;
    border-bottom-right-radius: 8px;
  }
  .item-card {
    margin-left: 0;
    margin-top: 0;
    border-radius: 8px;
    width: 100%;
    max-width: 700px;
  }
  .detail-nav-row {
    margin-left: 0;
  }
  .info-icon {
    vertical-align: middle;
    margin-bottom: 0;
  }
}

/* ── Desktop (+ landscape tablets): item-card is centered on the actual
   viewport - a third, empty grid column mirrors the infobox's width on
   the right, so the [infobox][item-card] pair sits symmetrically around
   center instead of the infobox's width dragging item-card off to one
   side. infobox still sits directly against item-card's left edge
   (adjacent grid columns, no gap) - it just isn't pinned to the literal
   screen edge anymore.

   detail-nav-row is NOT offset to match: it stays at the container's
   left edge, above the infobox (image/title) column, rather than
   shifted right to align with item-card. Centering item-card on the
   viewport is only about item-card's own content; the nav row reads
   as a page-level control (part of the browsing flow, not the item's
   info), so it belongs with the infobox above it, not repositioned
   whenever item-card's centering offset changes.

   This applies at 1025px+ (right above the tablet stacking breakpoint)
   AND at 900–1024px in landscape (tablets that skip stacking - see the
   tablet rule above). Without the second clause, that landscape-tablet
   band would fall through to the plain flex row and get the same
   asymmetric look this whole grid exists to avoid.

   --infobox-w shrinks fluidly toward the narrow end of this combined
   range (clamp floor 200px, low enough to leave item-card enough room
   at 900px) and grows back to its normal 386px by ~1930px viewport
   width. Both grid spacer columns read the same variable so they stay
   mirrored at every width. item-card's own floor (400px) is lowered to
   match - together they keep the layout from overflowing/squeezing at
   the narrow end. ── */
@media (min-width: 1025px),
       (min-width: 900px) and (max-width: 1024px) and (orientation: landscape) {
  .detail-container {
    --infobox-w: clamp(200px, 20vw, 386px);
    max-width: 1737px;
  }
  .detail-layout {
    display: grid;
    grid-template-columns: var(--infobox-w) minmax(400px, 1fr) var(--infobox-w);
    align-items: start;
  }
  .infobox {
    grid-column: 1;
    grid-row: 1;
  }
  .item-card {
    grid-column: 2;
    grid-row: 1;
  }
  /* grid-column 3 is intentionally left empty - a spacer equal to the
     infobox's width so item-card is centered on the viewport. */
}

/* Narrow-sidebar band (thin landscape windows, tall portrait monitors):
   the sidebar sits at its ~200px minimum while the four build badges
   (C.A.M.P. / Team C.A.M.P. / Shelter / Workshop) need ~260px+ in one
   row, so their nowrap labels bleed into each other. Fold the row into
   a 2x2 grid here; wider sidebars keep 4-across, narrower viewports
   already swap the hero to the top where there is room again. */
@media (min-width: 900px) and (max-width: 1400px) and (orientation: landscape),
       (min-width: 1025px) and (max-width: 1400px) and (orientation: portrait) {
  .infobox-build {
    flex-wrap: wrap;
    row-gap: 10px;
  }
  .infobox-build-item {
    flex: 1 1 40%;
  }
}


/* ============================================================
   Merged improvements (previously item-view-improvements.css)
   Appended in original load order so cascade/output is identical
   to the old two-file setup - these rules intentionally come
   after the base rules above to override/refine them.
   ============================================================ */

/* ============================================================
   item-view-improvements.css - v3, verified against item-view.js
   Append after your existing item-view.css. Reuses --accent-secondary,
   --divider, and --bg-chip from style-improvements.css - make sure
   that file (or those variables) is loaded first.
   ============================================================ */

/* ── Description: left-align instead of centered.
   Centered multi-line body text is harder to scan - the eye has to
   re-find the start of each line. Left-aligned reads noticeably
   faster for anything longer than one sentence. ── */
.entm-description {
  text-align: left;
}
.entm-description-label {
  text-align: left;
}

/* ── Structural dividers shift from flat gray to a faint steel-blue
   tint, freeing amber to mean "important" rather than "everything" ── */
.infobox-row { border-bottom-color: var(--divider); }
.infobox-label { border-right-color: var(--divider); }
.right-panel-block + .right-panel-block,
.right-panel-title,
.storefront-row {
  border-color: var(--divider);
}

/* Technical/debug rows (Form ID, Editor ID, Model) are reference
   data, not primary content - de-emphasize them with plain muted
   grey rather than any accent color, so they read as secondary
   info without introducing a second hue on the page. */
.infobox-label {
  color: var(--text-muted);
}
.infobox-value {
  color: var(--text-muted);
}

/* Alternating section tint removed per feedback - with pills now
   using their own --bg-chip background (see below), tinting the
   section behind them too made the panel look busier, not calmer.
   Sections now separate purely by the divider line + whitespace. */

/* ── "Shares Max Build Count With" thumbnails.
   CORRECTED: this section is NOT .assignable-grid/.assignable-item
   (that's the separate "Assignables Info" block above it) - it's
   built from .shared-grid/.shared-item/.shared-item-name in
   item-view.js (~line 2050+). The previous version of this file
   only resized the Assignables Info tiles and never touched the
   ones visible in the screenshot. Both are fixed here now.

   FIX: the earlier version of this rule turned these into a
   horizontal-scroll strip (flex-wrap: nowrap + overflow-x: auto),
   but never gave the row a bounded width. Without min-width: 0 /
   width: 100% on the flex container, the browser sizes it to fit
   all the nowrap items at once - so instead of scrolling, the row
   just expanded past its column and bled out of the card. Reverted
   to normal wrapping (matches your original layout) with just the
   larger tile size kept. ── */
.shared-grid,
.assignable-grid {
  flex-wrap: wrap;
}
.shared-item,
.shared-view-more,
.shared-none,
.assignable-item,
.assignable-view-more,
.assignable-none {
  width: 96px;
}
.shared-item img,
.assignable-item img {
  width: 64px;
  height: 64px;
}
.shared-item-name,
.assignable-item-name {
  font-size: 10px;
  -webkit-line-clamp: 2;
}

/* ── Reduce nested amber borders ──
   Feedback: the page reads as box-in-a-box-in-a-box - outer card
   border > infobox/right-panel > section blocks > pill-groups > pills
   > icons, each with its own fence. Outer containers now use a subtle
   dark-gray border (#2a2a2a) with elevated surface, so amber is freed
   for text hierarchy only. ── */

/* Pills become flat chips: no border, subtle background, off-white text
   for maximum readability. Hierarchy from typography, not a fence. */
.detail-pill {
  border: none;
  border-radius: 6px;
  background: #222222;
  color: #e0e0e0;
}
.detail-pill-group {
  border: none;
  border-radius: 6px;
  background: #222222;
}
/* A pill inside a group would otherwise be a chip inside a chip -
   the exact nesting problem being fixed. Let the group's background
   carry the color; the inner pill goes transparent. */
.detail-pill-group .detail-pill {
  background: transparent;
}

/* Infobox title: item name above the image render. */
.infobox-title {
  padding: 14px 12px 10px;
  text-align: center;
  color: var(--accent);
  font-size: 18px;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: 0.02em;
  border-bottom: 1px solid var(--divider);
}

/* Infobox image / budget hero: hairline divider instead of a full
   2px accent rule under every block. Match the image's dark background
   so the two flow together as one visual unit. */
.infobox-image {
  background: #121212;
  border-bottom: 1px solid var(--divider);
}
.infobox-budget,
.infobox-build {
  background: #161616;
  border-bottom: 1px solid var(--divider);
}

/* Shared / assignable item tiles: background instead of a resting
   border. Accent border only appears on hover, so amber still means
   "you can act on this" instead of being permanent scaffolding
   around every single tile in a grid of 18. */
.shared-item,
.assignable-item,
.shared-view-more,
.assignable-view-more {
  border-color: transparent;
  background: var(--bg-chip);
}
@media (hover: hover) {
  .shared-item:hover,
  .assignable-item:hover {
    border-color: var(--accent);
  }
}

/* Max Build Count stat boxes: same background-over-border swap. */
.max-build-stat {
  border-color: transparent;
  background: var(--bg-chip);
}

/* ── Even out spacing around the section dividers in the right panel.
   The space BEFORE each divider was already consistent (both the
   description section and .block-category use 1.25rem of bottom
   padding before their line). The space AFTER a divider, before the
   next heading, was not:
     - description's line → "Build Menu Category": 34px
       (1.25rem margin-bottom on .entm-description-section
       + .block-category's default 14px padding-top)
     - category's line → "Placement Conditions": 0px
       (.block-conditions explicitly zeroed its padding-top)
   Normalized both to the panel's standard 14px top padding, so every
   divider has the same amount of breathing room above the heading
   that follows it. ── */
.entm-description-section {
  margin-bottom: 0;
}
.block-conditions {
  padding-top: 14px;
}

/* ── Inline preview grid row cap ──
   The ASSIGNABLES INFO, SHARES MAX BUILD COUNT WITH, and BUILD MENU SLOT
   grids in the item detail page are previews - they should never expand
   to show all 8,500+ items. JS builds each grid with all items, then
   trimPreviewGrids() measures the real rendered width to compute column
   count and trims to exactly 2 rows (3 on mobile) with a "+X more" tile
   as the final slot. The CSS max-height below is a safety net that
   matches the worst-case tile height (~102px per row + 8px gap) so if
   JS miscalculates, content still doesn't spill. */
.assignable-grid,
.shared-grid {
  overflow: hidden;
}
/* 2 rows on desktop/tablet: 2 × 102px tiles + 1 × 8px gap = ~212px */
@media (min-width: 601px) {
  .right-panel .assignable-grid,
  .right-panel .shared-grid {
    max-height: 220px;
  }
}
/* 3 rows on mobile: 3 × 102px tiles + 2 × 8px gaps = ~322px */
@media (max-width: 600px) {
  .right-panel .assignable-grid,
  .right-panel .shared-grid {
    max-height: 330px;
  }
}

/* ── Collapsible "Technical" accordion in infobox ── */
.tech-accordion {
  border-top: 1px solid var(--divider);
}
.tech-accordion-toggle {
  box-sizing: border-box;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  padding: 10px 12px;
  cursor: pointer;
  user-select: none;
  font-size: 11px;
  font-weight: 700;
  color: var(--accent);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  background: transparent;
  transition: background 0.15s ease, color 0.15s ease;
}
/* Label sits in normal flow, centered by the flex container above -
   no reserved padding or absolute-position math needed. */
.tech-accordion-label {
  line-height: 1.2;
}
/* CSS border-triangle instead of a Unicode glyph (\25BE): glyph
   coverage depends on the active font-stack and can render as
   nothing on fonts that lack it. A border-triangle always renders,
   and matches the .back-arrow technique used elsewhere on this page.
   Absolutely positioned so it overlays without affecting the
   flex-centered label's available width. */
.tech-accordion-arrow {
  position: absolute;
  right: 12px;
  top: 50%;
  width: 0;
  height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid currentColor;
  transform: translateY(-50%);
  transition: transform 0.2s ease;
  pointer-events: none;
}
.tech-accordion.open .tech-accordion-arrow {
  transform: translateY(-50%) rotate(180deg);
}
@media (hover: hover) {
  .tech-accordion-toggle:hover {
    background: rgba(255,255,255,0.04);
    color: #f0d68a;
  }
}
.tech-accordion-body {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.25s ease;
}
.tech-accordion-body .infobox-row:last-child {
  border-bottom: none;
}

/* ── "Other items using this plan" in the plan sources modal ──
   A plain section: heading, then the first two rows' worth of tiles, then
   a nested accordion holding the overflow. The split is made in JS from the
   measured column count, so no height clamping is needed here. */
.ps-using-section {
  padding: 10px 16px;
  border-top: 1px solid #2a2a2a;
  border-bottom: 1px solid #2a2a2a;
}
/* In the entitlement modal the section is the only content, so it sits
   directly under the title bar and does not need its own top separator. */
.plan-sources-modal--entm .ps-using-section {
  border-top: none;
  padding-top: 14px;
}
.ps-using-head {
  color: var(--text-muted);
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: 8px;
}
.ps-using-section .shared-grid {
  /* Columns snap to a fixed track, so a short last row keeps the same
     left-hand columns as the rows above it instead of re-centring as a
     group; the track set as a whole is centred in the modal. Same technique
     the assignable modal uses. */
  display: grid;
  grid-template-columns: repeat(auto-fill, 96px);
  justify-content: center;
  overflow: hidden;
}
.ps-using-more {
  border-top: 1px solid #2a2a2a;
  margin-top: 10px;
}
.ps-using-more .tech-accordion-body {
  padding-top: 10px;
}
/* Expanded: breathing room between the overflow tiles above and the
   Show less toggle below, mirroring the padding-top above. Scoped to
   .open so the collapsed layout is untouched. */
.ps-using-more.open .tech-accordion-body {
  padding-bottom: 10px;
}

/* ── Assignable modal: mobile/tablet centered ── */
@media (max-width: 1024px) {
  .assignable-modal-overlay {
    align-items: center;
    padding: 16px;
  }
  .assignable-modal-wrap {
    width: 92vw;
    max-width: 560px;
    border-radius: 12px;
    max-height: 80vh;
    max-height: min(80vh, 80dvh);
  }
}

/* ── Extra shrink room on short viewports (landscape phones, small
   browser windows) - the body already scrolls internally via flex, but
   loosening the wrap's height ceiling further here means less of it
   needs to happen, and the fixed-size item image below shrinks with it
   instead of just pushing scroll. ── */
@media (max-height: 700px) {
  .assignable-modal-wrap {
    max-height: 92vh;
    max-height: min(92vh, 92dvh);
  }
  .assignable-modal-header {
    padding-top: 8px;
    padding-bottom: 6px;
  }
}

* {
  -webkit-tap-highlight-color: transparent;
}

/* ============================================================
   Resource Collection (resource collectors / extractors)
   Rendered by renderResourceCollection() in item-view.js from
   ResourceInfo.json. Compact styling mirrors the Player Buffs
   block (unbulleted mode list, tight rem typography); the drop
   table is borderless, constrained to a compact width, and
   flows down the page naturally (no internal scrollbars).
   ============================================================ */
.resource-collection {
  padding-bottom: 4px;
}
.resource-collection-meta {
  color: #ccc;
  font-size: 0.9rem;
  margin: 0 0 1.25rem;
}
.resource-collection-meta strong {
  color: #e0e0e0;
}

.resource-mode-list {
  list-style: none;
  padding: 0;
  margin: 0;
}
.resource-mode {
  margin-bottom: 24px;
  color: #ccc;
  font-size: 0.9rem;
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 8px;
  padding: 12px;
  width: 100%;
  box-sizing: border-box;
}
@media (min-width: 641px) {
  .resource-mode {
    padding: 16px;
  }
}
.resource-mode:last-child {
  margin-bottom: 0;
}
.resource-mode-title strong {
  color: #e0e0e0;
}
/* Flex row so the stacking-info "?" icon centers against the text the
   same way the Model-path / Assignables icons do elsewhere on this page
   (see .infobox-value-row and .h2--with-icon) - vertical-align on its own
   isn't reliable here since this line mixes a <strong> tag with a plain
   text node. */
.resource-mode-title {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
}
.resource-mode-title .info-icon {
  flex-shrink: 0;
}
/* Drops: label for Mystery Machines has no meta line below it (unlike
   Resource: which is followed by Interval/Fill Time before the table),
   so it needs its own bottom margin to keep the table from butting
   right up against it. */
.resource-mode-title--drops {
  margin-bottom: 0.5rem;
}
/* Single-line compact meta row ("Interval: X • Fill Time: ~Y").
   Kept as one flowing text block (not a fixed-width table) so it
   wraps cleanly on narrow phones instead of ever constraining the
   drop table's column widths below it. */
.resource-mode-meta {
  color: var(--text-muted);
  font-size: 0.78rem;
  margin: 4px 0 0.5rem;
  width: 100%;
  white-space: normal;
  overflow-wrap: break-word;
  box-sizing: border-box;
}
.resource-mode-meta-sep {
  margin: 0 2px;
  opacity: 0.7;
}

/* ============================================================
   Resource drop table - semantic <table> markup, but laid out
   per-row with CSS Grid: table/thead/tbody are set to
   display:contents (so they don't generate boxes of their own)
   and each <tr> becomes its own grid container with an explicit
   grid-template-columns track list. This gives exact, matching
   pixel-width columns for the header row and every body row
   (unlike table-layout:fixed, which only supports percentages/
   auto and can drift out of alignment), and lets the ITEM column
   truncate cleanly instead of forcing horizontal scroll or
   wrapping at narrow widths (down to 375px).

   Mobile-first: the base rules below target small phones; the
   min-width:641px query restores the roomier desktop sizing.
   ============================================================ */
.resource-drops {
  display: block;
  width: 100%;
  max-width: 42rem;
  font-size: 0.78rem;
  margin: 0.3rem 0 0.5rem;
  box-sizing: border-box;
}
.resource-drops thead,
.resource-drops tbody {
  display: contents;
}
.resource-drops tr {
  display: grid;
  grid-template-columns: 36px 1fr 38px 65px 50px;
  column-gap: 2px;
  width: 100%;
  box-sizing: border-box;
}
/* Cells stretch to a uniform row height (grid default) and use flex to
   center their own content - this keeps every cell's bottom edge (and
   therefore the header underline / row borders below) lined up evenly,
   even though the icon column has no text and would otherwise be a
   different height than its neighbors. */
.resource-drops th,
.resource-drops td {
  display: flex;
  align-items: center;
  padding: 3px 4px;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
  box-sizing: border-box;
}
.resource-drops th {
  padding: 6px 4px;
  color: var(--text-muted);
  font-size: 0.62rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
/* The underline lives on the header row itself, not individual <th>
   cells - a single continuous line unbroken by column-gap, rather
   than five separate per-cell borders with gaps between them. */
.resource-drops thead tr {
  border-bottom: 1px solid var(--divider);
}
.resource-drops th:not(.resource-col-name),
.resource-drops td:not(.resource-col-name):not(.resource-col-icon) {
  justify-content: center;
}

.resource-col-icon {
  justify-content: center;
  padding-left: 2px;
  padding-right: 0;
}
.resource-col-name {
  justify-content: flex-start;
}

/* Divider above the "nothing produced" row, set on the row itself for
   the same reason as the header underline (one continuous line). */
.resource-drops tr.resource-drop-nothing {
  border-top: 1px solid var(--divider);
}
.resource-drops tr.resource-drop-nothing td {
  padding-top: 8px;
}

@media (min-width: 641px) {
  .resource-drops {
    font-size: 0.85rem;
  }
  .resource-drops tr {
    grid-template-columns: 56px 1fr 80px 110px 80px;
    column-gap: 4px;
  }
  .resource-drops th,
  .resource-drops td {
    padding: 3px 8px;
  }
  .resource-drops th {
    padding: 6px 8px;
    font-size: 0.7rem;
    letter-spacing: 0.04em;
  }
  .resource-drops tr.resource-drop-nothing td {
    padding-top: 10px;
  }
  .resource-col-icon {
    padding-left: 8px;
  }
}

/* Extra rows revealed by "Show more" live in a second table inside a
   height-animated wrapper - mirrors .tech-accordion-body's slide. */
.resource-more-body {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.25s ease;
}
.resource-more-body .resource-drops {
  margin-top: 0;
}
/* When a "Show more" section exists the main table's own 0.5rem bottom
   margin would otherwise sit between its last row and the extra table's
   first row, doubling the spacing. The toggle's own margin handles the
   closed-state spacing instead. */
.resource-mode.has-more > .resource-drops {
  margin-bottom: 0;
}

.resource-drop-icon-link {
  display: inline-flex;
  width: 32px;
  height: 32px;
  margin: 0 auto;
}
.resource-drop-icon {
  width: 32px;
  height: 32px;
  object-fit: contain;
  border-radius: 4px;
  background: #161616;
  display: block;
  border: none;
  outline: none;
}
@media (min-width: 641px) {
  .resource-drop-icon-link,
  .resource-drop-icon {
    width: 36px;
    height: 36px;
  }
}
.resource-drop-name {
  display: block;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text);
  text-decoration: none;
}
@media (hover: hover) {
  a.resource-drop-icon-link:hover .resource-drop-icon { outline: 1px solid var(--accent); }
}

.resource-drops th:not(.resource-col-icon) {
  cursor: pointer;
  user-select: none;
}
@media (hover: hover) {
  .resource-drops th:not(.resource-col-icon):hover {
    color: var(--accent);
  }
}
.resource-drops th[tabindex]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
  color: var(--accent);
}

/* Row hover only ever highlights the item name - quantity/chance/
   weight stay their normal color no matter where in the row the
   pointer is, and the header row is excluded entirely (tbody-scoped)
   so it never picks up the gold tint. */
.resource-drops tbody tr:hover {
  background: rgba(230, 184, 59, 0.08);
  transition: background 120ms ease;
}
.resource-drops tbody tr:hover .resource-drop-icon {
  outline: none;
  border: none;
}
@media (hover: hover) {
  .resource-drops tbody tr:hover .resource-drop-name {
    color: var(--accent);
  }
}

.resource-drops th[data-sort-dir]::after {
  display: inline-block;
  content: '\25B2';
  width: 9px;
  margin-left: 5px;
  text-align: center;
  font-size: 0.6rem;
  vertical-align: middle;
  animation: resource-sort-in 160ms ease-out;
}
.resource-drops th[data-sort-dir="desc"]::after {
  content: '\25BC';
  animation: resource-sort-flip 160ms ease-out;
}
@keyframes resource-sort-in {
  from { opacity: 0; transform: translateY(-3px); }
  to { opacity: 1; transform: translateY(0); }
}
@keyframes resource-sort-flip {
  from { opacity: 0.3; transform: rotate(-180deg); }
  to { opacity: 1; transform: rotate(0deg); }
}
.resource-drops th.resource-sort-leave::after {
  display: inline-block;
  animation: resource-sort-out 160ms ease-in forwards;
}
@keyframes resource-sort-out {
  from { opacity: 1; transform: translateY(0); }
  to { opacity: 0; transform: translateY(-3px); }
}

.resource-more-toggle {
  cursor: pointer;
  text-align: left;
  color: var(--accent);
  font-size: 0.85rem;
  margin: 6px 0 0;
  padding: 8px;
  background: var(--bg-chip);
  border-radius: 4px;
  max-width: 42rem;
}
@media (hover: hover) {
  .resource-more-toggle:hover { background: var(--divider); }
}


/* -- Plan sources modal (prototype) -- */
.plan-sources-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.65);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 10000;
  padding: 16px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease;
}
.plan-sources-overlay.open {
  opacity: 1;
  pointer-events: auto;
}
.plan-sources-modal {
  background: var(--card-bg);
  border: 1px solid var(--border-subtle);
  border-radius: 8px;
  max-width: 600px;
  width: 100%;
  max-height: 82vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.6);
}
.plan-sources-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 16px;
  border-bottom: 1px solid #333;
  flex-shrink: 0;
}
/* The single scroll container for everything under the title bar. The
   min-height:0 is what lets a flex child scroll instead of expanding its
   parent past max-height. */
.ps-scroll-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--accent) #1e1e1e;
}
.plan-sources-header h3 {
  margin: 0;
  color: var(--accent);
  font-size: 16px;
  line-height: 1.3;
}
/* The heading holds the plan name and, for a non-tradable plan, a lock whose
   tooltip spells out the tag. The name sits in its own span so the title can
   be reset without tearing the lock out with it. */
.plan-sources-title {
  display: flex;
  align-items: center;
  gap: 7px;
  min-width: 0;
}
/* The plan name and the View Map button share one cluster at the left of
   the header bar, so the map control no longer claims a row of its own. */
.plan-sources-titlebox {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}
.plan-sources-header .ps-map-toggle {
  padding: 4px 10px;
  font-size: 12px;
  flex-shrink: 0;
  white-space: nowrap;
}
.plan-sources-title-text {
  min-width: 0;
}
.plan-sources-lock {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  /* No cursor override: the lock is a state marker, not a control, so it
     keeps the normal pointer. The tooltip still appears on hover/tap.
     Deliberately no hover shade either - the icon is a marker rather than a
     button, so dimming it on hover implied a control that isn't there. */
}
.plan-sources-close {
  color: var(--text-muted);
  font-size: 18px;
  cursor: pointer;
  padding: 2px 6px;
  border-radius: 4px;
  flex-shrink: 0;
}
/* Modal headings take programmatic-only focus on open (tabindex="-1"):
   screen readers announce the title without a glowing close button.
   No ring here - nothing to activate; the first Tab lands on a real
   control with its own indicator. */
.plan-sources-title:focus,
.assignable-modal-title:focus {
  outline: none;
}
@media (hover: hover) {
  .plan-sources-close:hover { background: #333; color: #fff; }
}
.plan-sources-meta {
  padding: 4px 16px;
  color: var(--text-muted);
  font-size: 12px;
}
.plan-sources-meta {
  border-bottom: 1px solid #2a2a2a;
}
.plan-sources-list {
  list-style: none;
  margin: 0;
  padding: 8px 0;
}
.plan-sources-list li {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 7px 16px;
  font-size: 14px;
}
.plan-sources-list li:nth-child(odd) {
  background: rgba(255, 255, 255, 0.02);
}
.plan-sources-list img {
  width: 28px;
  height: 28px;
  object-fit: contain;
  flex-shrink: 0;
}
.ps-body {
  flex: 1;
  min-width: 0;
}
.ps-title {
  line-height: 1.3;
}
.ps-detail {
  font-size: 11px;
  color: var(--text-muted);
  margin-top: 1px;
}
/* Plan-source annotations, styled like the Player Buffs footnotes: muted,
   italic marginless lines. One sits between "Other items using this plan"
   and the sources list (the shared drop-chance explanation); the others
   follow their own row ("Stops dropping/sold when learned"). */
.ps-notes {
      margin-top: 0.75rem;
      padding-left: 16px;
    }
.ps-annotation {
  font-size: 0.8rem;
  line-height: 1.3;
  font-style: italic;
  color: var(--text-muted);
  margin: 2px 0 0;
}
/* Like the Player Buffs footnote entries, the golden citation number sits
   flush against the text it introduces. */
.ps-annotation .buff-citation {
  margin-left: 0;
  margin-right: 0.1em;
}
.ps-chance {
  color: var(--text-muted);
  font-size: 12px;
  white-space: nowrap;
  flex-shrink: 0;
}
.ps-level {
  color: var(--text-muted);
  font-size: 11px;
  white-space: nowrap;
  flex-shrink: 0;
  padding: 1px 6px;
  border: 1px solid #383838;
  border-radius: 3px;
}
.ps-map-wrap {
  padding: 10px 16px;
  border-bottom: 1px solid #2a2a2a;
}
.ps-map-toggle {
  background: #333;
  color: var(--accent);
  border: 1px solid #444;
  border-radius: 4px;
  padding: 6px 16px;
  font-size: 13px;
  cursor: pointer;
}
@media (hover: hover) {
  .ps-map-toggle:hover { background: #444; }
}
.ps-map-filter-label {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--text-muted);
}
/* Filter chips are pills so they read as toggle controls rather than a
   read-only key. */
.ps-map-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  cursor: pointer;
  user-select: none;
  line-height: 1;
  padding: 3px 10px 3px 7px;
  border: 1px solid #454545;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.06);
}
@media (hover: hover) {
  .ps-map-chip:hover {
    background: rgba(255, 255, 255, 0.12);
    border-color: #666;
  }
}
.ps-map-chip--off {
  opacity: 0.35;
  border-color: #333;
}
.ps-map-total {
  margin-left: auto;
}
.ps-map-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  border: 1px solid #111;
  display: inline-block;
}
/* The key's stand-in for a door pin. Outlined rather than filled, because the
   legend otherwise keys every entry by a solid colour and a door has none of
   its own - it takes the colour of whatever kind it stands for. */
.ps-map-door {
  width: 7px;
  height: 10px;
  border: 1px solid #111;
  border-radius: 3px 3px 0 0;
  background: #cfd6e0;
  display: inline-block;
  flex: none;
}
.ps-map-note,
.ps-map-interior {
  margin-top: 8px;
  font-size: 11px;
  color: var(--text-muted);
  line-height: 1.5;
}
.ps-map-interior { color: #c9a227; }

/* ── Plan source map, zoomable lightbox version ──
   The map reuses openLightbox, so it gets the same click-to-zoom, drag-to-pan
   and pinch behaviour as the item images. The pins cannot live on the <img>,
   because attachZoomPan puts the transform there, so they sit in a layer
   inside a wrapper that takes the transform instead. */
.lightbox-map-frame {
  position: relative;
  display: inline-block;
  line-height: 0;
  transition: transform 180ms ease;
  will-change: transform;
}
/* The item-render caps (900px stage / 800px viewport) are far too small for a
   4096px map, so a map lightbox lifts them. */
.lightbox-overlay--map .lightbox-stage {
  max-width: min(96vw, 1560px);
}
.lightbox-overlay--map .lightbox-image-viewport {
  max-width: min(94vw, 1500px);
  max-height: 88vh;
}
.lightbox-map-frame img {
  max-width: min(94vw, 1500px);
  max-height: 88vh;
  border-radius: 4px;
}
.lightbox-map-pin-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
/* Hidden by a legend filter. A class rather than inline display so several
   filters can overlap without undoing each other. */
.lightbox-map-pin.pin-hidden {
  display: none;
}

/* Teardrop pin, drawn as one smooth silhouette. The box is exactly
   head + tip tall, so its bottom edge - the apex - sits precisely on the map
   coordinate.

   The pins live inside the zoomed frame, so without correction they would
   grow with the map and bury whatever they point at. The correction is a
   single transform: the pin is laid out at its true on-screen size (every
   length below is plain px) and scaled by 1 / --map-zoom (set by
   attachZoomPan on the frame) around its apex, which stays on the point.
   Laying it out at full size rather than shrinking each length keeps the
   curves and hairlines crisp at every zoom.

   Layers, back to front (all pseudo-elements, so the DOM is unchanged):
     pin::after        soft ground shadow at the apex
     head::before      pale halo, so the pin reads on dark and busy terrain
     tip::before       dark outline
     head::after       colour fill, with a light-to-dark gradient for depth
     head::after       (also draws the centre dot as a layer of the fill)
   The head and tip elements themselves are empty placeholders; the shape
   lives in --pin-shape, a 12x20 teardrop stretched to the box, so changing
   the head/tip ratio stretches the curve with it. */
.lightbox-map-pin {
  --pin-head: 12px;
  --pin-tip: 8px;
  /* A 12px marker is too small a click target, so the hit area is padded
     out invisibly. It is on-screen px like everything else here, so it
     stays the same size at every zoom. */
  --pin-hit: 28px;
  --pin-fill: var(--pin-color, #d0d0d0);
  --pin-ink: #111;
  --pin-halo: rgba(255, 255, 255, 0.6);
  /* How much bigger than the fill the halo and the outline are, in total
     (half each side). Variables so touch screens can slim them. */
  --pin-halo-grow: 4px;
  --pin-ink-grow: 2px;
  /* The halo and outline are the pin scaled up about its centre, by a
     different factor on each axis so that both grow by the same number of
     pixels (the pin is ~1.7x taller than wide, so one shared factor gives a
     border that is thin at the sides and fat at the top and bottom - or, scaled
     about the tip, shoved upward off the head). */
  /* The dot in the middle of the head. It is painted as a layer of the fill
     (below) rather than as a separate element: a separate 4px element is
     pixel-snapped on its own, so wherever the pin landed on a fractional
     position (every other zoom level) the dot drifted up to half a pixel off
     the head's centre. Drawn in the fill, it is rendered with it and cannot
     disagree. Position is a point in the stretched 12x20 shape: the head
     circle's centre is 6/20 of the way down. */
  --pin-dot: var(--pin-ink);
  --pin-dot-x: 50%;
  --pin-dot-y: 30%;
  --pin-dot-r: calc(var(--pin-head) * 0.18);
  --pin-shape: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 20' preserveAspectRatio='none'%3E%3Cpath d='M6 20C6 20 0 12.5 0 6a6 6 0 0 1 12 0c0 6.5-6 14-6 14z'/%3E%3C/svg%3E");
  position: absolute;
  width: var(--pin-head);
  height: calc(var(--pin-head) + var(--pin-tip));
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  pointer-events: auto;
  /* Scale first (around the apex), then pull the box up so the apex lands
     on the point. */
  transform: translate(-50%, -100%) scale(calc(1 / var(--map-zoom, 1)));
  transform-origin: 50% 100%;
  overflow: visible;
  /* The shape is the affordance; the browser's rectangular focus box would
     frame the empty bounding box. :focus-visible below lights the halo. */
  outline: none;
  /* No grey flash over the pin's box when it is tapped. */
  -webkit-tap-highlight-color: transparent;
  /* A stacking context, so the halo, outline and shadow below can use negative
     z-index to sit behind the pin's own fill without dropping behind the map
     image itself. Without it they are painted under the <img> and vanish. */
  z-index: 0;
  /* The pins are masked silhouettes; promoting each to its own layer means a
     zoom/pan on the frame only composites them rather than re-rasterising
     every mask every frame, which is what makes a crowded map stutter. */
  will-change: transform;
}
/* Whichever pin is hovered, focused, or tapped open rises above its
   neighbours, so its label is never buried under a pin that happens to come
   later in the DOM. */
.lightbox-map-pin.pin-hover,
.lightbox-map-pin:focus-visible,
.lightbox-map-pin.pin-open {
  z-index: 5;
}
/* A source whose point PlanInfo flags Interior is a door, so it is drawn as
   one: a doorway standing on the coordinate rather than a point dropped on
   it. Only --pin-shape changes, so the halo, outline and fill - which all
   mask with it - keep working untouched, and they still scale about the
   bottom edge, which is where the door meets the ground and therefore the
   spot.

   Same 12x20 viewBox as the teardrop, for the same reason: the box is
   head + tip tall, and matching the ratio keeps the arch from going
   lopsided if those two lengths are ever changed. The path is a doorway with
   an arched top, standing on y=20: up the left jamb from the floor to y=7,
   a 5-radius semicircle to the right jamb, then down to the floor again. */
.lightbox-map-pin[data-interior] {
  /* The handle sits off to the latch side at knuckle height rather than dead
     centre - 8.5/12 across and 13/20 down in the shape below. */
  --pin-dot-x: 70.8%;
  --pin-dot-y: 65%;
  --pin-dot-r: calc(var(--pin-head) * 0.1);
  --pin-shape: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 20' preserveAspectRatio='none'%3E%3Cpath d='M1 20V7a5 5 0 0 1 10 0v13z'/%3E%3C/svg%3E");
}
.lightbox-map-pin::before {
  content: '';
  position: absolute;
  left: 50%;
  top: calc(var(--pin-head) + var(--pin-tip) * 0.5);
  width: var(--pin-hit);
  height: var(--pin-hit);
  transform: translate(-50%, -50%);
}
/* Ground shadow: a small flat ellipse under the apex that makes the pin look
   planted on the map rather than printed on it. */
.lightbox-map-pin::after {
  content: '';
  position: absolute;
  left: 50%;
  top: calc(100% - var(--pin-head) * 0.25);
  width: calc(var(--pin-head) * 1.34);
  height: calc(var(--pin-head) * 0.5);
  transform: translateX(-50%);
  background: radial-gradient(ellipse at center, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0) 70%);
  z-index: -3;
  pointer-events: none;
}
.lightbox-map-pin-head,
.lightbox-map-pin-tip {
  position: absolute;
  left: 0;
  top: 0;
  width: 0;
  height: 0;
  padding: 0;
  border: 0;
  background: none;
  transform: none;
  filter: none;
}
/* Halo */
.lightbox-map-pin-head::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  width: var(--pin-head);
  height: calc(var(--pin-head) + var(--pin-tip));
  background: var(--pin-halo);
  -webkit-mask: var(--pin-shape) center / 100% 100% no-repeat;
  mask: var(--pin-shape) center / 100% 100% no-repeat;
  /* 2px of halo beyond the head, as a uniform scale rather than a larger box.
     A box grown by 4px wide but only 2px tall would stretch the 12x20 mask by
     a different factor on each axis - the head would render as an ellipse, its
     centre lifted above the fill's, which is exactly the "circle is not
     centred" look. Scaling about the apex keeps every layer the same
     silhouette and concentric with the fill. */
  transform: scale(
    calc((var(--pin-head) + var(--pin-halo-grow)) / var(--pin-head)),
    calc((var(--pin-head) + var(--pin-tip) + var(--pin-halo-grow)) / (var(--pin-head) + var(--pin-tip)))
  );
  transform-origin: 50% 50%;
  z-index: -2;
  transition: background-color 120ms ease;
}
/* Dark outline */
.lightbox-map-pin-tip::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  width: var(--pin-head);
  height: calc(var(--pin-head) + var(--pin-tip));
  background: var(--pin-ink);
  -webkit-mask: var(--pin-shape) center / 100% 100% no-repeat;
  mask: var(--pin-shape) center / 100% 100% no-repeat;
  /* 1px, same reasoning as the halo above. */
  transform: scale(
    calc((var(--pin-head) + var(--pin-ink-grow)) / var(--pin-head)),
    calc((var(--pin-head) + var(--pin-tip) + var(--pin-ink-grow)) / (var(--pin-head) + var(--pin-tip)))
  );
  transform-origin: 50% 50%;
  z-index: -1;
}
/* Colour fill. The gradient lifts the top and darkens the point so the pin
   reads as a little solid object; it follows whatever --pin-color JS sets. */
.lightbox-map-pin-head::after {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  width: var(--pin-head);
  height: calc(var(--pin-head) + var(--pin-tip));
  background: var(--pin-fill);
  background:
    radial-gradient(
      circle var(--pin-dot-r) at var(--pin-dot-x) var(--pin-dot-y),
      var(--pin-dot) calc(var(--pin-dot-r) - 0.75px),
      transparent var(--pin-dot-r)
    ),
    linear-gradient(
    170deg,
    color-mix(in srgb, var(--pin-fill) 72%, #fff) 0%,
    var(--pin-fill) 48%,
    color-mix(in srgb, var(--pin-fill) 78%, #000) 100%
    );
  -webkit-mask: var(--pin-shape) center / 100% 100% no-repeat;
  mask: var(--pin-shape) center / 100% 100% no-repeat;
  z-index: 1;
  transition: filter 120ms ease;
}
.lightbox-map-pin.pin-hover .lightbox-map-pin-head::before,
.lightbox-map-pin:focus-visible .lightbox-map-pin-head::before,
.lightbox-map-pin.pin-open .lightbox-map-pin-head::before {
  background: #fff;
}
.lightbox-map-pin.pin-hover .lightbox-map-pin-head::after,
.lightbox-map-pin:focus-visible .lightbox-map-pin-head::after,
.lightbox-map-pin.pin-open .lightbox-map-pin-head::after {
  filter: brightness(1.12) saturate(1.1);
}
/* The label is inside the pin, so it is counter-scaled along with it and
   needs no zoom maths of its own. */
.lightbox-map-pin-label {
  position: absolute;
  bottom: calc(100% + 7px);
  left: 50%;
  transform: translate(-50%, 2px);
  /* Set from JS when the label opens, to pull it back on screen when the
     pin is near the edge of the display. The caret below moves the other
     way so it still points at the pin. */
  translate: var(--label-dx, 0px) 0;
  padding: 4px 8px;
  background: rgba(17, 17, 17, 0.96);
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 4px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.55);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.01em;
  line-height: 1.3;
  color: #fff;
  white-space: nowrap;
  opacity: 0;
  visibility: hidden;
  transition: opacity 120ms ease, transform 120ms ease, visibility 0s linear 120ms;
  pointer-events: none;
  z-index: 3;
}
/* Small caret pointing at the pin. */
.lightbox-map-pin-label::after {
  content: '';
  position: absolute;
  left: 50%;
  top: calc(100% - 3px);
  width: 6px;
  height: 6px;
  transform: translateX(-50%) rotate(45deg);
  translate: calc(-1 * var(--label-dx, 0px)) 0;
  background: rgba(17, 17, 17, 0.96);
  border-right: 1px solid rgba(255, 255, 255, 0.16);
  border-bottom: 1px solid rgba(255, 255, 255, 0.16);
}
.lightbox-map-pin.pin-hover .lightbox-map-pin-label,
.lightbox-map-pin:focus-visible .lightbox-map-pin-label,
.lightbox-map-pin.pin-open .lightbox-map-pin-label {
  opacity: 1;
  visibility: visible;
  transform: translate(-50%, 0);
  transition-delay: 0s;
}
/* Pins near the top edge would otherwise have their label clipped off, so
   the label drops below the point and the caret flips to its top edge. */
.lightbox-map-pin[data-edge='top'] .lightbox-map-pin-label {
  bottom: auto;
  top: calc(100% + 10px);
  transform: translate(-50%, -2px);
}
.lightbox-map-pin[data-edge='top'].pin-hover .lightbox-map-pin-label,
.lightbox-map-pin[data-edge='top']:focus-visible .lightbox-map-pin-label,
.lightbox-map-pin[data-edge='top'].pin-open .lightbox-map-pin-label {
  transform: translate(-50%, 0);
}
.lightbox-map-pin[data-edge='top'] .lightbox-map-pin-label::after {
  top: auto;
  bottom: calc(100% - 3px);
  transform: translateX(-50%) rotate(225deg);
}
/* Side edges: the label stays flush with the map edge, and the caret slides
   over so it still points at the pin's centre line. */
.lightbox-map-pin[data-edge='left'] .lightbox-map-pin-label {
  left: 0;
  transform: translateY(2px);
}
.lightbox-map-pin[data-edge='left'] .lightbox-map-pin-label::after {
  left: calc(var(--pin-head) / 2);
}
.lightbox-map-pin[data-edge='right'] .lightbox-map-pin-label {
  left: auto;
  right: 0;
  transform: translateY(2px);
}
.lightbox-map-pin[data-edge='right'] .lightbox-map-pin-label::after {
  left: auto;
  right: calc(var(--pin-head) / 2);
  transform: translateX(50%) rotate(45deg);
}
.lightbox-map-pin[data-edge='left'].pin-hover .lightbox-map-pin-label,
.lightbox-map-pin[data-edge='left']:focus-visible .lightbox-map-pin-label,
.lightbox-map-pin[data-edge='left'].pin-open .lightbox-map-pin-label,
.lightbox-map-pin[data-edge='right'].pin-hover .lightbox-map-pin-label,
.lightbox-map-pin[data-edge='right']:focus-visible .lightbox-map-pin-label,
.lightbox-map-pin[data-edge='right'].pin-open .lightbox-map-pin-label {
  transform: none;
}
/* On coarse pointers (touch) the 1px dark outline around a small pin reads
   heavier than on a desktop hover, so it is softened while the centre dot
   keeps a solid ink to anchor on. */
@media (pointer: coarse) {
  .lightbox-map-pin {
    /* The map is shown ~400px wide on a phone, so the same 12px pin is a much
       bigger share of it than on a desktop. Slightly smaller, with a thinner
       and fainter halo (the white border) - every length in the pin derives
       from these variables, so this is the only place to tune it. */
    --pin-head: 10px;
    --pin-tip: 7px;
    --pin-halo-grow: 3px;
    --pin-ink-grow: 1.5px;
    --pin-halo: rgba(255, 255, 255, 0.45);
    --pin-ink: rgba(0, 0, 0, 0.42);
    /* The centre dot keeps a solid ink to anchor on. */
    --pin-dot: #111;
  }
  /* The ground shadow is an extra painted layer per pin that is barely
     visible at this size. */
  .lightbox-map-pin::after {
    display: none;
  }
}
/* Long labels wrap instead of running off a narrow screen. */
@media (max-width: 700px), (pointer: coarse) {
  .lightbox-map-pin-label {
    width: max-content;
    max-width: min(220px, calc(100vw - 24px));
    white-space: normal;
    text-align: center;
    overflow-wrap: anywhere;
  }
}
@media (prefers-reduced-motion: reduce) {
  .lightbox-map-pin-label,
  .lightbox-map-pin-head::before,
  .lightbox-map-pin-head::after {
    transition: none;
  }
}
/* Legend sits in the viewport, not the transformed frame, so it keeps its
   size at every zoom level. */
.lightbox-map-legend {
  position: absolute;
  left: 0;
  bottom: 0;
  z-index: 4;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 12px;
  max-width: min(90vw, 720px);
  padding: 8px 12px;
  background: rgba(17, 17, 17, 0.82);
  border: 1px solid #333;
  border-radius: 4px;
  font-size: 12px;
  color: var(--text-muted);
  /* Expanded, a long legend must not swallow the map. */
  max-height: 45vh;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.ps-map-filter-toggle {
  background: none;
  border: 0;
  padding: 4px 2px;
  margin: 0;
  font: inherit;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--text-muted);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.lightbox-map-legend--collapsed .ps-map-chip {
  display: none;
}
.ps-map-chip {
  -webkit-tap-highlight-color: transparent;
}
@media (pointer: coarse) {
  /* Fingertip-sized targets for the filter controls. */
  .lightbox-map-legend .ps-map-chip {
    padding: 7px 12px 7px 9px;
  }
  .ps-map-filter-toggle {
    padding: 8px 6px 8px 2px;
  }
}

/* ── Reduced motion (item page) ──────────────────────────────────────────
   Existing blocks above cover the tooltips, tab content and map pins. This
   covers the rest: modal/lightbox entrances and exits, accordions, the
   gallery slide and zoom, sort arrows, and the page fade-in.

   Durations are shrunk to ~0 rather than set to `none` on purpose:
   item-view.js waits on transitionend for the accordions (to release
   max-height) and on animationend for the resource-table sort reset, and
   neither event fires when there is no transition/animation at all. At
   0.01ms the end state is immediate and the events still arrive.
   !important is needed to beat inline transitions set from script
   (gallery stage) and the `.closing` animation shorthands.

   Deliberately left alone: .lightbox-spinner's loading rotation, which is
   feedback that something is in progress rather than decorative motion. */
@media (prefers-reduced-motion: reduce) {
  #content {
    transform: none; /* fade in place instead of rising 6px */
  }
  #content,
  .lightbox-overlay,
  .lightbox-stage,
  .lightbox-image-viewport img,
  .lightbox-map-frame,
  .assignable-modal-overlay,
  .assignable-modal-wrap,
  .assignable-modal-tab::after,
  .tech-accordion-arrow,
  .tech-accordion-body,
  .resource-more-body,
  .resource-drops th::after,
  .resource-drops th.resource-sort-leave::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}
