/* ============================================================
   OverSol design studios — shared layout for the ski + paddle apps.
   One stylesheet, one set of interaction patterns; each module only
   swaps --accent/--accent-2 (see body.studio-ski / body.studio-paddle
   below) so the two studios read as one product family.
   ============================================================ */

body.studio {
  background: radial-gradient(ellipse 140% 60% at 50% -10%, color-mix(in srgb, var(--accent) 14%, var(--bg)) 0%, var(--bg) 45%);
  min-height: 100vh;
}

body.studio-ski { --accent: var(--ski-accent); --accent-2: var(--ski-accent-2); --deep: var(--ski-deep); }
body.studio-snowboard { --accent: var(--snowboard-accent); --accent-2: var(--snowboard-accent-2); --deep: var(--snowboard-deep); }
body.studio-paddle { --accent: var(--paddle-accent); --accent-2: var(--paddle-accent-2); --deep: var(--paddle-deep); }
body.studio-surf { --accent: var(--surf-accent); --accent-2: var(--surf-accent-2); --deep: var(--surf-deep); }
body.studio-apparel { --accent: var(--apparel-accent); --accent-2: var(--apparel-accent-2); --deep: var(--apparel-deep); }

.studio-nav .brand-mark svg path { stroke: var(--text); }

.studio-title {
  text-align: center;
  padding: 4px clamp(16px, 4vw, 48px) 6px;
}

.studio-title h1 {
  margin: 0 0 4px;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(1.7rem, 2.6vw, 2.3rem);
  letter-spacing: 0.01em;
  background: linear-gradient(90deg, var(--accent), var(--accent-2));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.studio-title .subtitle {
  margin: 0;
  color: var(--muted);
  font-size: 0.95rem;
}

/* Desktop: a left pane of controls and a right pane holding the 3D viewer
   + specs, side by side — .viewer-col wraps the viewer and specs together
   (see the HTML) so the viewer can stay pinned to the top of that pane
   the same way it pins to the top of the whole page on the narrow layout
   below. Both panes are capped to the viewport height and scroll their
   own overflow internally: that's what guarantees the whole 3D area and
   at least one control are simultaneously on screen no matter how wide
   (or how short) the browser window is — a wide/short window used to let
   the viewer and the controls-panel both grow taller than the viewport,
   with no way to bring the rest of either into view. On the narrow layout,
   .viewer-col is neutralized (display:contents) so its children rejoin
   .app-layout directly, unchanged from the existing mobile behavior. */
.app-layout {
  display: grid;
  grid-template-columns: 340px minmax(280px, 900px);
  gap: 24px;
  padding: 12px clamp(16px, 4vw, 48px) 40px;
  align-items: start;
  max-width: 1320px;
  margin: 0 auto;
}

.controls-panel {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 20px;
  display: flex;
  flex-direction: column;
  gap: 18px;
  position: sticky;
  top: 16px;
  max-height: calc(100vh - 32px);
  overflow-y: auto;
}

.viewer-col {
  display: flex;
  flex-direction: column;
  gap: 24px;
  max-height: calc(100vh - 32px);
  overflow-y: auto;
}

/* Pinned to the top of its scroll pane on every layout — desktop pins it
   against .viewer-col scrolling past the specs card below; narrow pins it
   against the whole page scrolling past the slider list (see below). */
.viewer-card {
  position: sticky;
  top: 0;
  z-index: 20;
}

/* Narrow layout: below the width where the two-column layout would cramp
   both the controls and the 3D view, switch to one flex column and pin
   the 3D preview (plus its jump-to-parameter strip) to the top of the
   viewport instead of stacking it wherever it falls in source order —
   "narrow screen" means the preview needs to stay visible no matter how
   far down the slider list you've scrolled. Flex (not grid) is what makes
   the sticky viewer's stuck range span the *whole* page: as a plain flex
   child of .app-layout, its containing block is .app-layout itself, whose
   height already includes every slider below it. */
@media (max-width: 900px) {
  .app-layout {
    display: flex;
    flex-direction: column;
    max-width: none;
    /* The desktop rule above sets align-items:start for the grid, which
       (unset here) would size each flex item to its own content width —
       for .viewer-card that means the canvas's intrinsic pixel size,
       overflowing the narrow viewport. Stretch is what makes every item
       fill the column's actual width instead. */
    align-items: stretch;
  }
  /* Undo the desktop pane split: .viewer-col disappears from the box
     model entirely, so .viewer-card, #topDownCanvas and .spec-card become
     direct flex children of .app-layout again. */
  .viewer-col { display: contents; }
  .viewer-card { order: 1; }
  .controls-panel { order: 2; position: static; max-height: none; overflow-y: visible; }
  .spec-card { order: 3; }
}

.control-group { position: relative; }

/* Highlights whichever single control currently "owns" the 3D view — set in
   JS on focus (see each studio's app.js), cleared on blur, so at most one
   control is ever marked active at a time. Purely a visual echo of native
   focus (only one form control can be focused anyway); it exists so the
   camera-snap-on-select behavior below has an obvious, singular "this is
   what you're looking at" cue instead of relying on the focus ring alone. */
.control-group--active {
  border-radius: 10px;
  margin: -8px -10px;
  padding: 8px 10px;
  background: color-mix(in srgb, var(--accent) 7%, transparent);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 30%, transparent);
}

