/* ===================================================================
 * Deskfab — Page-specific styles
 *
 * This file layers on top of the shared Tactical HUD system. It adds
 * ONE product-level colour override block plus the layout primitives
 * that do not exist in the shared CSS (hero scene, bento grid,
 * application tracks).
 * =================================================================== */

/* ===================================================================
 * 1. PRODUCT ACCENT OVERRIDE  —  the only colour block on this page
 * -------------------------------------------------------------------
 * Deskfab is a hardware product and carries an industrial-orange accent
 * (#f5a623) instead of the organisation's lime (#A7D860).
 *
 * TO RE-SKIN THIS PRODUCT LATER, EDIT ONLY THIS BLOCK. Nothing else in
 * deskfab.css or deskfab.html hard-codes an accent colour: backgrounds, text,
 * spacing, radii, typography and motion all inherit unchanged from
 * css/variables.css. The hero SVG scene also draws every stroke and fill
 * from the scene tokens declared at the bottom of this block, so it
 * re-skins with the theme rather than carrying baked-in hex values.
 *
 * Alpha values below are identical to the lime originals in
 * variables.css — only the RGB channel changes.
 *
 *   lime  #A7D860 = rgb(167, 216,  96)
 *   orange#f5a623 = rgb(245, 166,  35)
 *   teal  #7bd6d1 = rgb(123, 214, 209)   <- SECONDARY DATA ACCENT ONLY
 * =================================================================== */

/* Registered so the lime -> orange entry transition can interpolate.
   Unregistered custom properties do not animate smoothly; @property
   with syntax "<color>" makes them animatable. Browsers without
   @property support fall back to a discrete (instant) swap. */
@property --accent-primary {
  syntax: "<color>";
  inherits: true;
  initial-value: #A7D860;
}

@property --accent-primary-glow {
  syntax: "<color>";
  inherits: true;
  initial-value: rgba(167, 216, 96, 0.3);
}

@property --accent-primary-dim {
  syntax: "<color>";
  inherits: true;
  initial-value: rgba(167, 216, 96, 0.15);
}

@property --accent-2 {
  syntax: "<color>";
  inherits: true;
  initial-value: #6FED0E;
}

@property --line-primary {
  syntax: "<color>";
  inherits: true;
  initial-value: rgba(167, 216, 96, 0.2);
}

@property --line-grid {
  syntax: "<color>";
  inherits: true;
  initial-value: rgba(167, 216, 96, 0.05);
}

@property --line-reticle {
  syntax: "<color>";
  inherits: true;
  initial-value: rgba(167, 216, 96, 0.1);
}

/* The product accent. THESE SEVEN VALUES ARE THE ONLY COLOURS TO EDIT. */
body.theme-deskfab {
  --accent-primary: #f5a623;
  --accent-primary-glow: rgba(245, 166, 35, 0.3);
  --accent-primary-dim: rgba(245, 166, 35, 0.15);
  --accent-2: #ffc04d;
  --line-primary: rgba(245, 166, 35, 0.2);
  --line-grid: rgba(245, 166, 35, 0.05);
  --line-reticle: rgba(245, 166, 35, 0.1);

  /* SECONDARY DATA ACCENT. Not a second brand colour: it is allowed only
     on status pips, secondary/instrumentation labels and the Academia
     track's accent bar. Three values, edit them together. */
  --df-data: #7bd6d1;
  --df-data-line: rgba(123, 214, 209, 0.35);
  --df-data-glow: rgba(123, 214, 209, 0.25);

  /* Hero SVG scene. Aliases only — no colour is written here, so the
     scene follows the accent above automatically. */
  --df-scene-line: var(--accent-primary);
  --df-scene-structure: var(--accent-primary-dim);
  --df-scene-data: var(--df-data);

  /* Alias pass-throughs. variables.css declares these on :root as
     var() of the tokens above, which means they are SUBSTITUTED AT
     :root and inherit the resolved lime literal. Re-declaring the same
     aliases here forces them to resolve against the values above.
     No colour is written below this line — do not edit these when
     re-skinning, they follow the seven values automatically. */
  --accent: var(--accent-primary);
  --color-primary: var(--accent-primary);
  --color-primary-alt: var(--accent-primary);
  --color-primary-hover: var(--accent-2);
  --status-online: var(--accent-primary);
  --color-success: var(--accent-primary);
  --line: var(--line-primary);
  --color-overlay-light: var(--line-primary);
  --glow: var(--accent-primary-glow);
  --glow-primary: 0 0 20px var(--accent-primary-glow);
  --glow-strong: 0 0 40px var(--accent-primary-glow);
  --shadow-glow: 0 0 20px var(--accent-primary-glow);
  --shadow-glow-strong: 0 0 40px var(--accent-primary-glow);
  --shadow-card-hover: 0 12px 48px rgba(0, 0, 0, 0.6), 0 0 24px var(--accent-primary-glow);
  --shadow-panel-hover: 0 12px 48px rgba(0, 0, 0, 0.7), 0 0 20px var(--accent-primary-glow);
}

