// ── flow.jsx ───────────────────────────────────────────────────────────────
// The redesigned, mobile-first progressive flow:
//   GarmentPicker → GarmentChip → SizeFit → YarnRow → FineTune → ResultContent / ResultBar
// Beginner path is garment → size → yarn → answer. Depth (gauge, stitch, per-
// piece dims) reveals one tap at a time, never buried in a dialog.

const { useState: useStateF, useRef: useRefF } = React

// ── full-screen entry: "What are you making?" ───────────────────────────────
function GarmentPicker({ onPick, onBlank, embedded }) {
  const t = useT()
  const Wrap = embedded ? "div" : "div"
  return (
    <Wrap style={{ maxWidth: 720, margin: "0 auto", padding: embedded ? 0 : "8px 16px 40px" }}>
      {!embedded && (
        <div style={{ textAlign: "center", padding: "20px 6px 24px" }}>
          <ShopLogo height={30} style={{ marginBottom: 16 }} />
          <h1 style={{ fontFamily: "var(--font-display)", fontSize: "clamp(30px,8vw,46px)", lineHeight: 1.04,
            color: "var(--ink)", margin: "0 0 10px", fontWeight: 500 }}>{t("How much yarn?")}</h1>
          <p style={{ fontSize: 15, color: "var(--muted)", maxWidth: 380, margin: "0 auto", lineHeight: 1.55,
            fontFamily: "var(--font-ui)" }}>{t("Pick what you're making. You'll get an estimate right away \u2014 fine-tune only if you want to.")}</p>
        </div>
      )}
      <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(150px, 1fr))", gap: 12 }}>
        {Object.entries(PRESETS).map(([key, p]) => (
          <button key={key} onClick={() => onPick(key)} className="preset-card"
            style={{ background: "var(--card)", border: "1px solid var(--line)", borderRadius: 18,
              padding: "16px 12px 14px", cursor: "pointer", textAlign: "center", fontFamily: "var(--font-ui)",
              display: "flex", flexDirection: "column", alignItems: "center", gap: 6 }}>
            <div style={{ height: 86, display: "grid", placeItems: "center", width: "100%" }}>
              <MiniSchematic presetKey={key} size={86} />
            </div>
            <div style={{ fontFamily: "var(--font-display)", fontSize: 19, color: "var(--ink)" }}>{t(p.label)}</div>
            <div style={{ fontSize: 11.5, color: "var(--muted)", lineHeight: 1.4 }}>{t(p.blurb)}</div>
          </button>
        ))}
      </div>
      <div style={{ display: "flex", justifyContent: "center", marginTop: 18 }}>
        <button onClick={onBlank} style={{ background: "transparent", border: "none", color: "var(--muted)",
          fontSize: 13, cursor: "pointer", fontFamily: "var(--font-ui)", textDecoration: "underline",
          textUnderlineOffset: 3, padding: 8 }}>{t("or start from a blank piece")}</button>
      </div>
    </Wrap>
  )
}

// ── compact garment selector (top of the build view) ────────────────────────
function GarmentChip({ presetKey, onClick }) {
  const t = useT()
  const label = presetKey ? t(PRESETS[presetKey].label) : t("Custom build")
  return (
    <button onClick={onClick} style={{ display: "inline-flex", alignItems: "center", gap: 9,
      background: "var(--card)", border: "1px solid var(--line-strong)", borderRadius: 13, padding: "7px 9px 7px 7px",
      cursor: "pointer", fontFamily: "var(--font-ui)" }}>
      <span style={{ width: 34, height: 34, borderRadius: 9, background: "var(--field)", border: "1px solid var(--line)",
        display: "grid", placeItems: "center", flexShrink: 0, overflow: "hidden" }}>
        {presetKey ? <MiniSchematic presetKey={presetKey} size={30} /> : <Icon.plus s={16} />}
      </span>
      <span style={{ display: "flex", flexDirection: "column", alignItems: "flex-start", lineHeight: 1.1 }}>
        <span style={{ fontSize: 9.5, letterSpacing: ".09em", textTransform: "uppercase", color: "var(--muted)", fontWeight: 700 }}>{t("Making")}</span>
        <span style={{ fontSize: 15, color: "var(--ink)", fontWeight: 700, fontFamily: "var(--font-display)" }}>{label}</span>
      </span>
      <span style={{ color: "var(--muted)", marginLeft: 2 }}><Icon.chevron s={16} /></span>
    </button>
  )
}

