/* global React, ReactDOM */
const { useState, useEffect, useRef, useCallback } = React;
const { useTweaks, TweaksPanel, TweakSection, TweakSlider, TweakRadio, TweakToggle } = window;
const DS = window.TKOsalesDesignSystem_019dc5 || {};
const RES = (p) => (window.__resources && window.__resources[String(p).replace(/[^a-z0-9]/gi, "_")]) || p;
const MARK = { wordmark: ["assets/brand/tko_logo_wordmark.svg", 3.815], white: ["assets/brand/tko_logo_white.svg", 3.815], full: ["assets/brand/tko_logo_full.svg", 2.764] };
const { CTAButton } = DS;
const CLIENTS = window.SOP_CLIENTS || [{ id: "chicka-d", name: "CHICKA-D", group: "external", data: "SOP_DATA", es: "SOP_DATA_ES" }];
const clientById = (id) => CLIENTS.find((c) => c.id === id) || CLIENTS[0];
const dataOf = (c) => window[c.data] || null;
const clientName = (c, lang) => (lang === "es" && c.nameEs) || c.name;
const has = (v) => v && v !== "—"; /* "—" marks a field the source didn't supply; hide it */

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "pressTemp": 320,
  "pressTime": 7,
  "pressPressure": "Medium-high",
  "peel": "Hot peel",
  "showSpec": false
}/*EDITMODE-END*/;

const UI_EN = {
  heroLine1: "Standard operating", heroLine2: "procedures",
  langLabel: "Language", library: "TKO SOP Library",
  clientSystem: "Client system",
  systemIntro: "The stations below move a CHICKA-D order from picking through pallet staging. Select a station to open its instructions, process photos, and video screen.",
  stationsTag: "6 stations · Revision 1",
  openSop: "Open SOP ☞", viewStatus: "View status ☞",
  staffing: "Staffing", steps: "Steps", variantWord: "variant", inPreparation: "In preparation",
  area: "Area", systemWord: "System", handoff: "Hand-off",
  videoProcess: "Video process", videoKicker: "Video process",
  elapsed: "Elapsed", remaining: "Time remaining",
  slotMark: "VIDEO SLOT",
  slotLine1: (n) => "Drop the " + n + " station recording into this project as",
  slotNote: "MP4, H.264. It will appear here automatically with the countdown timer running.",
  additionalInfo: "Additional information", nextStation: "Next station", note: "Note —",
  pendingTitle: "Procedure in preparation",
  pendingBody: "The Shipping instructions and process video were not included in the supplied source package. Add the approved procedure in the next controlled revision.",
  specKicker: "Heat application", specLead: "CHICKA-D transfers",
  specTemp: "Temperature", specTime: "Dwell time", specPressure: "Pressure", specPeel: "Peel",
  revision: "Revision", part: "Part", sec: "sec",
  bmKicker: "Benchmark", bmPerDay: "/day",
  bmUnit_units: "units per person / day", bmUnit_presses: "presses per person / day",
  bmUnit_orders: "orders per person / day", bmUnit_dispatch: "Same-day dispatch",
  bmShort_dispatch: "Same-day", bmPerHour: "/hr",
  bmUnit_garments_operator: "garments per operator / day", bmUnit_garments_machine: "garments per machine / day",
  bmUnit_garments_day: "garments / day", bmUnit_carts_hour: "carts per hour",
  bmNote_folding: "Folding is counted inside the Packing rate — garments are folded and bagged in one motion.",
  groupExternal: "External clients", groupInternal: "Internal", comingSoon: "In preparation",
  clientPendingTitle: "Procedures in preparation",
  clientPendingBody: (n) => "The " + n + " procedures are being prepared from the source workbooks. They will appear here as each station is approved."
};
const UI_ES = Object.assign({}, UI_EN, (window.SOP_DATA_ES && window.SOP_DATA_ES.ui) || {}, { sec: "seg",
  bmKicker: "Parámetro", bmPerDay: "/día",
  bmUnit_units: "unidades por persona / día", bmUnit_presses: "prensados por persona / día",
  bmUnit_orders: "pedidos por persona / día", bmUnit_dispatch: "Despacho el mismo día",
  bmShort_dispatch: "Mismo día", bmPerHour: "/h",
  bmUnit_garments_operator: "prendas por operador / día", bmUnit_garments_machine: "prendas por máquina / día",
  bmUnit_garments_day: "prendas / día", bmUnit_carts_hour: "carritos por hora",
  bmNote_folding: "El doblado se cuenta dentro del ritmo de Empaque — las prendas se doblan y embolsan en un solo movimiento.",
  groupExternal: "Clientes externos", groupInternal: "Interno", comingSoon: "En preparación",
  clientPendingTitle: "Procedimientos en preparación",
  clientPendingBody: (n) => "Los procedimientos de " + n + " se están preparando a partir de los libros de trabajo de origen. Aparecerán aquí conforme se apruebe cada estación."
});
const ES_VALUES = { Light: "Ligera", Medium: "Media", "Medium-high": "Media-alta", Firm: "Firme", "Hot peel": "En caliente", "Warm peel": "En tibio", "Cold peel": "En frío" };

