/* ────────────────────────────────────────────────────────────
   Section 3 - The Solve (v3 - exploded drill, PHASE 1: desktop)
   ----------------------------------------------------------------
   Replaces the single flat schematic image + SVG leader lines +
   off-image markers with an EXPLODED DRILL: seven separate
   transparent component PNGs stacked on the vertical axis, each
   with a number marker on the part itself and a 45deg base plane
   beneath it.

   What changed from v2:
     - Schematic component fully rebuilt (Drill component below).
       The flat image, the <svg> leader-line layer and the
       off-image marker layer are all gone.
     - Markers are number-only, ON the component (no rings/pulse,
       no leader lines).
     - Three interaction states per component: idle (dimmed),
       hover (clickable mid-state), active (full).
     - A 45deg rhombus "base plane" sits under each component;
       it brightens (opacity only, no colour) when its component
       is active.
     - Staggered entrance: one IntersectionObserver trigger, then
       the seven parts + planes settle in (slide-up + fade) on
       their own timeline.

   What is unchanged:
     - section-solve-data.jsx (decks, redaction flags, ROP row
       already removed) - this file just consumes it.
     - The left SubsystemPanel: it now renders `deck` (always
       visible, under the title) instead of the old `one` line.
     - The footer, the CTA, redaction rendering, reduced motion.
     - activeId remains the single source of truth; Esc -> "01".

   PHASE 2 (separate brief) rebuilds the mobile layout into the
   horizontal lying-down drill. In THIS phase, below 960px the
   drill simply stacks above the panel at a reduced size - safe,
   not final.
   ──────────────────────────────────────────────────────────── */