// ── size & fit ───────────────────────────────────────────────────────────────
function SizeFit({ presetKey, size, fit, onSize, onFit, step, blocks, effDims }) {
  const t = useT()
  const S = SIZING[presetKey]
  if (!S) return null
  const fm = actualFinishedMeasure(presetKey, size, fit, blocks, effDims)
  // "custom" iff the user has actually hand-edited the finished dimension (a pin
  // on the finished role's key) — not a value comparison, which would always
  // fire for hats where the derived circ carries a negative-ease factor.
  const finBlock = blocks && blocks.find(b => b.role === S.finishedRole)
  const isCustom = !!(finBlock && finBlock.pinnedDims && finBlock.pinnedDims[S.finishedKey])
  return (
    <SectionCard title={t("Size")} step={step} hint={<span style={{ fontVariantNumeric: "tabular-nums" }}>
      {t(S.finishedLabel)} <b style={{ color: "var(--accent)", fontSize: 14 }}>{fm}cm</b>{isCustom ? <span style={{ color: "var(--muted)", fontWeight: 400 }}> · {t("custom")}</span> : null}</span>}>
      <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
        <ScrollRow gap={6} pad={22} style={{ margin: "0 -2px", padding: "0 2px 2px" }}>
          {S.sizes.map(s => {
            const on = s === size
            return (
              <button key={s} onClick={() => onSize(s)}
                style={{ flex: "0 0 auto", border: `1px solid ${on ? "var(--accent)" : "var(--line-strong)"}`,
                  background: on ? "var(--accent-soft)" : "var(--field)", borderRadius: 11, padding: "8px 12px",
                  cursor: "pointer", fontFamily: "var(--font-ui)", display: "flex", flexDirection: "column",
                  alignItems: "center", gap: 1, minWidth: 56 }}>
                <span style={{ fontSize: 13.5, fontWeight: 700, color: on ? "var(--accent)" : "var(--ink)" }}>{t(s)}</span>
                <span style={{ fontSize: 9.5, color: "var(--muted)", fontVariantNumeric: "tabular-nums" }}>{S.base[s]}cm</span>
              </button>
            )
          })}
        </ScrollRow>
        <div style={{ display: "flex", flexDirection: "column", gap: 7 }}>
          <FieldLabel>{t("Fit")}</FieldLabel>
          <Segmented value={fit} onChange={onFit} size="sm"
            options={Object.entries(S.fits).map(([name, ease]) => ({ value: name, label: t(name),
              sub: (ease >= 0 ? "+" : "") + ease + "cm" }))} />
        </div>
      </div>
    </SectionCard>
  )
}

// ── yarn weight picker — visual thickness chips ─────────────────────────────
function YarnRow({ value, onChange, onCustom, customYarn, step }) {
  const t = useT()
  const order = ["lace", "fingering", "sport", "dk", "worsted", "bulky", "superbulky"]
  const nearestWeight = (m) => {
    let best = "dk", bd = Infinity
    order.forEach(k => { const dd = Math.abs(YARN[k].mPer100g - m); if (dd < bd) { bd = dd; best = k } })
    return best
  }
  const chip = (key, label, sub, weightKey, marked) => {
    const on = value === key
    return (
      <button key={key} onClick={() => onChange(key)}
        style={{ flex: "0 0 auto", scrollSnapAlign: "start", border: `1px solid ${on ? "var(--accent)" : (marked ? "var(--accent-2)" : "var(--line-strong)")}`,
          background: on ? "var(--accent-soft)" : (marked ? "var(--accent-2-soft)" : "var(--field)"), borderRadius: 13, padding: "9px 12px 8px",
          cursor: "pointer", fontFamily: "var(--font-ui)", display: "flex", flexDirection: "column",
          alignItems: "center", gap: 4, minWidth: 78 }}>
        <YarnIndicator yarnKey={key} color={on ? "var(--accent)" : (marked ? "var(--accent-2)" : "var(--muted)")} />
        <span style={{ fontSize: 12.5, fontWeight: 700, color: on ? "var(--accent)" : "var(--ink)", whiteSpace: "nowrap" }}>{label}</span>
        <span style={{ fontSize: 9.5, color: "var(--muted)", whiteSpace: "nowrap" }}>{sub}</span>
      </button>
    )
  }
  const thin = value === "lace" || value === "fingering" ||
    (value === "custom" && customYarn && customYarn.mPer100g > YARN.fingering.mPer100g)
  return (
    <SectionCard title={t("Yarn weight")} step={step} hint={t("tap to compare")}>
      <ScrollRow gap={7} pad={26} style={{ margin: "0 -2px", padding: "0 2px 3px" }}>
        {customYarn && chip("custom", t("My yarn"), customYarn.label || `${customYarn.mPer100g} m/100g`,
          nearestWeight(customYarn.mPer100g), true)}
        <button onClick={onCustom}
          style={{ flex: "0 0 auto", border: "1px dashed var(--line-strong)", background: "transparent",
            borderRadius: 13, padding: "9px 14px", cursor: "pointer", fontFamily: "var(--font-ui)",
            display: "flex", flexDirection: "column", alignItems: "center", gap: 4, minWidth: 74, color: "var(--muted)" }}>
          <span style={{ display: "grid", placeItems: "center", height: 22 }}><Icon.plus s={18} /></span>
          <span style={{ fontSize: 12.5, fontWeight: 700, whiteSpace: "nowrap" }}>{t("My yarn")}</span>
          <span style={{ fontSize: 9.5, whiteSpace: "nowrap" }}>{t("custom")}</span>
        </button>
        {order.map(k => chip(k, YARN[k].label, `${YARN[k].mPer100g} m/100g`, k))}
      </ScrollRow>
      {thin && (
        <p style={{ fontSize: 11.5, color: "var(--muted)", lineHeight: 1.5, marginTop: 10, fontFamily: "var(--font-ui)" }}>
          {t("Using a fuzzy yarn (mohair, alpaca, cashmere…)? We recommend entering the gauge you plan to knit at, or the gauge from the label, in Fine-tune below.")}
        </p>
      )}
    </SectionCard>
  )
}