.control-group label {
  font-weight: 600;
  font-size: 0.9rem;
  color: var(--text);
}

.control-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 4px;
}

.param-label-wrap {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.info-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 15px;
  height: 15px;
  flex-shrink: 0;
  border-radius: 50%;
  border: 1px solid var(--muted);
  color: var(--muted);
  font-family: Georgia, "Times New Roman", serif;
  font-style: italic;
  font-weight: 700;
  font-size: 0.62rem;
  line-height: 1;
  cursor: help;
  user-select: none;
  transition: border-color 0.15s ease, color 0.15s ease;
}

.info-icon:hover,
.info-icon:focus-visible {
  border-color: var(--accent);
  color: var(--accent);
  outline: none;
}

/* Positioned by js/ui-helpers.js, not CSS: a tooltip anchored purely to its
   (often narrow, inline-width) label wrapper had no way to know how much
   room the viewport actually had, so it either got squeezed to the label's
   own width or ran off the edge of the screen. ui-helpers.js measures the
   viewport at show-time and sets left/top/width directly — as wide as is
   comfortable, but always clamped fully on-screen. Visibility is toggled
   via the .tip-visible class it adds/removes (not :hover/:focus-within),
   since positioning has to be computed before the tooltip can be shown. */
.param-tooltip {
  position: fixed;
  z-index: 40;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px 12px;
  font-size: 0.78rem;
  font-weight: 400;
  font-style: normal;
  font-family: var(--font-body);
  line-height: 1.4;
  color: var(--muted);
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.4);
  opacity: 0;
  pointer-events: none;
  transform: translateY(-4px);
  transition: opacity 0.15s ease, transform 0.15s ease;
}

.param-label-wrap:hover .info-icon,
.param-label-wrap:focus-within .info-icon {
  border-color: var(--accent);
  color: var(--accent);
}

.param-tooltip.tip-visible {
  opacity: 1;
  transform: translateY(0);
}

.value-readout {
  font-variant-numeric: tabular-nums;
  color: var(--accent);
  font-weight: 600;
  font-size: 0.9rem;
}

.hint {
  margin: 4px 0 0;
  font-size: 0.75rem;
  color: var(--muted);
  line-height: 1.3;
}

.control-group > label + input[type="range"] { margin-top: 4px; }

.range-caption {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  margin-top: 6px;
  font-size: 0.72rem;
  color: var(--muted);
  line-height: 1.3;
}

.range-caption span:first-child { text-align: left; }
.range-caption span:last-child { text-align: right; }

input[type="range"] {
  width: 100%;
  appearance: none;
  -webkit-appearance: none;
  height: 6px;
  border-radius: 999px;
  background: var(--panel-2);
  outline: none;
  cursor: pointer;
}

input[type="range"]::-webkit-slider-thumb {
  appearance: none;
  -webkit-appearance: none;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--accent);
  border: 2px solid var(--bg);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 25%, transparent);
  cursor: pointer;
}

input[type="range"]::-moz-range-thumb {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--accent);
  border: 2px solid var(--bg);
  cursor: pointer;
}