/* ===================================================================
 * 2. Entry transition — organisation lime shifts to product orange
 * -------------------------------------------------------------------
 * One-shot on load. `both` fill keeps the final orange in place after
 * the animation ends; the static declarations above are the same
 * colour, so a browser that ignores the animation entirely still
 * renders correct orange.
 * =================================================================== */

@keyframes deskfab-accent-handover {
  from {
    --accent-primary: #A7D860;
    --accent-primary-glow: rgba(167, 216, 96, 0.3);
    --accent-primary-dim: rgba(167, 216, 96, 0.15);
    --accent-2: #6FED0E;
    --line-primary: rgba(167, 216, 96, 0.2);
    --line-grid: rgba(167, 216, 96, 0.05);
    --line-reticle: rgba(167, 216, 96, 0.1);
  }
  to {
    --accent-primary: #f5a623;
    --accent-primary-glow: rgba(245, 166, 35, 0.3);
    --accent-primary-dim: rgba(245, 166, 35, 0.15);
    --accent-2: #ffc04d;
    --line-primary: rgba(245, 166, 35, 0.2);
    --line-grid: rgba(245, 166, 35, 0.05);
    --line-reticle: rgba(245, 166, 35, 0.1);
  }
}

body.theme-deskfab {
  animation: deskfab-accent-handover 800ms var(--ease-precise) 1 both;
}

/* Accompanying one-shot sweep across the hero, reusing the shared
   shimmer-scan keyframes from css/animations.css. */
.deskfab-hero-sweep {
  position: absolute;
  top: 0;
  left: -100%;
  width: 100%;
  height: 100%;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(
    105deg,
    transparent 0%,
    transparent 40%,
    var(--accent-primary-dim) 50%,
    transparent 60%,
    transparent 100%
  );
  animation: shimmer-scan 900ms var(--ease-smooth) 1 both;
}

@media (prefers-reduced-motion: reduce) {
  /* Skip the handover and the sweep; final orange renders immediately
     because body.theme-deskfab already declares it statically. */
  body.theme-deskfab {
    animation: none;
  }

  .deskfab-hero-sweep {
    display: none;
  }
}

/* ===================================================================
 * 3. Shape language — sharp edges
 * -------------------------------------------------------------------
 * Deskfab's own surfaces are 0px radius ("precision-engineered"). Site
 * chrome (nav, footer) is deliberately left alone so the page still
 * reads as part of the same website.
 * =================================================================== */

body.theme-deskfab .panel,
body.theme-deskfab .card,
body.theme-deskfab .tech-card,
body.theme-deskfab .btn,
body.theme-deskfab .deskfab-hero-panel,
body.theme-deskfab .deskfab-bento-card {
  border-radius: 0;
}

/* Benchfab's corner brackets grow on hover — a small improvement on the
   shared motif, which only changes opacity. Scoped to this page. */
body.theme-deskfab .bracket-corner::before,
body.theme-deskfab .bracket-corner::after {
  width: 10px;
  height: 10px;
  transition: width var(--duration-fast) var(--ease-precise),
              height var(--duration-fast) var(--ease-precise),
              opacity var(--duration-fast) var(--ease-precise),
              box-shadow var(--duration-fast) var(--ease-precise);
}

body.theme-deskfab .bracket-corner:hover::before,
body.theme-deskfab .bracket-corner:hover::after {
  width: 20px;
  height: 20px;
}

@media (prefers-reduced-motion: reduce) {
  body.theme-deskfab .bracket-corner::before,
  body.theme-deskfab .bracket-corner::after {
    transition: none;
  }
}