function pickStep(en, es) {
  if (!es) return en;
  return { n: en.n, photos: en.photos, title: es.title || en.title, lead: es.lead, body: es.body, subs: es.subs, note: es.note };
}
function stationsFor(client, lang) {
  const D = dataOf(client);
  if (!D) return [];
  const E = client.es && window[client.es];
  if (lang !== "es" || !E) return D.stations;
  const ES = E.stations;
  return D.stations.map((st) => {
    const e = ES[st.id];
    if (!e) return st;
    const out = Object.assign({}, st, {
      name: e.name || st.name, owner: e.owner || st.owner, system: e.system || st.system,
      doc: st.doc.replace("SOP", "PEO"),
      handoff: e.handoff || st.handoff, summary: e.summary || st.summary,
      part: st.part ? st.part.replace("Part", "Parte") : st.part,
      benchmark: st.benchmark && e.benchmarkNote ? Object.assign({}, st.benchmark, { noteText: e.benchmarkNote }) : st.benchmark,
      steps: st.steps.map((s, i) => pickStep(s, e.steps && e.steps[i]))
    });
    if (st.variant && e.variant) out.variant = Object.assign({}, st.variant, { title: e.variant.title, steps: st.variant.steps.map((s, i) => pickStep(s, e.variant.steps && e.variant.steps[i])) });
    return out;
  });
}

const R = "#CC1717", BK = "#1A1A1A", GY = "#EDECEA", BD = "#C8C5C2";

/* ---- haptics + ripple ---- */
const REDUCED = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
function buzz(pattern) { try { if (navigator.vibrate && !REDUCED) navigator.vibrate(pattern); } catch (e) {} }
const HAPTIC = { tap: 12, press: [18, 30, 18], open: [10, 40, 24], soft: 6 };
function ripple(e) {
  if (REDUCED) return;
  const el = e.currentTarget, r = el.getBoundingClientRect();
  const size = Math.max(r.width, r.height) * 2;
  const x = (e.clientX || r.left + r.width / 2) - r.left - size / 2, y = (e.clientY || r.top + r.height / 2) - r.top - size / 2;
  const s = document.createElement("span");
  s.className = "rip"; s.style.cssText = `width:${size}px;height:${size}px;left:${x}px;top:${y}px`;
  el.appendChild(s); setTimeout(() => s.remove(), 700);
}
function press(fn, h = HAPTIC.tap) { return (e) => { ripple(e); buzz(h); fn && fn(e); }; }

/* ---- scroll-driven step reveal + active tracking ---- */
function useStepObserver(deps) {
  useEffect(() => {
    const steps = Array.from(document.querySelectorAll(".step"));
    if (!steps.length) return;
    if (REDUCED) { steps.forEach((s) => s.classList.add("in")); return; }
    const io = new IntersectionObserver((ents) => ents.forEach((en) => { if (en.isIntersecting) { en.target.classList.add("in"); io.unobserve(en.target); } }), { rootMargin: "0px 0px -8% 0px", threshold: 0.08 });
    steps.forEach((s) => io.observe(s));
    let raf = 0, last = null;
    const onScroll = () => {
      if (raf) return;
      raf = requestAnimationFrame(() => {
        raf = 0;
        const mid = window.innerHeight * 0.38;
        let best = null, bd = Infinity;
        for (const s of steps) { const b = s.getBoundingClientRect(); const d = Math.abs(b.top + Math.min(b.height, 240) / 2 - mid); if (d < bd) { bd = d; best = s; } }
        if (best !== last) {
          if (last) last.classList.remove("active");
          if (best) { best.classList.add("active"); if (last) buzz(HAPTIC.soft); }
          document.querySelectorAll(".rail-list a").forEach((a) => a.classList.toggle("on", best && a.getAttribute("href") === "#" + best.id));
          last = best;
        }
        const doc = document.documentElement, p = doc.scrollHeight - doc.clientHeight;
        const bar = document.querySelector(".readbar"); if (bar) bar.style.setProperty("--p", p > 0 ? (window.scrollY / p).toFixed(4) : 0);
      });
    };
    onScroll(); window.addEventListener("scroll", onScroll, { passive: true });
    return () => { io.disconnect(); window.removeEventListener("scroll", onScroll); if (raf) cancelAnimationFrame(raf); };
  }, deps);
}