.preset-group select,
.control-group select {
  width: 100%;
  padding: 8px 10px;
  border-radius: 8px;
  background: var(--panel-2);
  color: var(--text);
  border: 1px solid var(--border);
  font-size: 0.9rem;
  font-family: var(--font-body);
  cursor: pointer;
}

.control-group select:hover,
.control-group select:focus-visible {
  border-color: var(--accent);
  outline: none;
}

.preset-select-wrap { position: relative; }

/* Positioned by js/ui-helpers.js — see the .param-tooltip comment above for
   why this can't be pure CSS. */
.preset-tooltip {
  position: fixed;
  z-index: 30;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px 12px;
  font-size: 0.78rem;
  line-height: 1.4;
  color: var(--muted);
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.4);
  opacity: 0;
  transform: translateY(-4px);
  pointer-events: none;
  transition: opacity 0.15s ease, transform 0.15s ease;
}

.preset-tooltip:empty { display: none; }

.preset-tooltip.tip-visible {
  opacity: 1;
  transform: translateY(0);
}

/* Preset guide: a tap-to-expand alternative to the hover-only preset
   tooltip above, for anyone (chiefly mobile, where nothing hovers) who
   wants to read every preset's description before picking one, not just
   the currently-selected one. */
.preset-guide-toggle {
  margin-top: 8px;
  padding: 0;
  border: none;
  background: none;
  color: var(--accent);
  font-family: var(--font-body);
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
  text-align: left;
}

.preset-guide-toggle::after {
  content: "\25BE";
  display: inline-block;
  margin-left: 4px;
  font-size: 0.7em;
  transition: transform 0.15s ease;
}

.preset-guide-toggle[aria-expanded="true"]::after {
  transform: rotate(180deg);
}

.preset-guide-toggle:hover,
.preset-guide-toggle:focus-visible {
  color: var(--accent-2);
  outline: none;
}

.preset-guide-list {
  margin-top: 8px;
  max-height: 260px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--panel-2);
}

.preset-guide-row {
  display: block;
  width: 100%;
  padding: 9px 12px;
  border: none;
  border-bottom: 1px solid var(--border);
  background: none;
  color: var(--text);
  font-family: var(--font-body);
  text-align: left;
  cursor: pointer;
}

.preset-guide-row:last-child { border-bottom: none; }

.preset-guide-row:hover,
.preset-guide-row:focus-visible {
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  outline: none;
}

.preset-guide-name {
  display: block;
  font-size: 0.83rem;
  font-weight: 600;
  margin-bottom: 2px;
}

.preset-guide-desc {
  display: block;
  font-size: 0.74rem;
  line-height: 1.4;
  color: var(--muted);
}

/* Stacks the OverSol brand mark and the Board/Logo toggle in one column
   on the header's left side, toggle directly under the wordmark. */
.viewer-brand-col {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Wraps the "Make it Match" button above whichever one of the two
   .surface-strip groups (surface tail/tip, or logo font/ring) is
   currently visible — exactly one pair of color pickers is ever shown at
   a time; see setColorMode()/makeItMatch() in ski/app.js. */
.color-controls {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 6px;
}

.color-mode-toggle {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.color-mode-btn {
  padding: 5px 10px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--panel-2);
  color: var(--muted);
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

.color-mode-btn:hover {
  border-color: var(--accent);
  color: var(--text);
}

.color-mode-btn.is-active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--deep);
}

.color-mode-btn--match {
  color: var(--accent);
  border-color: var(--accent);
}

.color-mode-btn--match:hover {
  background: var(--accent);
  color: var(--deep);
}

.surface-strip {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: flex-end;
  gap: 12px 16px;
  margin: 2px 0 14px;
}

/* .surface-strip's own `display: flex` above otherwise beats the
   browser's default `[hidden] { display: none }` rule (author styles win
   ties over UA styles regardless of selector order) — without this, the
   Board/Logo color-mode toggle in ski/app.js's setColorMode() would set
   the `hidden` attribute on whichever group is inactive but the group
   would stay visibly laid out anyway. */
#surfaceColorGroup[hidden],
#logoColorGroup[hidden] {
  display: none;
}

.surface-strip .color-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: 108px;
}

.surface-strip .color-field--end {
  align-items: flex-end;
  text-align: right;
}

.surface-strip .color-field label {
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}

.surface-strip input[type="color"] {
  width: 100%;
  height: 32px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--panel-2);
  cursor: pointer;
}