/* ===================================================================
 * 4. Logo mark
 * =================================================================== */

/* reset.css sets `img { display: block }`, which would drop the wordmark
   onto its own line. Keep the mark inline beside the text and let the
   width/height attributes carry the intrinsic 1000x668 aspect ratio. */

/* The logo joins the lime -> orange handover.
 *
 * The mark is a flat PNG: a white "T" on rgb(161, 210, 88) lime. It cannot
 * carry a CSS colour, so the shift is done with a filter. hue-rotate and
 * saturate are both linear matrices that leave neutral greys untouched
 * (R == G == B maps to itself), so the white half of the mark and its grey
 * drop shadow are unaffected — only the lime channel moves.
 *
 * The two values were solved numerically against the real pixel values
 * rather than eyeballed:
 *
 *   in   rgb(161, 210,  88)  ->  out  rgb(255, 166,  35)
 *   target --accent-primary  =       rgb(245, 166,  35)   (#f5a623)
 *   white rgb(243, 243, 243) ->  out  rgb(243, 243, 243)  (unchanged)
 *
 * Red clips 10 above target, which is not perceptible next to the orange
 * it sits beside. If the mark is ever re-exported at a different lime,
 * re-solve these two numbers — do not nudge them by eye.
 *
 * Duration and easing are deliberately identical to
 * deskfab-accent-handover so the mark and the interface change together
 * rather than as two separate events. */
@keyframes deskfab-logo-handover {
  from {
    filter: hue-rotate(0deg) saturate(1);
  }
  to {
    filter: hue-rotate(-57.5deg) saturate(1.94);
  }
}

body.theme-deskfab .logo-mark {
  filter: hue-rotate(-57.5deg) saturate(1.94);
  animation: deskfab-logo-handover 800ms var(--ease-precise) 1 both;
}

@media (prefers-reduced-motion: reduce) {
  /* Land on the orange immediately; the static filter above already
     holds the final value. */
  body.theme-deskfab .logo-mark {
    animation: none;
  }
}

/* ===================================================================
 * 5. Hero — glass panel over the animated machine-floor scene
 * -------------------------------------------------------------------
 * body.page-interior sets .hero { min-height: 52vh } for the plain
 * interior heroes on other pages. This page's hero carries a full-bleed
 * scene behind a floating panel and needs the height back, so it is
 * overridden here (higher specificity) rather than by dropping the
 * class from <body>.
 * =================================================================== */

body.theme-deskfab.page-interior .hero {
  min-height: 90vh;
  padding: var(--spacing-24) var(--container-padding) var(--spacing-20);
}

body.theme-deskfab.page-interior .hero-content {
  max-width: 880px;
}

body.theme-deskfab.page-interior .hero-bg::before {
  width: 800px;
  height: 800px;
}

/* --- the scene layer ------------------------------------------------ */

.deskfab-scene {
  position: absolute;
  inset: 0;
  z-index: 1;
  overflow: hidden;
  pointer-events: none;
  opacity: 0.3;
}

.deskfab-scene-svg {
  display: block;
  width: 100%;
  height: 100%;
}

/* Every colour in the scene resolves from the scene tokens in block 1. */
.deskfab-scene-svg .df-edge {
  fill: none;
  stroke: var(--df-scene-line);
}

.deskfab-scene-svg .df-struct {
  fill: var(--df-scene-structure);
  stroke: none;
}

.deskfab-scene-svg .df-crate {
  fill: var(--df-scene-line);
  stroke: var(--df-scene-line);
}

.deskfab-scene-svg .df-read {
  fill: var(--df-scene-data);
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.05em;
}

/* The frozen twin is markup-identical to the animated layer minus its
   <animate>/<animateTransform> children. Hidden by default. */
.deskfab-scene-frozen {
  display: none;
}

/* --- reduced motion -------------------------------------------------
 * SMIL ignores prefers-reduced-motion and cannot be paused from CSS
 * (animation-play-state has no effect on it). The only reliable guard
 * is to remove the animated subtree from rendering and reveal the
 * static twin in its place, so the scene still reads as a machine floor
 * with nothing moving at all.
 * ------------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  .deskfab-scene-motion {
    display: none;
  }

  .deskfab-scene-frozen {
    display: inline;
  }
}

/* --- two-column layout: text left, 3D viewer right ------------------
 * Above 1024px the hero text panel and the sample-configuration viewer
 * sit side by side; below it they stack, text first. The scene layer
 * above is unaffected - it is a sibling of .container, full-bleed
 * behind both columns.
 * ------------------------------------------------------------------ */