function Embers() {
  if (REDUCED) return null;
  const list = React.useMemo(() => Array.from({ length: 14 }, (_, i) => ({ l: (i * 7.3 + 4) % 100, d: 11 + (i % 5) * 2.4, delay: -(i * 1.7), x: (i % 2 ? 1 : -1) * (12 + (i % 4) * 14) })), []);
  return <>{list.map((e, i) => <span key={i} className="ember" style={{ left: e.l + "%", "--d": e.d + "s", "--delay": e.delay + "s", "--x": e.x + "px" }}></span>)}</>;
}
const disp = "'Barlow Condensed', 'Arial Narrow', Arial, sans-serif";
const head = "'Oswald', 'Arial Narrow', Arial, sans-serif";

function Logo({ variant = "wordmark", height = 34, src }) {
  if (src) return React.createElement("img", { src: RES(src), height, width: Math.round(height * 3.815), alt: "TKO", style: { display: "inline-block", verticalAlign: "middle" } });
  const m = MARK[variant] || MARK.wordmark;
  return React.createElement("img", { src: RES(m[0]), height, width: Math.round(height * m[1]), alt: "TKO", style: { display: "inline-block", verticalAlign: "middle" } });
}

function Chrome({ crumbs, onCrumb, right }) {
  return (
    <div className="chrome">
      <div className="chrome-in">
        <div className="chrome-left"><Logo height={28} src="assets/brand/tko_logo_white_mark.svg" /><span className="chrome-div"></span>
          <nav className="crumbs">{crumbs.map((c, i) => (
            <span key={c.label} className="crumb-wrap">
              {i > 0 ? <span className="crumb-sep">/</span> : null}
              {i < crumbs.length - 1
                ? <button className="crumb link" onClick={press(() => onCrumb(c.to))}>{c.label}</button>
                : <span className="crumb here">{c.label}</span>}
            </span>))}
          </nav>
        </div>
        <div className="chrome-right">{right}</div>
      </div>
    </div>
  );
}

function Divider() {
  return <div className="divider"><span className="rule"></span><img src={RES("assets/brand/boxing_gloves_divider.svg")} height="22" alt="" /><span className="rule"></span></div>;
}

function fmt(s) {
  if (!isFinite(s) || s < 0) s = 0;
  const m = Math.floor(s / 60), sec = Math.floor(s % 60);
  return m + ":" + String(sec).padStart(2, "0");
}