.section-heading {
  margin: 4px 0 -6px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  font-weight: 700;
}

.reset-btn {
  margin-top: 4px;
  padding: 10px 14px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--panel-2);
  color: var(--text);
  font-size: 0.85rem;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}

.reset-btn:hover {
  background: var(--deep);
  border-color: var(--accent);
}

.export-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.export-row .btn {
  flex: 1 1 auto;
  font-size: 0.78rem;
  padding: 9px 12px;
}

/* ---------- Product pages (apparel) ---------- */

.product-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}

.product-title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.5rem;
  letter-spacing: 0.01em;
  background: linear-gradient(90deg, var(--accent), var(--accent-2));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.product-price {
  margin: 0;
  font-size: 1.1rem;
  font-weight: 700;
  color: var(--text);
  white-space: nowrap;
}

.product-description {
  margin: -8px 0 0;
  color: var(--muted);
  font-size: 0.92rem;
  line-height: 1.55;
}

.size-pills {
  display: flex;
  gap: 8px;
}

.size-pill {
  flex: 1 1 auto;
  border: 1px solid var(--border);
  background: var(--panel-2);
  color: var(--muted);
  font-family: var(--font-body);
  font-size: 0.85rem;
  font-weight: 600;
  padding: 10px 12px;
  border-radius: 8px;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}

.size-pill:hover,
.size-pill:focus-visible {
  color: var(--text);
  border-color: var(--accent);
}

.size-pill.selected {
  background: linear-gradient(120deg, var(--accent), var(--accent-2));
  border-color: transparent;
  color: #0a0e17;
}

.cart-note {
  min-height: 1.3em;
  color: var(--accent-2);
  font-size: 0.85rem;
  font-weight: 600;
  opacity: 0;
  transition: opacity 0.2s ease;
}

.cart-note.visible {
  opacity: 1;
}

.visually-hidden {
  position: absolute !important;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.viewer-card {
  padding-bottom: 18px;
  /* The <canvas> a few levels down carries native width/height attributes,
     giving it an intrinsic min-content size that grid/flex items respect
     by default (min-width: auto) regardless of the "width: 100%" CSS below
     — without this, the card refuses to shrink narrower than that canvas's
     last-rendered pixel size and overflows the mobile layout. */
  min-width: 0;
}

.canvas-card,
.spec-card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 18px 20px 22px;
}

.canvas-card h2,
.spec-card h2 {
  margin: 0 0 10px;
  font-size: 1rem;
  color: var(--text);
  font-weight: 600;
}

.canvas-card h2 .muted {
  color: var(--muted);
  font-weight: 400;
  font-size: 0.8rem;
}

.canvas-card-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}

.canvas-card-header h2 { margin: 0 0 10px; }

/* Replaces the old "3D Preview" label — that text described the obvious
   and told the user nothing useful, so the header slot now carries the
   brand mark instead (the same one in the top nav, at a smaller size).
   It's part of the sticky viewer card, so — like the on-canvas badge it
   replaces — it never scrolls out of view either. */
.viewer-brand-mark {
  display: flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
  color: var(--text);
  font-family: var(--font-display);
  /* Sized as large as the header row comfortably allows: the vw term does
     the scaling, the clamp() ceiling/floor keep it from ever outgrowing a
     wide column or getting cramped next to the color swatches on a narrow
     one (where flex-wrap on .canvas-card-header lets it drop to its own
     line if it still doesn't fit). */
  font-size: clamp(1.05rem, 4vw, 1.4rem);
  font-weight: 600;
  letter-spacing: 0.04em;
}

.viewer-brand-mark .viewer-icon {
  height: clamp(36px, 11vw, 56px);
  width: auto;
  display: block;
}

.viewer-brand-mark:hover {
  color: var(--accent);
}

canvas {
  width: 100%;
  height: auto;
  display: block;
  background: var(--panel-2);
  border-radius: 8px;
}