.deskfab-hero-grid {
  position: relative;
  z-index: 5;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--spacing-12);
  align-items: center;
  width: 100%;
}

.deskfab-hero-viewer {
  min-width: 0;
}

/* Fixed ratio, not the bento's flex-grow: here the stage has no row
   neighbour to inherit a height from, so it must claim its own height
   up front or the page jumps as the poster (and later the model)
   decodes. */
.deskfab-hero-viewer .deskfab-viewer-stage {
  flex-grow: 0;
  width: 100%;
  aspect-ratio: 4 / 3;
  min-height: 340px;
}

.deskfab-hero-viewer .deskfab-viewer-caption {
  margin-top: var(--spacing-3);
}

@media (max-width: 1024px) {
  .deskfab-hero-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

@media (max-width: 768px) {
  .deskfab-hero-viewer .deskfab-viewer-stage {
    min-height: 240px;
  }
}

/* --- the glass panel ------------------------------------------------ */

.deskfab-hero-panel {
  position: relative;
  z-index: 5;
  text-align: left;
  padding: var(--spacing-12) var(--spacing-10);
  background: var(--bg-panel);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border: 1px solid var(--line-primary);
  border-top: 2px solid var(--accent-primary);
  box-shadow: var(--shadow-panel, 0 8px 32px rgba(0, 0, 0, 0.5));
}

/* The shared hero-subtitle and hero-cta centre themselves by default
   (main.css / components.css); this hero is left-aligned instead. */
.deskfab-hero-panel .hero-subtitle {
  margin-left: 0;
  margin-right: 0;
}

.deskfab-hero-panel .hero-cta {
  justify-content: flex-start;
}

.deskfab-eyebrow {
  display: block;
  margin-bottom: var(--spacing-4);
}

.deskfab-hero-title {
  margin-bottom: var(--spacing-6);
}

body.theme-deskfab.page-interior .hero-title {
  font-size: inherit;
}

.deskfab-hero-name {
  display: block;
  font-size: clamp(44px, 7vw, 84px);
  font-weight: var(--font-weight-heading-bold);
  line-height: 1.05;
  letter-spacing: var(--letter-spacing-tight);
  margin-bottom: var(--spacing-4);
}

.deskfab-hero-descriptor {
  display: block;
  font-family: var(--font-body);
  font-size: clamp(16px, 1.9vw, 22px);
  font-weight: var(--font-weight-body);
  line-height: 1.45;
  color: var(--color-text-secondary);
  max-width: 42ch;
  margin: 0;
}

/* Teal status pips on the HUD strip — secondary data accent. */
.deskfab-pip {
  display: inline-block;
  width: 6px;
  height: 6px;
  flex-shrink: 0;
  margin-right: var(--spacing-2);
  background: var(--df-data);
  box-shadow: 0 0 6px var(--df-data-glow);
}

/* ===================================================================
 * 6. Section titles with a leading HUD icon
 * =================================================================== */

.deskfab-title-icon {
  color: var(--accent-primary);
  margin-right: var(--spacing-3);
  font-size: 0.8em;
  vertical-align: baseline;
}

/* ===================================================================
 * 7. Pillar cards — icon head on top of the shared .tech-card
 * =================================================================== */

.deskfab-pillar-icon {
  display: block;
  font-size: var(--font-size-3xl);
  line-height: 1;
  color: var(--accent-primary);
  margin-bottom: var(--spacing-6);
  opacity: 0.85;
  transition: opacity var(--transition-base);
}

.tech-card:hover .deskfab-pillar-icon {
  opacity: 1;
}

/* The shared .tech-grid (css/main.css) auto-fits at minmax(300px, 1fr),
   which only leaves room for three tracks inside this page's 1200px
   container, so the four Core Technical Pillars cards wrapped 3-then-1.
   Force four explicit columns here instead, collapsing at the same
   1024px / 768px breakpoints the shared grid already uses. */
body.theme-deskfab .tech-grid {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

@media (max-width: 1024px) {
  body.theme-deskfab .tech-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 768px) {
  body.theme-deskfab .tech-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ===================================================================
 * 8. Bento configuration grid
 * -------------------------------------------------------------------
 * Three columns, dense and asymmetric:
 *
 *   col      1        2        3
 *   row 1  [ machines        ][ drive     ]
 *   row 2  [ kinematic       ][ ctrl ]
 *   row 3  [ processors           ][ ctrl ]
 *   row 4  [ peripherals          ][ ctrl ]
 *
 * Everything here is auto-placed — there are no line numbers, only
 * spans - so SOURCE ORDER IS LAYOUT ORDER. The sample-configuration
 * viewer used to sit between Drive and Kinematics here; it now lives in
 * the hero (see deskfab.html), and Controllers took the column it gave
 * up, spanning all three rows beside Kinematics / Processors /
 * Peripherals instead of just two beside Processors / Peripherals.
 *
 * Collapses to two columns at 1024px (machines / kinematics / processors
 * / peripherals keep their full-width span; controllers takes a
 * full-width row of its own rather than a tall column) and to one
 * column at 768px, where every span is reset and rows auto-size.
 * =================================================================== */

.deskfab-bento {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  grid-auto-rows: minmax(180px, auto);
  gap: var(--spacing-6);
}

.deskfab-bento-card {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.deskfab-bento-machines,
.deskfab-bento-kinematics,
.deskfab-bento-processors,
.deskfab-bento-peripherals {
  grid-column: span 2;
}

.deskfab-bento-controllers {
  grid-row: span 3;
}

/* --- sample configuration viewer ------------------------------------
 * The stage is the render surface: the poster, and later the WebGL
 * canvas, are both absolutely positioned to fill it, so the card's
 * height never changes when one replaces the other. It lives in the
 * hero now (see deskfab.html); the base rule below only sets the
 * surface itself, and css/deskfab.css section 5's hero-scoped rules
 * give it its actual size.
 * ------------------------------------------------------------------ */

.deskfab-viewer-stage {
  position: relative;
  flex-grow: 1;
  min-height: 240px;
  overflow: hidden;
  background: var(--line-grid);
  border: 1px solid var(--line-primary);
}

.deskfab-viewer-poster,
.deskfab-viewer-canvas {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
}

.deskfab-viewer-poster {
  object-fit: cover;
  /* Cross-faded out once the model has rendered its first frame. The
     poster stays underneath rather than being removed: if the context
     is ever lost the card falls back to it without a reflow. */
  opacity: 1;
  transition: opacity var(--transition-slow, 600ms ease);
}

/* Set when the image 404s. The poster is decorative, so a broken-image
   glyph would be pure noise — the empty instrument frame reads better. */
.deskfab-viewer-stage.is-poster-missing .deskfab-viewer-poster {
  display: none;
}

.deskfab-viewer-canvas {
  opacity: 0;
  transition: opacity var(--transition-slow, 600ms ease);
  /* The model turns with a drag; say so with the cursor. */
  cursor: grab;
  /* touch-action is NOT set here. OrbitControls sets it on the element
     itself (an inline style, so it would win over anything declared in
     this file) to claim every touch gesture for itself; js/deskfab-3d.js
     overrides that inline value straight back to pan-y once the canvas
     exists, so a one-finger vertical swipe still scrolls the page and
     only a horizontal drag or a two-finger gesture goes to the model.
     Setting it here too would just be misleading dead weight, since it
     can never be the value that actually applies. */
}

.deskfab-viewer-canvas:active {
  cursor: grabbing;
}

.deskfab-viewer-stage.is-ready .deskfab-viewer-poster {
  opacity: 0;
}

.deskfab-viewer-stage.is-ready .deskfab-viewer-canvas {
  opacity: 1;
}

/* Reset-view control, top-right of the stage. Sits above the poster and
   canvas (both position: absolute with no z-index of their own) via an
   explicit z-index, regardless of the fact that the canvas is appended
   to the DOM after it. Hidden until the model is actually up: before
   that there is no view to reset. */
.deskfab-viewer-reset {
  position: absolute;
  top: var(--spacing-3);
  right: var(--spacing-3);
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  background: var(--bg-panel, rgba(15, 21, 17, 0.7));
  border: 1px solid var(--line-primary);
  color: var(--color-text-secondary);
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--transition-base), color var(--transition-base), border-color var(--transition-base);
}

.deskfab-viewer-reset:hover,
.deskfab-viewer-reset:focus-visible {
  color: var(--accent-primary);
  border-color: var(--accent-primary);
}

.deskfab-viewer-stage.is-ready .deskfab-viewer-reset {
  opacity: 1;
  pointer-events: auto;
}

/* Same instrumentation voice as the spec rows: states what the model
   is, and that it is one build rather than the build. */
.deskfab-viewer-caption {
  margin: var(--spacing-4) 0 0;
  font-size: var(--font-size-sm);
  line-height: 1.5;
  color: var(--color-text-secondary);
}

/* Subtler than the caption: a one-line reminder of the controls, not a
   second sentence of copy. Swapped for a touch-worded twin under
   (pointer: coarse) rather than sniffing the user agent, so a
   touch-and-mouse laptop still gets whichever one matches how the
   pointer that is actually present behaves. */
.deskfab-viewer-hint {
  margin: var(--spacing-2) 0 0;
  font-size: var(--font-size-xs);
  color: var(--color-text-secondary);
  opacity: 0.7;
}

.deskfab-viewer-hint-touch {
  display: none;
}

@media (pointer: coarse) {
  .deskfab-viewer-hint-mouse {
    display: none;
  }

  .deskfab-viewer-hint-touch {
    display: inline;
  }
}

@media (prefers-reduced-motion: reduce) {
  .deskfab-viewer-poster,
  .deskfab-viewer-canvas,
  .deskfab-viewer-reset {
    transition: none;
  }
}

/* --- spec panel internals ------------------------------------------ */

.deskfab-spec-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--spacing-4);
  padding-bottom: var(--spacing-4);
  margin-bottom: var(--spacing-6);
  border-bottom: 1px solid var(--line-primary);
}

.deskfab-spec-title {
  font-size: var(--font-size-xl);
  text-transform: uppercase;
  letter-spacing: var(--letter-spacing-wide);
  margin-bottom: 0;
}

.deskfab-spec-title-sm {
  font-size: var(--font-size-lg);
}

/* space-evenly distributes the slack a card inherits from the tallest
   card in its grid row — a shorter list would otherwise pool all of it
   in one lump at the bottom edge. It only ever spreads the difference
   between two cards standing side by side, which stays small. It is
   NOT a way to fill a card that is deliberately taller than its
   contents: that is a layout fault to fix in the grid, and was, when
   the machines card was cut from two rows to one. */
.deskfab-spec-list {
  list-style: none;
  margin: 0;
  padding: 0;
  flex-grow: 1;
  display: flex;
  flex-direction: column;
  justify-content: space-evenly;
}

.deskfab-spec-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--spacing-4);
  padding-bottom: var(--spacing-4);
  margin-bottom: var(--spacing-4);
  border-bottom: 1px solid var(--line-primary);
  min-width: 0;
}