function VideoPanel({ title, src, onClose, ui }) {
  const ref = useRef(null);
  const [state, setState] = useState({ dur: 0, cur: 0, playing: false, failed: false, ended: false });
  useEffect(() => { buzz(HAPTIC.open); }, []);
  const lastSec = useRef(-1);
  useEffect(() => {
    const rem = Math.ceil(state.dur - state.cur);
    if (state.playing && rem <= 5 && rem > 0 && rem !== lastSec.current) { lastSec.current = rem; buzz(HAPTIC.soft); }
  }, [state.cur, state.playing, state.dur]);
  useEffect(() => {
    const k = (e) => { if (e.key === "Escape") onClose(); };
    window.addEventListener("keydown", k);
    return () => window.removeEventListener("keydown", k);
  }, [onClose]);
  const v = ref.current;
  const remaining = Math.max(0, state.dur - state.cur);
  const pct = state.dur ? (state.cur / state.dur) * 100 : 0;
  return (
    <div className="modal-bg" onClick={onClose}>
      <div className="modal" onClick={(e) => e.stopPropagation()}>
        <div className="modal-head">
          <div>
            <div className="modal-kicker">{ui.videoKicker}</div>
            <div className="modal-title">{title}</div>
          </div>
          <button className="x" onClick={press(onClose)} aria-label="Close">✕</button>
        </div>
        <div className={"stage" + (state.tall ? " tall" : "")}>
          {state.failed
            ? <div className="slot">
                <div className="slot-mark">{ui.slotMark}</div>
                <p>{ui.slotLine1(title)}</p>
                <code>{src}</code>
                <p className="slot-note">{ui.slotNote}</p>
              </div>
            : <video ref={ref} src={RES(src)} playsInline
                onLoadedMetadata={(e) => { const t = e.target; setState((s) => ({ ...s, dur: t.duration, tall: t.videoHeight > t.videoWidth })); }}
                onTimeUpdate={(e) => setState((s) => ({ ...s, cur: e.target.currentTime }))}
                onPlay={() => setState((s) => ({ ...s, playing: true, ended: false }))}
                onPause={() => setState((s) => ({ ...s, playing: false }))}
                onEnded={() => { buzz(HAPTIC.press); setState((s) => ({ ...s, playing: false, ended: true })); }}
                onError={() => setState((s) => ({ ...s, failed: true }))} />}
        </div>
        <div className="vbar">
          <button className={"pbtn pressable" + (state.playing ? " playing" : "")} disabled={state.failed} onClick={press(() => { if (!v) return; v.paused ? v.play() : v.pause(); }, HAPTIC.press)}>
            {state.playing ? "❙❙" : "▶"}
          </button>
          <div className="track" onClick={(e) => {
            if (!v || !state.dur) return;
            const r = e.currentTarget.getBoundingClientRect();
            v.currentTime = ((e.clientX - r.left) / r.width) * state.dur;
          }}><span className="fill" style={{ width: pct + "%" }}></span></div>
          <div className="timers">
            <div className="tbox"><span className="tlabel">{ui.elapsed}</span><span className="tval">{fmt(state.cur)}</span></div>
            <div className={"tbox down" + (state.playing && remaining <= 5 && remaining > 0 ? " urgent" : "") + (state.ended ? " done" : "")}><span className="tlabel">{ui.remaining}</span><span className="tval">{state.failed ? "–:––" : fmt(remaining)}</span></div>
          </div>
        </div>
      </div>
    </div>
  );
}

function Photos({ dir, photos, onZoom }) {
  if (!photos || !photos.length) return null;
  return (
    <div className={"shots n" + Math.min(photos.length, 4)}>
      {photos.map((p, i) => (
        <button key={p + i} className="shot pressable" onClick={press(() => onZoom(RES(dir + p)), HAPTIC.soft)}>
          <img src={RES(dir + p)} alt="" loading="lazy" />
        </button>
      ))}
    </div>
  );
}

function Step({ s, dir, onZoom, ui }) {
  return (
    <article className="step" id={"step-" + s.n}>
      <div className="step-n">{String(s.n).padStart(2, "0")}</div>
      <div className="step-body">
        <h3 className="step-t">{s.title}</h3>
        {s.lead ? <p className="lead">{s.lead}</p> : null}
        {s.body ? <p className="lead">{s.body}</p> : null}
        {s.subs ? <ol className="subs">{s.subs.map((t, i) => <li key={i}><span className="sq"></span><span>{t}</span></li>)}</ol> : null}
        {s.note ? <p className="note"><strong>{ui.note}</strong> {s.note}</p> : null}
        <Photos dir={dir} photos={s.photos} onZoom={onZoom} />
      </div>
    </article>
  );
}

function ClientButton({ c, lang, ui, onOpen }) {
  const ready = !!dataOf(c);
  return (
    <button className={"client" + (c.group === "internal" ? " internal" : "") + (ready ? "" : " soon")} onClick={press(() => onOpen(c.id), HAPTIC.open)}>
      <span className="client-n">{clientName(c, lang)}</span>
      {ready ? null : <span className="client-tag">{ui.comingSoon}</span>}
    </button>
  );
}