.viewer-3d-wrap {
  position: relative;
  width: 100%;
  min-width: 0;
  aspect-ratio: 4 / 3;
  /* Caps the viewer's height directly against the viewport rather than
     letting it grow without bound from the width alone — on a wide
     monitor the 340px-plus-900px-max column is still very wide, and
     without this the aspect-ratio-derived height could exceed the
     viewport before the width cap above ever kicks in. The 3D canvas
     re-reads its container's actual size on load/resize (see
     js/viewer3d.js), so it always renders correctly whatever shape
     results — nothing here depends on the box staying exactly 4:3. */
  max-height: 56vh;
  border-radius: 8px;
  overflow: hidden;
  background: radial-gradient(ellipse at 50% 30%, color-mix(in srgb, var(--accent) 10%, var(--panel-2)) 0%, var(--panel-2) 70%);
}

@media (max-width: 900px) {
  /* max-height: none used to mean this box's height tracked width alone —
     fine down at phone widths, where a narrower width already yields a
     short box, but at "half a laptop screen" widths (roughly 700-900px)
     the same aspect-ratio produces a box tall enough to rival or exceed
     the viewport itself. Because .viewer-card is pinned (position:sticky;
     top:0) with a stacking-context z-index above the normal-flow controls
     that follow it, any control-group that ends up scrolled to a screen
     position "under" an oversized pinned card is fully hidden behind it —
     not just cropped — for the entire scroll range, so it can never be
     brought into view. Capping height against the viewport (like the
     desktop 56vh rule already does) guarantees a shrinking or merely-wide
     viewport always leaves real space below the pinned card for at least
     one control. The 3D canvas re-reads its container's actual size on
     load/resize (see the comment further up), so it renders correctly
     whatever shape this produces — nothing depends on staying exactly
     4:3.4 once this cap is what's binding. */
  .viewer-3d-wrap { aspect-ratio: 4 / 3.4; max-height: min(46vh, calc(100vh - 340px)); }
}

/* Small, persistent brand mark overlaid on the 3D viewer itself — the one
   spot in the app that's always on screen on every layout (pinned at the
   top of its pane on desktop, pinned at the top of the page on narrow
   screens), so the OverSol mark never scrolls out of view even though the
   top nav bar (which also carries it) does. */
.viewer-brand {
  position: absolute;
  top: 10px;
  left: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 60px;
  height: 60px;
  border-radius: 50%;
  background: rgba(8, 11, 18, 0.55);
  pointer-events: none;
  z-index: 5;
}

.viewer-brand .viewer-icon {
  width: 42px;
  height: auto;
  display: block;
}

.viewer-3d-wrap canvas {
  width: 100%;
  height: 100%;
  border-radius: 0;
  background: transparent;
  touch-action: none;
}

/* Apparel product pages (e.g. the hat) show a static illustration in this
   same viewer card instead of a <canvas> — same box, same sizing rule. */
.viewer-3d-wrap svg.product-illustration {
  width: 100%;
  height: 100%;
  display: block;
}

.viewer-3d-hint {
  position: absolute;
  left: 12px;
  bottom: 10px;
  font-size: 0.7rem;
  color: var(--muted);
  background: rgba(8, 11, 18, 0.55);
  padding: 4px 9px;
  border-radius: 999px;
  pointer-events: none;
}

/* Live key-spec readout overlaid on the 3D view itself — so the numbers
   that actually describe what you're looking at (length, waist/underfoot,
   sidecut radius) are visible right there instead of only in the Specs
   card, which can be scrolled out of view below the viewer on narrow
   screens. Kept to three values and small type deliberately, so it reads
   as a light HUD, not a second spec card competing with the real one. */
.viewer-stats {
  position: absolute;
  left: 12px;
  bottom: 10px;
  display: flex;
  gap: 12px;
  padding: 6px 12px;
  border-radius: 10px;
  background: rgba(8, 11, 18, 0.6);
  pointer-events: none;
  z-index: 5;
}

.viewer-stat {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  line-height: 1.2;
}

.viewer-stat + .viewer-stat {
  padding-left: 12px;
  border-left: 1px solid rgba(255, 255, 255, 0.14);
}

.viewer-stat-value {
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--accent-2);
  font-variant-numeric: tabular-nums;
}

.viewer-stat-label {
  font-size: 0.58rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
}

@media (max-width: 900px) {
  .viewer-stats { gap: 9px; padding: 5px 10px; }
  .viewer-stat + .viewer-stat { padding-left: 9px; }
  .viewer-stat-value { font-size: 0.74rem; }
  .viewer-stat-label { font-size: 0.54rem; }
}