function SectionSolve() {
  const subs = window.SOLVE_SUBS;

  // activeId is the single source of truth: marker state, panel
  // expansion, the amber rail, and the active base plane.
  const [activeId, setActiveId] = React.useState("01");
  const handleSelect = React.useCallback((id) => {
    // Toggle: clicking the already-active entry closes it, so the
    // accordion can return to a fully-collapsed state. Default on
    // mount is still "01"; Esc still resets to "01".
    setActiveId((current) => (current === id ? null : id));
  }, []);

  // hoveredId drives the panel-entry rail preview on hover.
  const [hoveredId, setHoveredId] = React.useState(null);

  // `revealed` gates the staged in-view entrance.
  const [revealed, setRevealed] = React.useState(false);
  const sectionRef = React.useRef(null);

  React.useEffect(() => {
    if (!sectionRef.current) return;
    if (revealed) return;
    const io = new IntersectionObserver(
      (entries) => {
        entries.forEach((e) => {
          if (e.isIntersecting) {
            setRevealed(true);
            io.disconnect();
          }
        });
      },
      { threshold: 0.2 }
    );
    io.observe(sectionRef.current);
    return () => io.disconnect();
  }, [revealed]);

  // Esc returns active to "01" - there is no deselected state.
  React.useEffect(() => {
    const onKey = (e) => {
      if (e.key === "Escape") setActiveId("01");
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, []);

  return (
    <SectionFrame index="03" plate="III." dispatch="Encapsulated laser system · § 03" bg="#0D0F12">
      {/* Drafting-paper grid overlay - barely perceptible. */}
      <div aria-hidden="true" style={{
        position: "absolute", inset: 0,
        backgroundImage: `
          linear-gradient(to right, rgba(237,234,226,0.06) 1px, transparent 1px),
          linear-gradient(to bottom, rgba(237,234,226,0.06) 1px, transparent 1px)
        `,
        backgroundSize: "48px 48px",
        maskImage: "radial-gradient(ellipse at center, rgba(0,0,0,0.9) 0%, rgba(0,0,0,0.5) 60%, rgba(0,0,0,0.2) 100%)",
        WebkitMaskImage: "radial-gradient(ellipse at center, rgba(0,0,0,0.9) 0%, rgba(0,0,0,0.5) 60%, rgba(0,0,0,0.2) 100%)",
        pointerEvents: "none",
        zIndex: 0
      }} />

      {/* Ambient particle field - quieter execution than Section 02
          (Domain expansion). Same component, lower density and
          default speedScale so the field stays subliminal over the
          drafting-paper grid. zIndex 0 keeps it on the same
          background tier as the grid; the content wrapper below
          (zIndex 1) stays above it. */}
      {typeof PtParticles !== "undefined" &&
        <PtParticles style={{ zIndex: 0 }} density={0.0024} />
      }

      <div ref={sectionRef} style={{ position: "relative", zIndex: 1 }}>
        <SectionEyebrow index="03" label="The architecture" />
        <SectionHeading size="lg">
          The technology already exists.<br />
          Nobody has assembled it this way<span style={{ color: "#D4A24C" }}>.</span>
        </SectionHeading>
        <SectionDeck max={760}>
          Our team comes from SpaceX, Blue Origin, and NASA - environments where
          encapsulation isn't a design choice, it's a survival requirement. The
          Axion drill stack closes every major subsystem inside established
          oilfield, HPHT, and industrial-laser envelopes.
        </SectionDeck>

        {/* ── Two-column layout: 60% panel + 40% drill ─────────────── */}
        <div className="solve-grid" style={{
          marginTop: 56,
          display: "grid",
          gridTemplateColumns: "minmax(0, 60fr) minmax(0, 40fr)",
          gap: 64,
          alignItems: "start"
        }}>
          {/* Mobile drill - horizontal row, hidden on desktop via
             CSS. Rendered first so on single-column mobile grid
             it lands above the accordion. */}
          <DrillMobile
            subs={subs}
            activeId={activeId}
            onSelect={handleSelect}
            revealed={revealed} />

          <SubsystemPanel
            subs={subs}
            activeId={activeId}
            hoveredId={hoveredId}
            onSelect={handleSelect}
            revealed={revealed} />

          <Drill
            subs={subs}
            activeId={activeId}
            onSelect={handleSelect}
            onHover={setHoveredId}
            revealed={revealed} />
        </div>

        {/* Section footer - closing line + CTA. */}
        <div className="solve-footer" style={{
          marginTop: 72,
          paddingTop: 28,
          display: "flex",
          alignItems: "baseline",
          gap: 24,
          flexWrap: "wrap"
        }}>
          <p style={{
            fontFamily: "var(--font-body)",
            fontSize: 16,
            lineHeight: 1.5,
            color: "#EDEAE2",
            margin: 0,
            flex: "1 1 420px",
            textWrap: "pretty"
          }}>
            <span style={{
              fontFamily: "var(--font-display)",
              fontWeight: 500,
              color: "#EDEAE2"
            }}>Every major subsystem closes.</span>{" "}
            <span style={{ color: "rgba(237,234,226,0.70)", fontSize: "15px" }}>
              The full feasibility analysis is available on request.
            </span>
          </p>
          <a
            href="mailto:investors@axionldt.com?subject=Feasibility%20Study%20request"
            className="solve-cta"
            style={{
              display: "inline-flex",
              alignItems: "center",
              padding: "13px 18px",
              borderRadius: 4,
              background: "transparent",
              border: "1px solid rgba(237,234,226,0.28)",
              color: "#EDEAE2",
              fontFamily: "var(--font-display)",
              fontWeight: 500,
              fontSize: 14,
              lineHeight: 1,
              letterSpacing: "0.01em",
              textDecoration: "none",
              transition: "background 120ms ease, border-color 120ms ease"
            }}>
            Request feasibility study
          </a>
        </div>
      </div>

      <style>{SOLVE_CSS}</style>
    </SectionFrame>);
}

/* ────────────────────────────────────────────────────────────
   SubsystemPanel - left column, 60% width.
   All 7 entries rendered; exactly one is active (full body +
   spec visible). Collapsed entries show number + title + deck.
   The active entry has a 2px amber rail; hovered inactive
   entries get a 50%-opacity preview of it.

   Change from v2: the always-visible secondary line is now
   `s.deck` (the headline deck) instead of `s.one`. A chevron
   sits at the end of each head row and rotates when active.
   ──────────────────────────────────────────────────────────── */
function SubsystemPanel({ subs, activeId, hoveredId, onSelect, revealed }) {
  return (
    <div className={`solve-panel ${revealed ? "is-revealed" : ""}`}>
      <div className="solve-panel-eyebrow">
        AX-LDT-PROTO // SUBSYSTEM ARCHITECTURE
      </div>
      <ul className="solve-panel-list" role="list">
        {subs.map((s, i) => {
          const isActive = s.id === activeId;
          const isHovered = s.id === hoveredId;
          return (
            <li
              key={s.id}
              className={
              "solve-entry" + (
              isActive ? " is-active" : "") + (
              isHovered ? " is-hovered" : "")
              }
              style={{ "--reveal-delay": `${1300 + i * (window.ax ? window.ax.stagger("tight") : 40)}ms` }}>
              <button
                type="button"
                className="solve-entry-head"
                aria-expanded={isActive ? "true" : "false"}
                aria-current={isActive ? "true" : undefined}
                onMouseDown={(e) => e.preventDefault()}
                onClick={(e) => {
                  if (e.currentTarget.focus) e.currentTarget.focus({ preventScroll: true });
                  onSelect(s.id);
                }}>
                <span className="solve-entry-num">{s.id}</span>
                <span className="solve-entry-title">{s.title}</span>
                <span className="solve-entry-chevron" aria-hidden="true">&#8250;</span>
                <span className="solve-entry-deck">{s.deck}</span>
              </button>

              {/* Expandable body - grid-rows 0fr -> 1fr. */}
              <div className="solve-entry-expand" aria-hidden={!isActive}>
                <div className="solve-entry-expand-inner">
                  <p className="solve-entry-body">{s.body}</p>
                  <dl className="solve-entry-spec">
                    {s.spec.map(([k, v, flag]) =>
                    <div key={k} className="solve-entry-spec-row">
                        <dt>{k}</dt>
                        {flag === "redacted" ?
                        <dd className="solve-entry-spec-redacted" aria-label="redacted">
                            <span className="solve-redact-bar" aria-hidden="true" />
                          </dd> :
                        <dd>{v}</dd>}
                      </div>
                    )}
                  </dl>
                </div>
              </div>
            </li>);
        })}
      </ul>
    </div>);
}

/* ────────────────────────────────────────────────────────────
   Drill - right column, 40% width. (Replaces Schematic.)
   Seven exploded transparent component PNGs stacked on the
   vertical axis. Each component:
     - is sized by master-measured proportions (PART_GEO below),
       relative to the column width, so it is independent of the
       source files' export resolution;
     - carries a number marker positioned on the part;
     - has a 45deg rhombus base plane beneath it;
     - cycles idle / hover / active state.
   Clicking a component (or hovering it) drives activeId, same as
   the panel - both views read the one source of truth.

   Sticky on the right so the drill stays in view while the long
   panel scrolls. Staggered entrance keyed off `revealed`.
   ──────────────────────────────────────────────────────────── */

/* Master-measured geometry. Derived directly from the source
   PNG pixel dimensions so the on-screen proportions match the
   reference render exactly.
   - ar    = native aspect ratio (width / height). The part box
             is sized BY HEIGHT in the new layout, so the width
             follows from this and the relative widths fall out
             correctly (Sapphire stays thin, Laser Array stays
             tall and wide).
   - hPct  = native height as a fraction of the tallest part
             (04, 294px). Drives the row's flex share so the 7
             rows divide the drill height proportionally.
   Order is drill order, top down. */
const PART_GEO = {
  "01": { file: "Umbilical-Cable.png",         ar: 246 / 246, hPct: 246 / 294 },
  "02": { file: "Power-Sub.png",               ar: 254 / 199, hPct: 199 / 294 },
  "03": { file: "Thermal-Management.png",      ar: 257 / 201, hPct: 201 / 294 },
  "04": { file: "Laser-Array.png",             ar: 267 / 294, hPct: 294 / 294 },
  "05": { file: "Debris-Management.png",       ar: 257 / 157, hPct: 157 / 294 },
  "06": { file: "Sapphire-Optical-Window.png", ar: 257 /  84, hPct:  84 / 294 },
  "07": { file: "Laser-Emission.png",          ar: 243 / 105, hPct: 105 / 294 }
};

function Drill({ subs, activeId, onSelect, onHover, revealed }) {
  const stackRef = React.useRef(null);

  /* Lock the drill stack height to the panel's MAX possible
     height - i.e. what the panel would be if its tallest entry
     were the active one. We compute that by reading each
     entry's hidden expand-inner.scrollHeight and adjusting
     the current panel height for the swap.

     This is set once on mount (with delayed re-apply for fonts)
     and never grows on accordion changes - so clicking entries
     no longer makes the whole drill rescale. It only re-measures
     on window resize, which may legitimately change wrapping. */
  React.useEffect(() => {
    const stack = stackRef.current;
    if (!stack) return;
    const panel = document.querySelector(".solve-panel");
    if (!panel) return;

    let appliedH = 0;

    function measure() {
      const entries = Array.prototype.slice.call(
        panel.querySelectorAll(".solve-entry")
      );
      if (!entries.length) return 0;
      const innerH = entries.map((e) => {
        const inner = e.querySelector(".solve-entry-expand-inner");
        return inner ? inner.scrollHeight : 0;
      });
      const maxInner = Math.max.apply(null, innerH);
      const active = panel.querySelector(".solve-entry.is-active");
      const activeIdx = active ? entries.indexOf(active) : -1;
      const activeInner = activeIdx >= 0 ? innerH[activeIdx] : 0;
      const panelH = panel.getBoundingClientRect().height;
      return panelH - activeInner + maxInner;
    }

    function apply() {
      const target = measure();
      if (target > appliedH) {
        appliedH = target;
        stack.style.setProperty("--drill-h", `${Math.round(target)}px`);
      }
    }

    apply();
    /* Late re-apply: fonts/images may shift wrapping after mount. */
    const t1 = setTimeout(apply, 200);
    const t2 = setTimeout(apply, 800);

    const onResize = () => {
      appliedH = 0;
      apply();
    };
    window.addEventListener("resize", onResize);

    return () => {
      clearTimeout(t1);
      clearTimeout(t2);
      window.removeEventListener("resize", onResize);
    };
  }, []);

  return (
    <div className={`solve-drill ${revealed ? "is-revealed" : ""}`}>
      <div className="solve-drill-stack" ref={stackRef}>
        {subs.map((s, i) => {
          const geo = PART_GEO[s.id] || { file: "", ar: 1, hPct: 1 };
          const isActive = s.id === activeId;
          /* z-index ladders down the stack: row 0 highest, row 6
             lowest - so a row's plane sits behind every component
             below it (the drill threads through the planes). */
          const z = (subs.length - i) * 10;
          return (
            <div
              className="solve-row"
              key={s.id}
              style={{
                zIndex: z,
                "--share": geo.hPct,
                "--reveal-delay": `${900 + i * (window.ax ? window.ax.stagger("loose") : 90)}ms`
              }}>
              {/* part wrapper - sized by its native aspect ratio.
                  the plane is positioned against THIS box so it
                  tracks the component. */}
              <button
                type="button"
                className={"solve-part" + (isActive ? " is-active" : "")}
                aria-label={`${s.id} - ${s.title}. Click to view details.`}
                aria-pressed={isActive}
                onMouseDown={(e) => e.preventDefault()}
                onClick={(e) => {
                  if (e.currentTarget.focus) e.currentTarget.focus({ preventScroll: true });
                  onSelect(s.id);
                }}
                onMouseEnter={() => onHover(s.id)}
                onMouseLeave={() => onHover(null)}
                onFocus={() => onHover(s.id)}
                onBlur={() => onHover(null)}
                style={{ "--ar": geo.ar }}>
                {/* base plane - sits at the component's base */}
                <span className="solve-part-plane" aria-hidden="true" />
                <img
                  src={`assets/imagery/${geo.file}`}
                  alt=""
                  className="solve-part-img"
                  draggable={false} />
                <span className="solve-part-num">{s.id}</span>
              </button>
            </div>);
        })}
      </div>
    </div>);
}

/* ────────────────────────────────────────────────────────────
   DrillMobile - PHASE 2. Horizontal lying-down drill for mobile.
   The seven components sit in a single left-to-right row using
   the -Mobile.png assets (rotated masters). Layout is a CSS grid
   whose column track sizes are the native pixel widths as `fr`
   units, so the row fits the viewport at any width while keeping
   the parts' relative lengths exact. Heights are intrinsic (each
   image's own aspect ratio); align-items: center seats them on
   the drill's horizontal axis.

   Independent of the desktop drill: no planes, no sticky, no
   height-pegging. Shares activeId via onSelect so taps stay in
   sync with the accordion.
   ──────────────────────────────────────────────────────────── */

/* Native pixel dims of the -Mobile.png renders. Widths drive
   the grid track sizes (`fr`); the aspect ratio drives each
   part's intrinsic height. */
const PART_GEO_MOBILE = {
  "01": { file: "Umbilical-Cable-Mobile.png",         w: 246, h: 246 },
  "02": { file: "Power-Sub-Mobile.png",               w: 199, h: 254 },
  "03": { file: "Thermal-Management-Mobile.png",      w: 201, h: 257 },
  "04": { file: "Laser-Array-Mobile.png",             w: 294, h: 267 },
  "05": { file: "Debris-Management-Mobile.png",       w: 157, h: 257 },
  "06": { file: "Sapphire-Optical-Window-Mobile.png", w:  84, h: 257 },
  "07": { file: "Laser-Emission-Mobile.png",          w: 105, h: 243 }
};

/* PHASE 2 rebuild: the mobile drill is now a horizontal scrollable
   band, not a fit-to-width grid.
   - Components render at a small base scale (MOBILE_SCALE) so 3-4
     fit on a 390px viewport at once; tapping a component pops it
     up to artifact-scale via a transform on the active button -
     same idea as the desktop active state, just a bigger lift
     because the resting size is smaller here.
   - The row scrolls horizontally with mandatory x-snap and a
     hidden scrollbar; right/left edge fades carry the affordance
     instead.
   - The container also hosts the eyebrow spec line above the
     band - mobile-only, hidden on desktop where this component
     itself is display:none. */
const MOBILE_SCALE = 0.5;

function DrillMobile({ subs, activeId, onSelect, revealed }) {
  const rowRef = React.useRef(null);
  const wasScrolling = React.useRef(false);
  const wasScrollingTimer = React.useRef(null);
  const didMount = React.useRef(false);
  const [scrolled, setScrolled] = React.useState(false);

  /* Track horizontal scroll: drives the left-edge fade (visible
     after any scroll right) and the click-suppression below.
     Idempotent - setScrolled bails out when the value matches. */
  React.useEffect(() => {
    const el = rowRef.current;
    if (!el) return;
    const onScroll = () => {
      setScrolled(el.scrollLeft > 4);
      /* Defense-in-depth: a touch swipe occasionally fires click
         on the button it ends on. Mark the row as "scrolling" for
         150ms so any click that lands during/just-after a swipe
         doesn't accidentally activate a subsystem. */
      wasScrolling.current = true;
      if (wasScrollingTimer.current) clearTimeout(wasScrollingTimer.current);
      wasScrollingTimer.current = setTimeout(() => {
        wasScrolling.current = false;
      }, 150);
    };
    el.addEventListener("scroll", onScroll, { passive: true });
    return () => {
      el.removeEventListener("scroll", onScroll);
      if (wasScrollingTimer.current) clearTimeout(wasScrollingTimer.current);
    };
  }, []);

  /* When the active subsystem changes (from any source - drill
     tap, accordion click, Esc reset), bring the active component
     to the centre of the row with a custom-eased horizontal
     scroll. We avoid native scrollTo({behavior: 'smooth'}) so we
     can control the easing curve and duration directly. Snap is
     temporarily disabled during the animation so intermediate
     scrollLeft frames don't get yanked to the nearest snap point
     (mandatory snap + per-frame scrollLeft writes can fight each
     other). Skip the very first mount so 01 stays at the left
     on initial paint. */
  React.useEffect(() => {
    if (!didMount.current) {
      didMount.current = true;
      return;
    }
    const row = rowRef.current;
    if (!row) return;
    const activeBtn = row.querySelector(".solve-part-m.is-active");
    if (!activeBtn) return;
    const rowRect = row.getBoundingClientRect();
    const btnRect = activeBtn.getBoundingClientRect();
    /* Convert the button's centre from viewport coords to the
       row's scroll-coord space, then offset so it lands at the
       row's visible centre. The browser clamps negative /
       overshoot scrollLeft to the valid range automatically. */
    const btnCenterInRow =
      (btnRect.left - rowRect.left) + row.scrollLeft + btnRect.width / 2;
    const target = btnCenterInRow - rowRect.width / 2;
    const start = row.scrollLeft;
    const delta = target - start;
    if (Math.abs(delta) < 1) return;

    /* Respect reduced motion - jump, no animation. */
    const prm = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)");
    if (prm && prm.matches) {
      row.scrollLeft = target;
      return;
    }

    /* Custom rAF animation. ease-out-expo matches the brand's
       deliberate-but-not-theatrical motion language (same curve
       used by the section reveal). 520ms is slow enough that the
       easing reads, short enough that it doesn't feel sluggish. */
    const duration = 520;
    const easeOutExpo = (t) => t >= 1 ? 1 : 1 - Math.pow(2, -10 * t);
    const t0 = performance.now();

    /* Pause snap for the duration so per-frame writes pass
       through unimpeded; restore the previous inline value
       (usually empty string) when done. */
    const prevSnap = row.style.scrollSnapType;
    row.style.scrollSnapType = "none";

    let raf = 0;
    const step = (now) => {
      const t = Math.min(1, (now - t0) / duration);
      row.scrollLeft = start + delta * easeOutExpo(t);
      if (t < 1) {
        raf = requestAnimationFrame(step);
      } else {
        row.style.scrollSnapType = prevSnap;
      }
    };
    raf = requestAnimationFrame(step);

    return () => {
      cancelAnimationFrame(raf);
      row.style.scrollSnapType = prevSnap;
    };
  }, [activeId]);

  return (
    <div className={`solve-drill-mobile ${revealed ? "is-revealed" : ""}`}>
      {/* Eyebrow - mobile-only spec line introducing the drill.
          The desktop instance lives inside SubsystemPanel and is
          hidden on mobile via CSS so there's exactly one on
          screen at any breakpoint. */}
      <div className="solve-drill-mobile-eyebrow">
        AX-LDT-PROTO // SUBSYSTEM ARCHITECTURE
      </div>

      {/* Band - full-bleed wrapper holding the scroll row and the
          two edge-fade overlays. Bleeds to viewport edge via
          negative margins so the right fade reads against the
          section bg, not a padded edge. */}
      <div className={"solve-drill-mobile-band" + (scrolled ? " is-scrolled" : "")}>
        <div ref={rowRef} className="solve-drill-mobile-row">
          {subs.map((s, i) => {
            const geo = PART_GEO_MOBILE[s.id] || { file: "", w: 1, h: 1 };
            const isActive = s.id === activeId;
            return (
              <button
                key={s.id}
                type="button"
                className={"solve-part-m" + (isActive ? " is-active" : "")}
                aria-label={`${s.id} - ${s.title}. Tap to view details.`}
                aria-pressed={isActive}
                onMouseDown={(e) => e.preventDefault()}
                onClick={(e) => {
                  if (wasScrolling.current) return;
                  if (e.currentTarget.focus) e.currentTarget.focus({ preventScroll: true });
                  onSelect(s.id);
                }}
                style={{
                  "--m-w": `${geo.w}px`,
                  "--ar": `${geo.w} / ${geo.h}`,
                  "--reveal-delay": `${600 + i * 60}ms`
                }}>
                <img
                  src={`assets/imagery/${geo.file}`}
                  alt=""
                  className="solve-part-m-img"
                  draggable={false} />
                <span className="solve-part-m-num">{s.id}</span>
              </button>);
          })}
        </div>
      </div>

    </div>);
}

/* ────────────────────────────────────────────────────────────
   CSS. Namespaced under `.solve-`. Panel/entry/redaction CSS is
   carried from v2 unchanged except the renamed secondary line
   (.solve-entry-deck) and the new chevron. The schematic CSS is
   replaced by the drill CSS.
   ──────────────────────────────────────────────────────────── */
const SOLVE_CSS = `
/* ── Reveal choreography ─────────────────────────────────── */
@keyframes solvePanelIn {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes solvePartIn {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ── Panel ───────────────────────────────────────────────── */
.solve-panel {
  opacity: 0;
}
.solve-panel.is-revealed {
  animation: solvePanelIn var(--motion-base) cubic-bezier(0.16, 1, 0.3, 1) 1300ms backwards;
  opacity: 1;
}
.solve-panel-eyebrow {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(212,162,76,0.85);
  margin-bottom: 24px;
}
.solve-panel-list {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid rgba(237,234,226,0.10);
}

/* ── Entry ───────────────────────────────────────────────── */
.solve-entry {
  position: relative;
  border-bottom: 1px solid rgba(237,234,226,0.10);
}
.solve-entry::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 2px;
  background: #D4A24C;
  transform: scaleY(0);
  transform-origin: top center;
  opacity: 0;
  transition: transform var(--motion-base) cubic-bezier(0.16, 1, 0.3, 1),
              opacity var(--motion-base) cubic-bezier(0.16, 1, 0.3, 1);
}
.solve-entry.is-hovered::before {
  transform: scaleY(1);
  opacity: 0.5;
}
.solve-entry.is-active::before {
  transform: scaleY(1);
  opacity: 1;
}
.solve-entry-head {
  display: grid;
  grid-template-columns: 44px 1fr auto;
  grid-template-rows: auto auto;
  column-gap: 16px;
  row-gap: 6px;
  align-items: center;
  width: 100%;
  padding: 22px 16px 22px 18px;
  background: transparent;
  border: 0;
  text-align: left;
  cursor: pointer;
  color: inherit;
  font: inherit;
  opacity: 0.6;
  transition: opacity var(--motion-fast) cubic-bezier(0.25, 1, 0.5, 1),
              background var(--motion-fast) cubic-bezier(0.25, 1, 0.5, 1);
}
.solve-entry.is-hovered .solve-entry-head { opacity: 0.85; }
.solve-entry.is-active .solve-entry-head  { opacity: 1; }
.solve-entry-head:hover { background: rgba(237,234,226,0.02); }
.solve-entry-head:focus { outline: none; }
.solve-entry-head:focus-visible { outline: none; }
.solve-entry-num {
  grid-row: 1 / span 2;
  align-self: start;
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.14em;
  color: rgba(212,162,76,0.85);
  padding-top: 4px;
}
.solve-entry-title {
  grid-column: 2;
  font-family: var(--font-display);
  font-size: 18px;
  font-weight: 500;
  letter-spacing: -0.005em;
  color: #EDEAE2;
}
/* chevron - rotates from > (closed) to v (open) when active. */
.solve-entry-chevron {
  grid-column: 3;
  grid-row: 1;
  align-self: center;
  font-family: var(--font-display);
  font-size: 18px;
  line-height: 1;
  color: rgba(237,234,226,0.45);
  transform: rotate(0deg);
  transition: transform var(--motion-base) cubic-bezier(0.16, 1, 0.3, 1),
              color var(--motion-base) cubic-bezier(0.16, 1, 0.3, 1);
}
.solve-entry.is-active .solve-entry-chevron {
  transform: rotate(90deg);
  color: #D4A24C;
}
/* the deck - always-visible headline line under the title. */
.solve-entry-deck {
  grid-column: 2 / span 2;
  grid-row: 2;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 16px;
  line-height: 1.4;
  color: var(--c-ash);
  transition: color var(--motion-base) cubic-bezier(0.16, 1, 0.3, 1);
}
.solve-entry.is-active .solve-entry-deck { color: var(--c-ash); }

/* ── Expand region ───────────────────────────────────────── */
.solve-entry-expand {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--motion-base) cubic-bezier(0.25, 1, 0.5, 1);
}
.solve-entry.is-active .solve-entry-expand {
  grid-template-rows: 1fr;
  transition: grid-template-rows var(--motion-base) cubic-bezier(0.16, 1, 0.3, 1) 150ms;
}
.solve-entry-expand-inner {
  overflow: hidden;
  opacity: 0;
  transition: opacity var(--motion-base) cubic-bezier(0.25, 1, 0.5, 1);
}
.solve-entry.is-active .solve-entry-expand-inner {
  opacity: 1;
  transition: opacity var(--motion-base) cubic-bezier(0.16, 1, 0.3, 1) 200ms;
}
.solve-entry-body {
  margin: 0 16px 18px 78px;
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.55;
  color: var(--c-ash);
  text-wrap: pretty;
}
.solve-entry-spec {
  margin: 0 16px 24px 78px;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 8px;
  border-top: 1px solid rgba(237,234,226,0.10);
  padding-top: 14px;
}
.solve-entry-spec-row {
  display: grid;
  grid-template-columns: 180px 1fr;
  column-gap: 16px;
  font-family: var(--font-mono);
  font-size: 11px;
  line-height: 1.5;
}
.solve-entry-spec-row dt {
  color: rgba(212,162,76,0.85);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  margin: 0;
}
.solve-entry-spec-row dd {
  color: #EDEAE2;
  letter-spacing: 0.04em;
  margin: 0;
}

/* ── Redacted spec value ─────────────────────────────────── */
.solve-entry-spec-redacted {
  display: flex;
  align-items: center;
  min-height: 1.5em;
}
.solve-redact-bar {
  display: inline-block;
  width: 84px;
  max-width: 100%;
  height: 12px;
  border-radius: 1px;
  background-color: #1A1D21;
  background-image: repeating-linear-gradient(
    135deg,
    rgba(237, 234, 226, 0.06) 0 2px,
    transparent 2px 5px
  );
  border: 1px solid rgba(237, 234, 226, 0.10);
}

/* ── Drill (right column) ────────────────────────────────── */
.solve-drill {
  position: sticky;
  top: 96px;
  align-self: start;
}
/* Height-driven stack. --drill-h is set in JS to match the left
   subsystem panel height; the 7 rows then divide that height by
   their native --share, and each part fills its row's height -
   width follows from aspect-ratio. So the whole drill fits the
   same vertical band as the copy on the left. */
.solve-drill-stack {
  position: relative;
  height: var(--drill-h, 720px);
  width: 100%;
  max-width: 220px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 1px;
  align-items: center;
  justify-content: center;
}

/* Soft-fade the very top of the umbilical image so the cables
   don't hard-cut at the row edge. Scoped to row 01's image only
   so it doesn't clip the base planes (which extend beyond the
   part box) or anything else. */
.solve-row:first-child .solve-part-img {
  -webkit-mask-image: linear-gradient(to bottom,
    transparent 0%,
    rgba(0,0,0,0.35) 8%,
    rgba(0,0,0,0.85) 16%,
    #000 24%,
    #000 100%);
  mask-image: linear-gradient(to bottom,
    transparent 0%,
    rgba(0,0,0,0.35) 8%,
    rgba(0,0,0,0.85) 16%,
    #000 24%,
    #000 100%);
}

/* Each row gets a flex share = native height of its component
   (relative to the tallest, 04). min-height: 0 lets the row
   actually shrink below its content's intrinsic height. */
.solve-row {
  position: relative;
  flex: var(--share, 1) 1 0;
  min-height: 0;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* ── A component ─────────────────────────────────────────── */
/* Part is height-first: fills its row's height, width follows
   from aspect-ratio. max-width: 100% guards the narrow stack
   case (e.g. Sapphire/Emission, ar ~3:1, would otherwise
   exceed the stack width if --drill-h were very large). */
.solve-part {
  position: relative;
  z-index: 2;
  height: 100%;
  width: auto;
  aspect-ratio: var(--ar, 1);
  max-width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  background: transparent;
  padding: 0;
  margin: 0;
  cursor: pointer;
  filter: saturate(0.42) brightness(0.5);
  opacity: 0;
  transform: translateY(16px);
  transition: filter var(--motion-base) cubic-bezier(0.4, 0, 0.2, 1),
              transform var(--motion-base) cubic-bezier(0.4, 0, 0.2, 1);
}
.solve-drill.is-revealed .solve-part {
  animation: solvePartIn var(--motion-base) cubic-bezier(0.16, 1, 0.3, 1) var(--reveal-delay, 0ms) backwards;
  opacity: 1;
  transform: translateY(0);
}
.solve-part:hover,
.solve-part:focus-visible {
  outline: none;
  filter: saturate(0.72) brightness(0.78);
}
.solve-part.is-active {
  filter: saturate(1.05) brightness(1.04)
          drop-shadow(0 0 9px rgba(212,162,76,0.18));
}
.solve-drill.is-revealed .solve-part.is-active {
  transform: scale(1.015);
}
.solve-part-img {
  position: relative;
  z-index: 2;
  display: block;
  width: 100%;
  height: 100%;
  /* object-fit so the source PNG (which has its own aspect) is
     scaled to fit the part box without distortion. */
  object-fit: contain;
  object-position: center;
  user-select: none;
}
.solve-part-num {
  position: absolute;
  left: -22px;
  top: 50%;
  transform: translateY(-50%);
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.06em;
  color: rgba(168,168,162,0.9);
  z-index: 3;
  transition: color var(--motion-base) cubic-bezier(0.16, 1, 0.3, 1);
}
.solve-part:hover .solve-part-num,
.solve-part:focus-visible .solve-part-num { color: #A8A8A2; }
.solve-part.is-active .solve-part-num { color: #D4A24C; }

/* 45deg base plane - positioned against the PART box, anchored
   to the part's lower area so it reads as the component's base.
   width is a % of the part, so it tracks the component size.
   z-index 1 = behind its own part; the row z-index ladder keeps
   it behind every component lower in the stack. */
.solve-part-plane {
  position: absolute;
  left: 50%;
  bottom: 14%;
  z-index: 1;
  width: 122%;
  aspect-ratio: 1 / 1;
  background: rgba(237,234,226,0.022);
  border: 1px solid rgba(237,234,226,0.09);
  transform-origin: 50% 50%;
  transform: translate(-50%, 50%) rotateX(74deg) rotateZ(45deg) scale(0.55);
  opacity: 0;
  pointer-events: none;
  transition: transform var(--motion-base) cubic-bezier(0.22, 1, 0.36, 1),
              opacity var(--motion-base) cubic-bezier(0.22, 1, 0.36, 1),
              background var(--motion-base) ease,
              border-color var(--motion-base) ease;
}
.solve-drill.is-revealed .solve-part-plane {
  transform: translate(-50%, 50%) rotateX(74deg) rotateZ(45deg) scale(1);
  opacity: 0.9;
}
.solve-row:has(.solve-part.is-active) .solve-part-plane {
  background: rgba(237,234,226,0.07);
  border-color: rgba(237,234,226,0.20);
}

/* -- Footer CTA hover (ghost variant) ---------------------- */
.solve-cta:hover {
  background: rgba(237,234,226,0.06) !important;
  border-color: rgba(237,234,226,0.45) !important;
}

/* ── Reduced motion ──────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .solve-panel.is-revealed,
  .solve-drill.is-revealed .solve-part {
    animation: none;
  }
  .solve-part {
    opacity: 1;
    transform: none;
  }
  .solve-drill.is-revealed .solve-part-plane {
    transition: opacity 150ms linear;
  }
  .solve-entry-expand,
  .solve-entry.is-active .solve-entry-expand,
  .solve-entry-expand-inner,
  .solve-entry.is-active .solve-entry-expand-inner {
    transition: opacity 150ms linear;
  }
}

/* ── Responsive ──────────────────────────────────────────────
   Below 960px the desktop vertical drill is hidden and replaced
   by the horizontal mobile drill (Phase 2 rebuild): an artifact-
   scale scrollable band with eyebrow above and plate caption
   below. */
.solve-drill-mobile { display: none; }

@media (max-width: 960px) {
  .solve-grid {
    grid-template-columns: 1fr !important;
    gap: 32px !important;
  }
  /* Desktop vertical drill hidden entirely on mobile. */
  .solve-drill { display: none; }
  /* The eyebrow lives inside DrillMobile on mobile (above the
     band); hide the desktop instance in the panel so it appears
     exactly once. */
  .solve-panel > .solve-panel-eyebrow { display: none; }

  /* ── DrillMobile container ─────────────────────────────── */
  .solve-drill-mobile {
    display: block;
    width: 100%;
    opacity: 0;
    transition: opacity var(--motion-base) ease;
  }
  .solve-drill-mobile.is-revealed { opacity: 1; }

  /* Spec line above the drill. Same typography as the desktop
     panel eyebrow (brass mono caps), with 32px below before the
     drill begins. */
  .solve-drill-mobile-eyebrow {
    font-family: var(--font-mono);
    font-size: 11px;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: rgba(212,162,76,0.85);
    margin: 0 0 32px;
  }

  /* Full-bleed band. Extends past the section's horizontal
     padding to the viewport edges so the right-edge fade reads
     into the section bg, not into a padded margin. The
     overflow-y: visible lets number markers below each component
     show without being clipped. */
  .solve-drill-mobile-band {
    position: relative;
    width: 100vw;
    margin-left: calc(-1 * var(--sp-section-x));
    margin-right: calc(-1 * var(--sp-section-x));
  }

  /* Right-edge fade - always present (the drill is always
     scrollable-right on initial load because the components
     significantly exceed viewport width). Linear from transparent
     to the section bg over 56px. pointer-events: none so it
     doesn't intercept taps on the underlying components. */
  .solve-drill-mobile-band::after {
    content: "";
    position: absolute;
    top: 0; right: 0; bottom: 0;
    width: 56px;
    background: linear-gradient(
      to left,
      #0D0F12 0%,
      rgba(13,15,18,0.92) 30%,
      rgba(13,15,18,0) 100%
    );
    pointer-events: none;
    z-index: 3;
  }
  /* Left-edge fade - hidden initially, appears once the user has
     scrolled at all. Mirror of the right fade. */
  .solve-drill-mobile-band::before {
    content: "";
    position: absolute;
    top: 0; left: 0; bottom: 0;
    width: 56px;
    background: linear-gradient(
      to right,
      #0D0F12 0%,
      rgba(13,15,18,0.92) 30%,
      rgba(13,15,18,0) 100%
    );
    pointer-events: none;
    z-index: 3;
    opacity: 0;
    transition: opacity 220ms cubic-bezier(0.2, 0.7, 0.2, 1);
  }
  .solve-drill-mobile-band.is-scrolled::before { opacity: 1; }

  /* The scroll row. Flex (not grid) so each component sits at
     its own intrinsic width derived from native pixel dimensions.
     padding-left = section-x so component 01 lines up under the
     eyebrow at the original content column; padding-right gives
     the last component (07) room to clear the right fade. */
  .solve-drill-mobile-row {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    gap: 4px;
    padding: 36px var(--sp-section-x) 40px;
    overflow-x: auto;
    overflow-y: visible;
    scroll-snap-type: x mandatory;
    scroll-padding-left: var(--sp-section-x);
    scroll-padding-right: var(--sp-section-x);
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .solve-drill-mobile-row::-webkit-scrollbar { display: none; }

  /* A mobile part - sized small by default (native width * 0.6)
     so 3-4 fit on screen at once. Native aspect ratio preserved.
     flex: 0 0 auto so the intrinsic width is honored, not the
     flex shrink/grow. scroll-snap-align: center so each component
     snaps to viewport center as the user swipes - first/last
     items clamp to the row's start/end naturally.
     Active state pops the component up to ~0.95 of native scale
     via a transform on the button (transform-origin: center so it
     grows symmetrically into its neighbors with z-index raised so
     it sits ABOVE them - mirrors the desktop active treatment). */
  .solve-part-m {
    position: relative;
    flex: 0 0 auto;
    width: calc(var(--m-w, 200px) * 0.5);
    aspect-ratio: var(--ar, 1);
    background: transparent;
    border: 0;
    padding: 0;
    margin: 0;
    cursor: pointer;
    filter: saturate(0.5) brightness(0.58);
    transform-origin: center center;
    transition: filter var(--motion-base) cubic-bezier(0.4, 0, 0.2, 1),
                transform var(--motion-slow) cubic-bezier(0.16, 1, 0.3, 1);
    scroll-snap-align: center;
    -webkit-tap-highlight-color: transparent;
    z-index: 1;
  }
  .solve-drill-mobile.is-revealed .solve-part-m {
    animation: solvePartIn var(--motion-base) cubic-bezier(0.16, 1, 0.3, 1) var(--reveal-delay, 0ms) backwards;
  }
  .solve-part-m.is-active {
    filter: saturate(1.05) brightness(1.04)
            drop-shadow(0 0 8px rgba(212,162,76,0.24));
    transform: scale(1.1);
    z-index: 4;
  }
  .solve-part-m:focus-visible { outline: none; }
  .solve-part-m-img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: contain;
    object-position: center;
    user-select: none;
    pointer-events: none;
  }
  .solve-part-m-num {
    position: absolute;
    bottom: -18px;
    left: 50%;
    transform: translateX(-50%);
    font-family: var(--font-mono);
    font-size: 10px;
    letter-spacing: 0.08em;
    color: rgba(168,168,162,0.85);
    transition: color var(--motion-base) cubic-bezier(0.16, 1, 0.3, 1);
    pointer-events: none;
  }
  .solve-part-m.is-active .solve-part-m-num { color: #D4A24C; }

  /* Soft-fade the umbilical's LEFT edge so the cables don't
     hard-cut at the drill row's start. This is an IMAGE-level
     mask (separate from the container-level scroll fade above)
     and coexists with the new left-edge fade overlay. */
  .solve-drill-mobile-row > .solve-part-m:first-child .solve-part-m-img {
    -webkit-mask-image: linear-gradient(to right,
      transparent 0%, rgba(0,0,0,0.4) 10%, rgba(0,0,0,0.9) 20%, #000 30%, #000 100%);
    mask-image: linear-gradient(to right,
      transparent 0%, rgba(0,0,0,0.4) 10%, rgba(0,0,0,0.9) 20%, #000 30%, #000 100%);
  }

  .solve-entry-body,
  .solve-entry-spec { margin-left: 60px; }
  .solve-entry-spec-row {
    grid-template-columns: 140px 1fr;
  }
}
@media (max-width: 600px) {
  .solve-entry-head {
    grid-template-columns: 24px 1fr auto;
    column-gap: 12px;
    padding: 18px 12px 18px 8px;
  }
  .solve-entry-deck { grid-column: 2 / span 2; }
  .solve-entry-body,
  .solve-entry-spec { margin-left: 44px; margin-right: 12px; }
  .solve-entry-spec-row {
    grid-template-columns: 1fr;
    row-gap: 2px;
  }
}
`;

window.SectionSolve = SectionSolve;