function Home({ onOpenClient, lang, onLang, ui }) {
  const ext = CLIENTS.filter((c) => c.group !== "internal"), int = CLIENTS.filter((c) => c.group === "internal");
  return (
    <div className="home-screen">
      <div className="home-bg-drift"></div>
      <div className="glow"></div><div className="glow g2"></div>
      <Embers />
      <div className="scan"></div>
      <div className="home-in" key={lang}>
        <img src={RES("assets/brand/tko_logo_white_mark.svg")} height="40" width="153" alt="TKO" style={{ display: "inline-block", verticalAlign: "middle" }} />
        <div className="langbar">
          <span className="lang-k">{ui.langLabel}</span>
          <div className="lang-set">
            <button className={"lang pressable" + (lang === "en" ? " on" : "")} onClick={press(() => onLang("en"))}>English</button>
            <button className={"lang pressable" + (lang === "es" ? " on" : "")} onClick={press(() => onLang("es"))}>Español</button>
          </div>
        </div>
        <h1 className="hero lang-swap">{ui.heroLine1}<br /><span className="accent">{ui.heroLine2}</span></h1>
        <Divider />
        <div className="client-groups">
          <div className="client-group">
            <div className="client-gk">{ui.groupExternal}</div>
            <div className={"client-row" + (ext.length > 1 ? " two" : "")}>
              {ext.map((c) => <ClientButton key={c.id} c={c} lang={lang} ui={ui} onOpen={onOpenClient} />)}
            </div>
          </div>
          {int.length ? <div className="client-group">
            <div className="client-gk">{ui.groupInternal}</div>
            <div className="client-row">
              {int.map((c) => <ClientButton key={c.id} c={c} lang={lang} ui={ui} onOpen={onOpenClient} />)}
            </div>
          </div> : null}
        </div>
      </div>
    </div>
  );
}

function System({ client, onOpen, stations, ui, lang }) {
  const st = stations;
  const name = clientName(client, lang);
  if (!st.length) return (
    <div className="page screen-enter" key={client.id}>
      <header className="page-head">
        <h1 className="ptitle"><span className="accent">{name}</span></h1>
      </header>
      <div className="pendbox">
        <h2>{ui.clientPendingTitle}</h2>
        <p>{ui.clientPendingBody(name)}</p>
      </div>
    </div>
  );
  const intro = client.id === "chicka-d" ? ui.systemIntro : ((client.intro && (client.intro[lang] || client.intro.en)) || "");
  return (
    <div className="page screen-enter" key={client.id}>
      <header className="page-head">
        <h1 className="ptitle"><span className="accent">{name}</span></h1>
        {intro ? <p className="pintro">{intro}</p> : null}
      </header>
      <div className="flow">
        {st.map((s, i) => (
          <React.Fragment key={s.id}>
            <span className="flow-node" style={{ "--i": i }}>{s.name}</span>
            {i < st.length - 1 ? <span className="flow-arrow" style={{ "--i": i }}>→</span> : null}
          </React.Fragment>
        ))}
      </div>
      <div className="grid">
        {st.map((s, i) => (
          <button key={s.id} className={"scard pressable" + (s.pending ? " pending" : "")} style={{ "--i": i }} onClick={press(() => onOpen(s.id), HAPTIC.open)}>
            <div className="scard-top">
              <span className="seq">{String(s.seq).padStart(2, "0")}</span>
              <span className="doc">{s.doc}{s.part ? " · " + s.part : ""}</span>
            </div>
            <h2 className="scard-t">{s.name}</h2>
            <p className="scard-s">{s.summary}</p>
            {s.benchmark ? <span className="bpill">{bpill(s.benchmark, ui)}</span> : null}
            <dl className="meta">
              {has(s.owner) ? <div><dt>{ui.staffing}</dt><dd>{s.owner}</dd></div> : null}
              <div><dt>{ui.steps}</dt><dd>{s.pending ? ui.inPreparation : s.steps.length + (s.variant ? " + " + s.variant.steps.length + " " + ui.variantWord : "")}</dd></div>
            </dl>
            <span className="scard-cta">{s.pending ? ui.viewStatus : ui.openSop}</span>
          </button>
        ))}
      </div>
    </div>
  );
}

function PressSpec({ t, ui, lang }) {
  const val = (v) => (lang === "es" ? ES_VALUES[v] || v : v);
  return (
    <div className="spec">
      <div className="spec-cell lead"><div className="spec-k">{ui.specKicker}</div><div className="spec-v">{ui.specLead}</div></div>
      <div className="spec-cell"><div className="spec-k">{ui.specTemp}</div><div className="spec-v">{t.pressTemp}<small>°F</small></div></div>
      <div className="spec-cell"><div className="spec-k">{ui.specTime}</div><div className="spec-v">{t.pressTime}<small>{ui.sec}</small></div></div>
      <div className="spec-cell"><div className="spec-k">{ui.specPressure}</div><div className="spec-v">{val(t.pressPressure)}</div></div>
      <div className="spec-cell"><div className="spec-k">{ui.specPeel}</div><div className="spec-v">{val(t.peel)}</div></div>
    </div>
  );
}