.viewer-3d-error {
  position: absolute;
  inset: 0;
  display: none;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 24px;
  color: var(--muted);
  font-size: 0.85rem;
  line-height: 1.5;
}

/* Jump-to-parameter strip: only shown once the viewer is pinned (narrow
   layout, see .app-layout's 900px breakpoint above) — on desktop the full
   slider list is already visible beside the viewer, so it'd be redundant. */
.jump-nav {
  display: none;
}

@media (max-width: 900px) {
  .jump-nav {
    display: flex;
    gap: 8px;
    overflow-x: auto;
    margin-top: 14px;
    padding-bottom: 2px;
    scrollbar-width: thin;
    -webkit-overflow-scrolling: touch;
  }

  .jump-pill {
    flex: 0 0 auto;
    border: 1px solid var(--border);
    background: var(--panel-2);
    color: var(--muted);
    font-family: var(--font-body);
    font-size: 0.78rem;
    font-weight: 600;
    white-space: nowrap;
    padding: 7px 14px;
    border-radius: 999px;
    cursor: pointer;
    transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
  }

  .jump-pill:hover,
  .jump-pill:focus-visible {
    color: var(--text);
    border-color: var(--accent);
  }

  .jump-pill.active {
    background: linear-gradient(120deg, var(--accent), var(--accent-2));
    border-color: transparent;
    color: #0a0e17;
  }
}

/* ---------- "Describe This X" (oracle overlay) ----------
   Shared across every studio: matches the live control values against
   that studio's own presets (ski additionally checks its 200-entry
   description library — see findClosestDescription() in each app.js) and
   shows the closest match as an overlay on the 3D viewer — the in-context
   alternative to relying on the preset dropdown/guide, which only ever
   describes a named preset, never whatever the controls are currently
   set to. Class names here are studio-agnostic (.describe-btn, .oracle*)
   so every studio's index.html can reuse this same block; only the
   button/overlay ids differ per studio (e.g. #skiOracle, #snowboardOracle). */
.describe-btn {
  position: absolute;
  top: 10px;
  right: 12px;
  padding: 8px 14px;
  border-radius: 999px;
  border: 1px solid transparent;
  background: linear-gradient(120deg, var(--accent), var(--accent-2));
  color: #0a0e17;
  font-family: var(--font-body);
  font-size: 0.78rem;
  font-weight: 700;
  cursor: pointer;
  box-shadow: 0 6px 18px -6px rgba(0, 0, 0, 0.6);
  transition: box-shadow 0.15s ease, transform 0.15s ease;
  z-index: 6;
}

.describe-btn:hover,
.describe-btn:focus-visible {
  box-shadow: 0 8px 24px -8px var(--accent);
  transform: translateY(-1px);
}

@media (max-width: 900px) {
  .describe-btn { font-size: 0.72rem; padding: 7px 12px; }
}

/* The oracle overlay: speaks over the 3D ski itself instead of a panel
   below it — dimming/blurring the model behind it like a vision rather
   than replacing it, so the ski you're being told about is still the
   thing on screen. Covers the whole viewer (inset:0) so it never adds
   height to .viewer-card, and sits above everything else in the viewer
   (canvas, stat HUD, describe button) via z-index. Dismissed by a click
   anywhere on it — see the click listener in app.js. */
.oracle {
  position: absolute;
  inset: 0;
  z-index: 15;
  display: flex;
  /* display:flex above and [hidden]'s UA-stylesheet display:none have
     equal (0,1,0) specificity, and an author rule wins ties over a UA
     one — so without this, the element stays visibly displayed (and
     keeps intercepting clicks) even while the hidden attribute is set.
     .oracle[hidden]'s higher specificity forces it hidden for real. */
  padding: 32px 28px;
  cursor: pointer;
  /* overflow-y:auto here (not justify-content:center on this element
     directly) is what keeps a long description reachable when the 3D
     viewer is short — see .oracle-inner's margin:auto trick below,
     the standard way to get "centered when it fits, scrollable from the
     top when it doesn't" instead of clipping or starting mid-scroll. */
  overflow-y: auto;
  background:
    radial-gradient(ellipse 85% 65% at 50% 46%, color-mix(in srgb, var(--accent) 18%, rgba(6, 8, 14, 0.88)) 0%, rgba(4, 6, 11, 0.95) 65%, rgba(2, 3, 6, 0.98) 100%);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  animation: oracle-fade-in 0.45s ease both;
}