.deskfab-spec-row:last-child {
  padding-bottom: 0;
  margin-bottom: 0;
  border-bottom: none;
}

.deskfab-spec-row > div {
  min-width: 0;
}

.deskfab-spec-list-tight .deskfab-spec-row {
  padding-bottom: var(--spacing-3);
  margin-bottom: var(--spacing-3);
}

/* Wide cards lay their rows out two-up instead of running long. */
.deskfab-spec-list-split {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--spacing-6);
  align-content: center;
}

.deskfab-spec-list-split .deskfab-spec-row {
  padding-bottom: 0;
  margin-bottom: 0;
  border-bottom: none;
}

.deskfab-spec-name {
  display: block;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-body-medium);
  letter-spacing: var(--letter-spacing-wide);
  text-transform: uppercase;
  color: var(--accent-primary);
  margin-bottom: var(--spacing-1);
  overflow-wrap: anywhere;
}

.deskfab-spec-desc {
  display: block;
  font-size: var(--font-size-base);
  line-height: 1.5;
  color: var(--color-text-secondary);
  overflow-wrap: anywhere;
}

.deskfab-spec-icon {
  flex-shrink: 0;
  font-size: var(--font-size-xl);
  line-height: 1.4;
  color: var(--accent-primary);
  opacity: 0.5;
}