// ── fine-tune accordion: gauge (first-class) + row gauge + construction ──────
function FineTune({ presetKey, effGauge, baseGauge, gaugeOverride, onGauge, effRows, rowOverride, onRows,
                    construction, onConstruction, open, onToggle, step }) {
  const t = useT()
  const showConstruction = presetKey === "sweater"
  const overridden = gaugeOverride != null && gaugeOverride > 0 && Math.round(gaugeOverride) !== Math.round(baseGauge)
  const stdRows = Math.round(stdRowsFor(effGauge))
  const rowsEstimated = !(rowOverride != null && rowOverride > 0)
  const constrLabel = (JUNCTION[construction] || {}).label || "Set-in"
  return (
    <div className="card" style={{ background: "var(--card)", border: "1px solid var(--line)", borderRadius: 16,
      overflow: "hidden" }}>
      <button onClick={onToggle} style={{ width: "100%", display: "flex", alignItems: "center", gap: 10,
        padding: "13px 15px", background: "transparent", border: "none", cursor: "pointer", fontFamily: "var(--font-ui)",
        textAlign: "left" }}>
        {step != null ? <StepDot n={step} /> : <span style={{ color: "var(--muted)", display: "grid", placeItems: "center" }}><Icon.tune s={18} /></span>}
        <span style={{ marginRight: "auto" }}>
          <span style={{ display: "block", fontSize: 14, fontWeight: 700, color: "var(--ink)" }}>
            {t("Fine-tune")}
            <span style={{ fontWeight: 500, color: "var(--muted)", fontSize: 12 }}> · {t("optional")}</span>
          </span>
          <span style={{ display: "block", fontSize: 11, color: "var(--muted)" }}>
            {t("Gauge")} · {effGauge}×{effRows} {t("sts / 10cm")}{overridden ? ` · ${t("custom")}` : ""}{showConstruction ? ` · ${t(constrLabel)}` : ""}
          </span>
        </span>
        <span style={{ color: "var(--muted)", transform: open ? "rotate(180deg)" : "none", transition: "transform .2s" }}><Icon.chevron /></span>
      </button>
      {open && (
        <div style={{ padding: "2px 15px 16px", display: "flex", flexDirection: "column", gap: 16,
          borderTop: "1px solid var(--line)" }}>
          {/* stitch gauge */}
          <div style={{ paddingTop: 14 }}>
            <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 8 }}>
              <FieldLabel accent={overridden}>{t("Stitch gauge")}</FieldLabel>
              {overridden
                ? <button onClick={() => onGauge(null)} style={{ background: "transparent", border: "none",
                    color: "var(--accent)", fontSize: 11, cursor: "pointer", fontFamily: "var(--font-ui)",
                    display: "inline-flex", alignItems: "center", gap: 4, padding: 0 }}>
                    <Icon.reset s={13} /> {t("from yarn ({n})", { n: Math.round(baseGauge) })}</button>
                : <span style={{ fontSize: 10.5, color: "var(--ok)", fontWeight: 700, fontFamily: "var(--font-ui)" }}>{t("from your yarn")}</span>}
            </div>
            <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
              <input type="range" className="yarn-range" min={8} max={40} step={1}
                value={Math.round(effGauge)} onChange={e => onGauge(Number(e.target.value))}
                style={{ flex: 1 }} />
              <div style={{ display: "flex", alignItems: "baseline", gap: 4, minWidth: 92, justifyContent: "flex-end", whiteSpace: "nowrap" }}>
                <span style={{ fontSize: 20, fontWeight: 700, color: overridden ? "var(--accent)" : "var(--ink)",
                  fontVariantNumeric: "tabular-nums", fontFamily: "var(--font-ui)" }}>{Math.round(effGauge)}</span>
                <span style={{ fontSize: 10.5, color: "var(--muted)", whiteSpace: "nowrap" }}>{t("sts / 10cm")}</span>
              </div>
            </div>
            <p style={{ fontSize: 11.5, color: "var(--muted)", lineHeight: 1.5, marginTop: 8, fontFamily: "var(--font-ui)" }}>
              {t("Knit a swatch? Set its gauge here \u2014 it's the biggest factor in how much yarn you'll use.")}
            </p>
          </div>
          {/* row gauge — visibly estimated, editable (audit A11) */}
          <div style={{ borderTop: "1px solid var(--line)", paddingTop: 14 }}>
            <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 8 }}>
              <FieldLabel accent={!rowsEstimated}>{t("Row gauge")}</FieldLabel>
              {rowsEstimated
                ? <span style={{ fontSize: 10.5, color: "var(--muted)", fontStyle: "italic", fontFamily: "var(--font-ui)" }}>{t("estimated")}</span>
                : <button onClick={() => onRows(null)} style={{ background: "transparent", border: "none",
                    color: "var(--accent)", fontSize: 11, cursor: "pointer", fontFamily: "var(--font-ui)",
                    display: "inline-flex", alignItems: "center", gap: 4, padding: 0 }}>
                    <Icon.reset s={13} /> {t("estimated {n}", { n: stdRows })}</button>}
            </div>
            <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
              <input type="range" className="yarn-range" min={Math.max(8, Math.round(effGauge * 0.8))} max={Math.round(effGauge * 2.2)} step={1}
                value={Math.round(effRows)} onChange={e => onRows(Number(e.target.value))}
                style={{ flex: 1, opacity: rowsEstimated ? 0.75 : 1 }} />
              <div style={{ display: "flex", alignItems: "baseline", gap: 4, minWidth: 92, justifyContent: "flex-end", whiteSpace: "nowrap" }}>
                <span style={{ fontSize: 20, fontWeight: 700, color: rowsEstimated ? "var(--muted)" : "var(--accent)",
                  fontStyle: rowsEstimated ? "italic" : "normal",
                  fontVariantNumeric: "tabular-nums", fontFamily: "var(--font-ui)" }}>{Math.round(effRows)}</span>
                <span style={{ fontSize: 10.5, color: "var(--muted)", whiteSpace: "nowrap" }}>{t("rows / 10cm")}</span>
              </div>
            </div>
            <p style={{ fontSize: 11.5, color: "var(--muted)", lineHeight: 1.5, marginTop: 8, fontFamily: "var(--font-ui)" }}>
              {t("Rows can't be read from the ball band \u2014 we estimate them. A row-dense fabric (lace, slip-stitch) uses more yarn; set yours if you counted it.")}
            </p>
          </div>
          {/* construction → junction allowance */}
          {showConstruction && (
            <div style={{ borderTop: "1px solid var(--line)", paddingTop: 14 }}>
              <FieldLabel style={{ display: "block", marginBottom: 8 }}>{t("Construction")}</FieldLabel>
              <Segmented value={construction} onChange={onConstruction} size="sm" grow={false}
                options={["set_in", "raglan", "yoke", "drop", "modified_drop"].map(k => ({ value: k, label: t((JUNCTION[k] || {}).label || k) }))} />
              <p style={{ fontSize: 11.5, color: "var(--muted)", lineHeight: 1.5, marginTop: 8, fontFamily: "var(--font-ui)" }}>
                {t("How the sleeves join the body. Set-in and raglan sit closest to the body; a circular yoke deepens the upper body, while a drop shoulder swaps shaping for a wider body and roomier sleeves \u2014 each spends yarn a little differently.")}
              </p>
            </div>
          )}
        </div>
      )}
    </div>
  )
}