function bpill(b, ui) { return b.value ? b.value + (b.unit === "carts_hour" ? ui.bmPerHour : ui.bmPerDay) : ui.bmShort_dispatch; }
function Benchmark({ b, ui }) {
  if (!b) return null;
  return (
    <div className="bmark">
      <div className="bmark-k">{ui.bmKicker}</div>
      <div className="bmark-v">{b.value ? <b>{b.value}</b> : null}<span>{ui["bmUnit_" + b.unit]}</span></div>
      {b.noteText || b.note ? <p className="bmark-n">{b.noteText || ui["bmNote_" + b.note]}</p> : null}
    </div>
  );
}

function Sop({ client, station, onOpen, onZoom, t, stations, ui, lang }) {
  const [video, setVideo] = useState(null);
  const dir = (dataOf(client).photoDirs || {})[station.id] || "";
  const next = stations.find((s) => s.seq === station.seq + 1);
  useStepObserver([station.id, lang]);
  return (
    <div className="page sop screen-enter" key={station.id}>
      <div className="readbar"></div>
      <header className="sop-head">
        <div className="sop-head-l">
          <div className="kicker">{station.doc}{station.part ? " · " + station.part : ""} · {ui.revision} {station.rev}</div>
          <h1 className="ptitle">{station.name}</h1>
          <p className="pintro">{station.summary}</p>
          <Benchmark b={station.benchmark} ui={ui} />
        </div>
        <div className="sop-head-r">
          {station.video ? <CTAButton size="lg" onClick={press(() => setVideo({ title: station.name, src: station.video }), HAPTIC.open)}>{ui.videoProcess}</CTAButton> : null}
          <div className="doc-meta">
            <div><span>{ui.area}</span><b>{station.name}</b></div>
            {has(station.owner) ? <div><span>{ui.staffing}</span><b>{station.owner}</b></div> : null}
            {has(station.system) ? <div><span>{ui.systemWord}</span><b>{station.system}</b></div> : null}
            {has(station.handoff) ? <div><span>{ui.handoff}</span><b>{station.handoff}</b></div> : null}
          </div>
        </div>
      </header>
      {client.id === "chicka-d" && station.id === "heatpress" && t.showSpec ? <PressSpec t={t} ui={ui} lang={lang} /> : null}
      {station.pending
        ? <div className="pendbox">
            <h2>{ui.pendingTitle}</h2>
            <p>{ui.pendingBody}</p>
          </div>
        : <div className="sop-body">
            <aside className="rail">
              <div className="rail-k">{ui.steps}</div>
              <ol className="rail-list">
                {station.steps.map((s) => (
                  <li key={s.n}><a href={"#step-" + s.n} onClick={(e) => { e.preventDefault(); buzz(HAPTIC.tap); const el = document.getElementById("step-" + s.n); if (el) window.scrollTo({ top: el.getBoundingClientRect().top + window.scrollY - 84, behavior: REDUCED ? "auto" : "smooth" }); }}><span>{String(s.n).padStart(2, "0")}</span>{s.title}</a></li>
                ))}
              </ol>
            </aside>
            <div className="steps">
              {station.steps.map((s) => <Step key={s.n} s={s} dir={dir} onZoom={onZoom} ui={ui} />)}
              {station.variant
                ? <section className="variant">
                    <Divider />
                    <div className="kicker">{ui.additionalInfo}</div>
                    <h2 className="vtitle">{station.variant.title}</h2>
                    {station.variant.video
                      ? <div className="vvideo"><CTAButton onClick={press(() => setVideo({ title: station.variant.title, src: station.variant.video }), HAPTIC.open)}>{ui.videoProcess}</CTAButton></div>
                      : null}
                    {station.variant.steps.map((s) => <Step key={"v" + s.n} s={s} dir={dir} onZoom={onZoom} ui={ui} />)}
                  </section>
                : null}
              {next
                ? <button className="nextbar pressable" onClick={press(() => onOpen(next.id), HAPTIC.open)}>
                    <span className="kicker">{ui.nextStation}</span>
                    <span className="nextbar-n">{next.name} ☞</span>
                  </button>
                : null}
            </div>
          </div>}
      {video ? ReactDOM.createPortal(<VideoPanel {...video} onClose={() => setVideo(null)} ui={ui} />, document.body) : null}
    </div>
  );
}

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [view, setView] = useState(() => {
    try {
      const v = JSON.parse(localStorage.getItem("chicka-d-sop-view"));
      if (!v || (v.screen !== "system" && v.screen !== "sop")) return { screen: "home" };
      const c = CLIENTS.find((x) => x.id === (v.client || "chicka-d"));
      if (!c) return { screen: "home" };
      if (v.screen === "sop" && !(dataOf(c) && dataOf(c).stations.some((s) => s.id === v.id))) return { screen: "home" };
      return Object.assign({}, v, { client: c.id });
    }
    catch (e) { return { screen: "home" }; }
  });
  const [zoom, setZoom] = useState(null);
  const [lang, setLang] = useState(() => { try { return localStorage.getItem("chicka-d-sop-lang") || "en"; } catch (e) { return "en"; } });
  useEffect(() => { try { localStorage.setItem("chicka-d-sop-lang", lang); } catch (e) {} }, [lang]);
  const ui = lang === "es" ? UI_ES : UI_EN;
  const client = clientById(view.client);
  const stations = React.useMemo(() => stationsFor(client, lang), [client, lang]);
  useEffect(() => { try { localStorage.setItem("chicka-d-sop-view", JSON.stringify(view)); } catch (e) {} }, [view]);
  const go = useCallback((v) => { setView(v); window.scrollTo(0, 0); }, []);
  const station = view.screen === "sop" ? stations.find((s) => s.id === view.id) : null;
  const crumbs = [{ label: ui.library, to: { screen: "home" } }];
  if (view.screen !== "home") crumbs.push({ label: clientName(client, lang), to: { screen: "system", client: client.id } });
  if (station) crumbs.push({ label: station.name, to: null });
  return (
    <div className={"app " + view.screen}>
      {view.screen !== "home"
        ? <Chrome crumbs={crumbs} onCrumb={(t) => t && go(t)}
            right={station ? <span className="chrome-tag">{station.doc + (station.part ? " · " + station.part : "")}</span> : null} />
        : null}
      {view.screen === "home" ? <Home onOpenClient={(id) => go({ screen: "system", client: id })} lang={lang} onLang={setLang} ui={ui} /> : null}
      {view.screen === "system" ? <System client={client} onOpen={(id) => go({ screen: "sop", client: client.id, id })} stations={stations} ui={ui} lang={lang} /> : null}
      {station ? <Sop client={client} station={station} onOpen={(id) => go({ screen: "sop", client: client.id, id })} onZoom={setZoom} t={t} stations={stations} ui={ui} lang={lang} /> : null}
      <footer className="foot">
        <img src={RES("assets/brand/white-tko-logo.png")} height="22" width="84" alt="TKO" style={{ display: "inline-block", verticalAlign: "middle" }} /><span>TKO Sales · 1700 NW 18th St, Pompano Beach, FL 33069 · (888) 286-4121 · info@tkosales.com</span>
      </footer>
      {zoom ? ReactDOM.createPortal(<div className="modal-bg" onClick={() => setZoom(null)}><img className="zoom" src={zoom} alt="" /></div>, document.body) : null}
      <TweaksPanel>
        <TweakSection label="Heat application — Heat Press SOP" />
        <TweakSlider label="Temperature" value={t.pressTemp} min={260} max={400} step={5} unit="°F" onChange={(v) => setTweak("pressTemp", v)} />
        <TweakSlider label="Dwell time" value={t.pressTime} min={3} max={30} step={1} unit="s" onChange={(v) => setTweak("pressTime", v)} />
        <TweakRadio label="Pressure" value={t.pressPressure} options={["Light", "Medium", "Medium-high", "Firm"]} onChange={(v) => setTweak("pressPressure", v)} />
        <TweakRadio label="Peel" value={t.peel} options={["Hot peel", "Warm peel", "Cold peel"]} onChange={(v) => setTweak("peel", v)} />
        <TweakToggle label="Show spec strip" value={t.showSpec} onChange={(v) => setTweak("showSpec", v)} />
      </TweaksPanel>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