.oracle[hidden] {
  display: none;
}

/* margin:auto on a flex item absorbs all the container's slack when the
   content is shorter than the box (true vertical centering), and clamps
   to 0 instead of clipping when the content is taller — combined with
   .oracle's overflow-y:auto above, that's what lets you scroll up
   from the bottom of a long description to read its title, rather than
   the box just cutting it off. */
.oracle-inner {
  margin: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  text-align: center;
}

@keyframes oracle-fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* An inscription face, not the app's usual type — reads as "words from
   somewhere above," not another UI label. */
.oracle-name {
  font-family: "Cinzel", var(--font-display), serif;
  font-weight: 700;
  font-size: clamp(1.35rem, 3.6vw, 2.1rem);
  letter-spacing: 0.07em;
  text-transform: uppercase;
  background: linear-gradient(90deg, var(--accent-2), var(--accent), var(--accent-2));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: drop-shadow(0 0 22px color-mix(in srgb, var(--accent) 55%, transparent));
  animation: oracle-name-rise 0.65s cubic-bezier(0.2, 0.8, 0.2, 1) 0.08s both;
}

@keyframes oracle-name-rise {
  from { opacity: 0; transform: translateY(8px); letter-spacing: 0.22em; }
  to { opacity: 1; transform: translateY(0); letter-spacing: 0.07em; }
}

.oracle-flag {
  display: none;
  font-family: var(--font-body);
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: #ffb4a8;
  border: 1px solid rgba(255, 143, 107, 0.55);
  border-radius: 999px;
  padding: 4px 12px;
}

/* Not-recommended matches read as a warning, not a vision — a cooled,
   ember-toned inscription instead of the usual accent glow. */
.oracle--not-recommended .oracle-name {
  background: linear-gradient(90deg, #ffb4a8, #ff8f6b, #ffb4a8);
  -webkit-background-clip: text;
  background-clip: text;
  filter: drop-shadow(0 0 22px rgba(255, 143, 107, 0.45));
}

.oracle--not-recommended .oracle-flag {
  display: inline-block;
}

.oracle-text {
  max-width: 48ch;
  margin: 0;
  font-family: "Cormorant Garamond", var(--font-display), serif;
  font-style: italic;
  font-size: clamp(0.96rem, 1.9vw, 1.15rem);
  line-height: 1.6;
  color: #e7ecf7;
  text-shadow: 0 1px 14px rgba(0, 0, 0, 0.65);
  animation: oracle-text-fade 0.7s ease 0.22s both;
}

@keyframes oracle-text-fade {
  from { opacity: 0; }
  to { opacity: 1; }
}

.oracle-hint {
  margin-top: 4px;
  font-family: var(--font-body);
  font-size: 0.66rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--muted);
  animation: oracle-hint-pulse 1.8s ease-in-out infinite;
}

@keyframes oracle-hint-pulse {
  0%, 100% { opacity: 0.4; }
  50% { opacity: 0.85; }
}

@media (max-width: 900px) {
  .oracle { padding: 22px 18px; }
  .oracle-inner { gap: 9px; }
  .oracle-text { max-width: 38ch; }
}

.spec-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 12px 20px;
  margin: 0;
}

.spec-item {
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px 12px;
}

/* A handful of specs (fin setup, camber profile, ride style) are a phrase
   or sentence, not a short number+unit — spanning the full row and
   dropping to a smaller, non-bold value style keeps those readable
   instead of wrapping a long sentence at a numeric spec's large bold size. */
.spec-item--wide {
  grid-column: 1 / -1;
}

.spec-item--wide .spec-value {
  font-size: 0.92rem;
  font-weight: 600;
  line-height: 1.4;
  color: var(--text);
}

.spec-item .spec-label {
  display: block;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
  margin-bottom: 2px;
}

.spec-item .spec-value {
  display: block;
  font-size: 1.1rem;
  font-weight: 700;
  color: var(--accent-2);
}

.app-footer {
  text-align: center;
  padding: 10px 16px 30px;
  color: var(--muted-dim);
  font-size: 0.75rem;
}

.app-footer a { color: var(--muted); }