// ── reusable section card with title + optional hint + optional step badge ───
function SectionCard({ title, hint, action, step, children }) {
  return (
    <div className="card" style={{ background: "var(--card)", border: "1px solid var(--line)", borderRadius: 16,
      padding: "13px 15px 15px" }}>
      <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 11 }}>
        {step != null && <StepDot n={step} />}
        <FieldLabel style={{ marginRight: "auto" }}>{title}</FieldLabel>
        {hint && <span style={{ fontSize: 11, color: "var(--muted)", fontFamily: "var(--font-ui)" }}>{hint}</span>}
        {action}
      </div>
      {children}
    </div>
  )
}

function StepDot({ n }) {
  return (
    <span style={{ width: 19, height: 19, borderRadius: "50%", background: "var(--accent)", color: "var(--on-accent)",
      display: "grid", placeItems: "center", fontSize: 11, fontWeight: 800, fontFamily: "var(--font-ui)", flexShrink: 0 }}>{n}</span>
  )
}

// ── result content (used both in the desktop rail and the mobile sheet) ──────
// One headline figure: YARN TO BUY = flat-swatch metres × (1 + spare). Shown as
// balls (100g, rounded up), ≈ metres and grams. No consumption / weight figure.
function ResultContent({ pieces, baseTotal, strands, byYarn, garmentYarn, stash, onStash, buffer, onBuffer,
                         ballGrams, onBallGrams, selectedId, onSelect, onShare, onSave, onAddPiece, justSaved }) {
  const t = useT()
  const stashNum = parseInt(stash) || 0
  const spare = Math.max(0, Math.min(25, buffer ?? 0))
  const spareFrac = spare / 100
  const bg = (ballGrams && ballGrams > 0) ? ballGrams : 100
  const has = baseTotal > 0
  const knit = buyFromYarns(byYarn, 0, bg)              // 0-spare: exactly enough to knit
  const buy = buyFromYarns(byYarn, spareFrac, bg)       // the one number to buy
  const extraM = buy.metres - knit.metres
  const extraBalls = buy.balls - knit.balls
  const ss = stashStatus(buy.metres, knit.metres, stash)
  const covered = ss.state === "covered"
  const tight = ss.state === "tight"
  const positive = covered || tight
  const multi = byYarn.length > 1

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
      {/* hero — the single "yarn to buy" figure */}
      <div>
        <FieldLabel>{t("Yarn to buy")}</FieldLabel>
        <div style={{ display: "flex", alignItems: "baseline", gap: 6, marginTop: 3, flexWrap: "wrap" }}>
          <span style={{ fontSize: 42, fontWeight: 800, color: "var(--accent)", lineHeight: .95,
            fontFamily: "var(--font-ui)", fontVariantNumeric: "tabular-nums" }}>{has ? buy.metres : "—"}</span>
          <span style={{ fontSize: 18, fontWeight: 600, color: "var(--muted)" }}>m</span>
          {has && <span style={{ fontSize: 11.5, color: "var(--muted)", marginLeft: 4, fontWeight: 600,
            fontVariantNumeric: "tabular-nums" }}>≈ {buy.balls} {buy.balls === 1 ? t("ball") : t("balls")} · {buy.grams} g · {t("{g}g balls", { g: bg })}</span>}
        </div>
        {has && strands === 2 && (
          <div style={{ fontSize: 11, color: "var(--muted)", marginTop: 4, fontFamily: "var(--font-ui)" }}>
            {t("2 strands held together — total single-strand metres")}</div>
        )}
      </div>

      {/* ball / skein size — sets how the metres round into balls to buy */}
      {has && onBallGrams && (
        <div style={{ background: "var(--field)", border: "1px solid var(--line)", borderRadius: 12, padding: "11px 12px" }}>
          <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
            <FieldLabel style={{ marginRight: "auto" }}>{t("Ball size")}</FieldLabel>
            <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
              <input type="number" inputMode="numeric" min={1} value={bg}
                onChange={e => { const v = parseInt(e.target.value); if (v > 0) onBallGrams(v) }}
                style={{ width: 56, background: "var(--card)", border: "1px solid var(--line-strong)", borderRadius: 8,
                  padding: "5px 8px", fontSize: 13, fontWeight: 700, color: "var(--ink)", outline: "none",
                  fontFamily: "var(--font-ui)", textAlign: "right", fontVariantNumeric: "tabular-nums" }} />
              <span style={{ fontSize: 11, color: "var(--muted)" }}>g</span>
            </div>
          </div>
          <div style={{ display: "flex", gap: 6, marginTop: 9, flexWrap: "wrap" }}>
            {[25, 50, 100, 150, 200].map(g => {
              const on = bg === g
              return (
                <button key={g} onClick={() => onBallGrams(g)}
                  style={{ flex: "1 1 0", minWidth: 44, padding: "6px 0", borderRadius: 8, cursor: "pointer",
                    border: `1px solid ${on ? "var(--accent)" : "var(--line-strong)"}`,
                    background: on ? "var(--accent-soft)" : "var(--card)",
                    color: on ? "var(--accent)" : "var(--muted)", fontWeight: 700, fontSize: 12,
                    fontFamily: "var(--font-ui)", fontVariantNumeric: "tabular-nums" }}>{g}g</button>
              )
            })}
          </div>
        </div>
      )}

      {/* spare slider — 0–25%, default 0 */}
      {has && (
        <div style={{ background: "var(--field)", border: "1px solid var(--line)", borderRadius: 12, padding: "11px 12px" }}>
          <div style={{ display: "flex", alignItems: "baseline", gap: 8 }}>
            <FieldLabel style={{ marginRight: "auto" }}>{t("Spare")}</FieldLabel>
            <span style={{ fontSize: 13, fontWeight: 800, color: spare === 0 ? "var(--muted)" : "var(--accent)",
              fontFamily: "var(--font-ui)", fontVariantNumeric: "tabular-nums" }}>+{spare}%</span>
          </div>
          <input type="range" className="yarn-range" min={0} max={25} step={1} value={spare}
            onChange={e => onBuffer(Number(e.target.value))}
            style={{ width: "100%", margin: "9px 0 7px" }} />
          <div style={{ fontSize: 11.5, color: "var(--muted)", fontFamily: "var(--font-ui)" }}>
            {spare === 0
              ? t("our estimate — add a little spare with the slider above")
              : t("+{m}m spare{b}", { m: extraM, b: extraBalls > 0 ? ` · +${extraBalls} ${extraBalls === 1 ? t("ball") : t("balls")}` : "" })}
          </div>
          <div style={{ fontSize: 10.5, color: "var(--muted)", fontFamily: "var(--font-ui)", lineHeight: 1.45,
            marginTop: 7, paddingTop: 7, borderTop: "1px solid var(--line)" }}>
            {t("An estimate — real use shifts with tension, blocking and ends. A little spare keeps you from running short.")}
          </div>
        </div>
      )}

      {/* stash check */}
      <div style={{ background: positive ? "var(--ok-soft)" : (stashNum > 0 ? "var(--accent-2-soft)" : "var(--field)"),
        border: `1px solid ${positive ? "var(--ok)" : (stashNum > 0 ? "var(--accent-2)" : "var(--line)")}`,
        borderRadius: 12, padding: "10px 12px" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
          <FieldLabel style={{ marginRight: "auto" }}>{t("Already have some?")}</FieldLabel>
          <input type="number" inputMode="numeric" placeholder="0" value={stash}
            onChange={e => onStash(e.target.value)}
            style={{ width: 72, background: "var(--card)", border: "1px solid var(--line-strong)", borderRadius: 8,
              padding: "6px 8px", fontSize: 13.5, fontWeight: 700, color: "var(--ink)", outline: "none",
              fontFamily: "var(--font-ui)", textAlign: "right", fontVariantNumeric: "tabular-nums" }} />
          <span style={{ fontSize: 11, color: "var(--muted)" }}>m</span>
        </div>
        {stashNum > 0 && has && (
          <div style={{ fontSize: 12, fontWeight: 700, marginTop: 7, fontFamily: "var(--font-ui)",
            color: positive ? "var(--ok)" : "var(--accent-2)", display: "flex", alignItems: "center", gap: 6 }}>
            {covered ? <><Icon.check s={15} /> {t("You're covered \u2014 {n}m to spare", { n: ss.spare })}</>
              : tight ? <><Icon.check s={15} /> {t("Enough to knit it \u2014 {n}m short of your spare", { n: ss.buy })}</>
              : <>{t("Buy about {n}m more", { n: ss.buy })}</>}
          </div>
        )}
      </div>

      {/* per-piece breakdown */}
      <div>
        <div style={{ display: "flex", alignItems: "center", marginBottom: 4 }}>
          <FieldLabel style={{ marginRight: "auto" }}>{t("Pieces")}</FieldLabel>
          {onAddPiece && <button onClick={onAddPiece} style={{ background: "transparent", border: "none",
            color: "var(--accent)", fontSize: 11.5, fontWeight: 700, cursor: "pointer", fontFamily: "var(--font-ui)",
            display: "inline-flex", alignItems: "center", gap: 3, padding: "2px 0" }}><Icon.plus s={13} /> {t("Add piece")}</button>}
        </div>
        <div style={{ display: "flex", flexDirection: "column" }}>
          {pieces.map((p, i) => {
            const sel = p.blockId != null && p.blockId === selectedId
            const clickable = p.blockId != null
            const stitchLabel = p.stitch && p.stitch !== "stockinette"
              ? t((CONSTRUCTION[p.stitch] || {}).label || p.stitch) : null
            return (
              <button key={i}
                onClick={() => clickable && onSelect(p.blockId)}
                style={{ display: "flex", alignItems: "center", gap: 9, padding: "9px 8px", margin: "0 -8px",
                  borderRadius: 9, border: "none", background: sel ? "var(--accent-soft)" : "transparent",
                  cursor: clickable ? "pointer" : "default", fontFamily: "var(--font-ui)", textAlign: "left", minHeight: 42 }}>
                <span style={{ width: 9, height: 9, borderRadius: 3, background: p.color, flexShrink: 0 }} />
                <span style={{ color: "var(--ink)", fontSize: 13.5, marginRight: "auto" }}>
                  {t(p.name)}{(p.quantity || 1) > 1 && <span style={{ color: "var(--muted)" }}> ×{p.quantity}</span>}
                </span>
                {stitchLabel &&
                  <span style={{ fontSize: 9.5, color: "var(--muted)", background: "var(--field-dim)",
                    border: "1px solid var(--line)", borderRadius: 5, padding: "1px 6px", whiteSpace: "nowrap" }}>
                    {stitchLabel}</span>}
                <span style={{ color: p.yarnOverride ? "var(--accent-2)" : "var(--ink)", fontSize: 13, fontWeight: 700,
                  fontVariantNumeric: "tabular-nums", flexShrink: 0, minWidth: 44, textAlign: "right" }}>{p.metres}m</span>
                {clickable && <span style={{ color: "var(--line-strong)", flexShrink: 0 }}><Icon.chevron s={15} style={{ transform: "rotate(-90deg)" }} /></span>}
              </button>
            )
          })}
        </div>
      </div>

      {/* buy summary per yarn — only when more than one yarn is in play */}
      {has && multi && (
        <div style={{ borderTop: "1px solid var(--line)", paddingTop: 12, display: "flex", flexDirection: "column", gap: 8 }}>
          {byYarn.map(({ key: k, metres, basis }) => {
            const mBuf = metres * (1 + spareFrac)
            const g = basis ? Math.round(mBuf * 100 / basis) : 0
            const balls = basis ? Math.ceil(mBuf / (basis * bg / 100)) : 0
            return (
              <div key={k} style={{ display: "flex", justifyContent: "space-between", alignItems: "center",
                gap: 8, fontFamily: "var(--font-ui)" }}>
                <span style={{ display: "inline-flex", alignItems: "center", gap: 8, fontSize: 13, color: "var(--ink)", fontWeight: 600 }}>
                  <YarnIndicator yarnKey={k} framed />{k === "custom" ? t("My yarn") : YARN[k].label}
                </span>
                <span style={{ fontSize: 12, color: "var(--muted)", fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap" }}>
                  ≈{g}g · {t("buy")} <b style={{ color: "var(--ink)" }}>{balls}×{bg}g</b></span>
              </div>
            )
          })}
        </div>
      )}

      {/* actions */}
      <div style={{ display: "flex", gap: 8, marginTop: 2 }}>
        <button onClick={onSave} disabled={!has}
          style={{ flex: 1, display: "flex", alignItems: "center", justifyContent: "center", gap: 7,
            background: justSaved ? "var(--ok-soft)" : "var(--field)",
            border: `1px solid ${justSaved ? "var(--ok)" : "var(--line-strong)"}`, borderRadius: 11, padding: "11px 0",
            color: justSaved ? "var(--ok)" : "var(--ink)", fontSize: 13.5, fontWeight: 700, cursor: !has ? "default" : "pointer",
            fontFamily: "var(--font-ui)", opacity: !has ? .5 : 1 }}>
          {justSaved ? <Icon.check /> : <Icon.save />}{justSaved ? t("Saved") : t("Save")}</button>
        <button onClick={onShare} disabled={!has}
          style={{ flex: 1, display: "flex", alignItems: "center", justifyContent: "center", gap: 7,
            background: "var(--accent)", border: "1px solid var(--accent)", borderRadius: 11, padding: "11px 0",
            color: "var(--on-accent)", fontSize: 13.5, fontWeight: 700, cursor: !has ? "default" : "pointer",
            fontFamily: "var(--font-ui)", opacity: !has ? .5 : 1 }}>
          <Icon.share />{t("Share")}</button>
      </div>

      {/* shop brand — prominent in shared screenshots */}
      <div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 5, marginTop: 4,
        paddingTop: 13, borderTop: "1px solid var(--line)" }}>
        <ShopLogo height={22} style={{ opacity: .95 }} />
        <span style={{ fontSize: 9.5, letterSpacing: ".16em", textTransform: "uppercase", color: "var(--muted)",
          fontFamily: "var(--font-ui)", fontWeight: 600 }}>{t("yarn estimator")}</span>
      </div>
    </div>
  )
}

// ── mobile sticky result bar ────────────────────────────────────────────────
function ResultBar({ baseTotal, byYarn, buffer, ballGrams, stash, onExpand }) {
  const t = useT()
  const stashNum = parseInt(stash) || 0
  const spareFrac = Math.max(0, Math.min(25, buffer ?? 0)) / 100
  const bg = (ballGrams && ballGrams > 0) ? ballGrams : 100
  const has = baseTotal > 0
  const knit = buyFromYarns(byYarn, 0, bg)
  const buy = buyFromYarns(byYarn, spareFrac, bg)
  const ss = stashStatus(buy.metres, knit.metres, stash)
  const positive = ss.state === "covered" || ss.state === "tight"
  return (
    <button onClick={onExpand} style={{ width: "100%", display: "flex", alignItems: "center", gap: 12,
      background: "var(--bg-bar)", backdropFilter: "blur(10px)", WebkitBackdropFilter: "blur(10px)",
      border: "none", borderTop: "1px solid var(--line-strong)", padding: "12px 18px calc(12px + env(safe-area-inset-bottom))",
      cursor: "pointer", fontFamily: "var(--font-ui)", textAlign: "left" }}>
      <div style={{ marginRight: "auto", minWidth: 0 }}>
        <div style={{ fontSize: 10, letterSpacing: ".09em", textTransform: "uppercase", color: "var(--muted)", fontWeight: 700 }}>{t("Yarn to buy")}</div>
        <div style={{ display: "flex", alignItems: "baseline", gap: 5 }}>
          <span style={{ fontSize: 28, fontWeight: 800, color: "var(--accent)", lineHeight: 1,
            fontVariantNumeric: "tabular-nums" }}>{has ? buy.metres : "—"}</span>
          <span style={{ fontSize: 13, fontWeight: 600, color: "var(--muted)" }}>m</span>
          {has && <span style={{ fontSize: 11, color: "var(--muted)", marginLeft: 2, fontVariantNumeric: "tabular-nums" }}>≈{buy.balls} {buy.balls === 1 ? t("ball") : t("balls")}</span>}
          {stashNum > 0 && has && <span style={{ fontSize: 11.5, fontWeight: 700, marginLeft: 4,
            color: positive ? "var(--ok)" : "var(--accent-2)" }}>{positive ? t("covered ✓") : t("+{n}m", { n: ss.buy })}</span>}
        </div>
      </div>
      <span style={{ display: "inline-flex", alignItems: "center", gap: 6, background: "var(--accent)",
        color: "var(--on-accent)", borderRadius: 11, padding: "10px 15px", fontSize: 13.5, fontWeight: 700 }}>
        {t("Details & ball size")} <Icon.chevron s={16} style={{ transform: "rotate(180deg)" }} />
      </span>
    </button>
  )
}

Object.assign(window, { GarmentPicker, GarmentChip, SizeFit, YarnRow, FineTune, SectionCard, StepDot, ResultContent, ResultBar })