/* ===================================================================
 * 9. Application tracks — Academia leads, Industry second
 * -------------------------------------------------------------------
 * Two tracks side by side, each item carrying an accent bar. Academia
 * runs on the teal data accent, Industry on the product orange.
 * =================================================================== */

.deskfab-tracks {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--spacing-12);
  align-items: start;
}

.deskfab-track {
  min-width: 0;
}

.deskfab-track-title {
  font-size: var(--font-size-2xl);
  text-transform: uppercase;
  letter-spacing: var(--letter-spacing-wide);
  padding-bottom: var(--spacing-4);
  margin-bottom: var(--spacing-8);
  color: var(--deskfab-track-accent, var(--accent-primary));
  border-bottom: 1px solid var(--deskfab-track-line, var(--line-primary));
}

.deskfab-track-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.deskfab-track-item {
  border-left: 4px solid var(--deskfab-track-line, var(--line-primary));
  padding-left: var(--spacing-6);
  margin-bottom: var(--spacing-8);
}

.deskfab-track-item:last-child {
  margin-bottom: 0;
}

.deskfab-track-item h4 {
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-body-medium);
  letter-spacing: var(--letter-spacing-wide);
  text-transform: uppercase;
  color: var(--color-text-primary);
  margin-bottom: var(--spacing-2);
}

.deskfab-track-item p {
  font-size: var(--font-size-base);
  line-height: 1.6;
  color: var(--color-text-secondary);
  margin-bottom: 0;
}

/* Industry — product accent. */
.deskfab-track-accent {
  --deskfab-track-accent: var(--accent-primary);
  --deskfab-track-line: var(--accent-primary-glow);
}

/* Academia — secondary data accent. */
.deskfab-track-data {
  --deskfab-track-accent: var(--df-data);
  --deskfab-track-line: var(--df-data-line);
}

/* ===================================================================
 * 10. Closing block
 * =================================================================== */

.deskfab-closing {
  max-width: 800px;
  margin: 0 auto;
  text-align: center;
}

.deskfab-closing p {
  font-size: var(--font-size-lg);
  line-height: 1.6;
  color: var(--color-text-secondary);
}

/* ===================================================================
 * 11. Responsive
 * =================================================================== */

@media (max-width: 1024px) {
  .deskfab-bento {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-auto-rows: auto;
  }

  .deskfab-bento-machines {
    grid-column: span 2;
    grid-row: auto;
  }

  /* Kinematics keeps its span and pairs with Drive: drive(1) then
     kinematics(1) fills the second row cleanly. */
  .deskfab-bento-kinematics {
    grid-column: auto;
  }

  /* Controllers gives up its tall column here and takes a full-width
     row instead, same reasoning as the viewer used to: at two columns
     a lone span-1 card with a multi-row span would leave a hole beside
     whichever row it lands in. */
  .deskfab-bento-controllers {
    grid-column: span 2;
    grid-row: auto;
  }

  .deskfab-bento-processors,
  .deskfab-bento-peripherals {
    grid-column: span 2;
  }
}

@media (max-width: 768px) {
  /* The status strip stacks to three tall rows below 768px; the extra
     bottom padding keeps the glass panel from sitting on top of it. */
  body.theme-deskfab.page-interior .hero {
    min-height: 80vh;
    padding: var(--spacing-20) var(--container-padding) 11rem;
  }

  .deskfab-hero-panel {
    padding: var(--spacing-8) var(--spacing-5);
  }

  /* viewBox 1440x800 with xMidYMid slice crops hard on a narrow, tall
     hero — at 375px only ~430 user units stay visible. Overscanning the
     SVG box horizontally (the parent clips it) brings the gantries and
     the printer node back into frame. No page overflow: .deskfab-scene
     and .hero both clip. */
  .deskfab-scene-svg {
    position: absolute;
    top: 0;
    left: 50%;
    width: 280%;
    transform: translateX(-50%);
  }

  .deskfab-bento {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--spacing-5);
  }

  .deskfab-bento-machines,
  .deskfab-bento-kinematics,
  .deskfab-bento-controllers,
  .deskfab-bento-processors,
  .deskfab-bento-peripherals {
    grid-column: auto;
    grid-row: auto;
  }

  .deskfab-spec-list-split {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--spacing-4);
  }

  .deskfab-tracks {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--spacing-12);
  }
}

@media (max-width: 480px) {
  .deskfab-spec-head {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--spacing-2);
  }

  .deskfab-track-item {
    padding-left: var(--spacing-4);
  }
}
