// SSPHONE UI kit — ธีม C · Navy Bold
// Shared tokens + components. Exposed on window at the end.

const T = {
  headFont: "'Prompt', sans-serif",
  bodyFont: "'Noto Sans Thai', sans-serif",
  bg: "#EEF1F7", surface: "#FFFFFF",
  border: "#E2E7F0", rowBorder: "#EFF2F8",
  text: "#101B33", sub: "#5F6B85",
  primary: "#2E63E7", primaryDark: "#1B3FA0", primaryFg: "#FFFFFF",
  accent: "#E5302F",
  chipBg: "#F1F4FA", radius: 16,
  shadow: "0 2px 4px rgba(14,27,61,.06), 0 8px 24px rgba(14,27,61,.07)",
  btnShadow: "0 6px 16px rgba(46,99,231,.40)",
  sideBg: "linear-gradient(180deg, #0E1B3D, #13265C)",
  sideText: "#9DACD2", sideMuted: "#6E7FAB",
  sideActiveBg: "linear-gradient(90deg, #2E63E7, #4F86FF)",
  okBg: "#DCF5E8", okFg: "#0E7A4D",
  warnBg: "#FFEFD6", warnFg: "#B25E09",
  infoBg: "#E2EBFF", infoFg: "#2E63E7",
  dangerBg: "#FDE3E3", dangerFg: "#C02120",
  mutedBg: "#EEF1F7", mutedFg: "#5F6B85",
  thumbA: "#EDF1F8", thumbB: "#E1E8F3",
  chart2: "#8FB3FB",
  barGrad: "linear-gradient(180deg, #4F86FF, #2350C8)",
  heroGrad: "linear-gradient(135deg, #2E63E7, #1B3FA0)",
  c1: "#1B3FA0", c2: "#4F9DF7", c3: "#11A05F", c4: "#1B7A4B",
  chartPalette: ["#2E63E7", "#E5302F", "#11A05F", "#B25E09", "#7C3AED", "#0E9AA7", "#D6336C", "#475569"],
  inputBg: "#FFFFFF", ghostBg: "#FFFFFF",
};

// ── ระบบ Theme (เปลี่ยนชุดสีทั้งแอป) ──
// snapshot ค่า default (น้ำเงินเข้ม) ไว้ก่อน เพื่อ reset ได้เสมอ
const T_DEFAULT = { ...T };

const THEMES = {
  default: { label: "น้ำเงินเข้ม", swatch: "#2E63E7", patch: {} },
  glassWhite: {
    label: "Crystal White", swatch: "#EAF2FF",
    patch: {
      bg: "linear-gradient(155deg, #F7FAFF 0%, #EAF1FB 45%, #EEF3FB 100%)",
      surface: "#FFFFFF",
      border: "#DCE7F4", rowBorder: "#EEF3FA",
      text: "#1A2F4E", sub: "#64789C",
      primary: "#3B82E0", primaryDark: "#2360BE", primaryFg: "#FFFFFF",
      accent: "#E0476B",
      chipBg: "#EEF4FD",
      shadow: "0 1px 2px rgba(40,80,150,.05), 0 6px 16px rgba(40,80,150,.07), 0 18px 48px rgba(40,80,150,.10)",
      btnShadow: "0 4px 12px rgba(59,130,224,.30), 0 1px 3px rgba(59,130,224,.20)",
      sideBg: "linear-gradient(165deg, #FFFFFF 0%, #F0F6FF 55%, #E4EEFB 100%)",
      sideText: "#3A567F", sideMuted: "#93A6C6",
      sideActiveBg: "linear-gradient(95deg, #3B82E0, #6AA6F2)",
      infoBg: "#E6F0FF", infoFg: "#2360BE",
      thumbA: "#EEF3FA", thumbB: "#DEE9F7",
      chart2: "#9CC0F7",
      barGrad: "linear-gradient(180deg, #7CB0F8, #3B82E0)",
      heroGrad: "linear-gradient(135deg, #5E9BEC 0%, #3B82E0 50%, #2360BE 100%)",
      c1: "#2360BE", c2: "#6AA6F2", c3: "#13A86A", c4: "#1B7A4B",
    },
  },
  pastelPink: {
    label: "ชมพูพาสเทล", swatch: "#EC8FB5",
    patch: {
      bg: "#FDF1F6", surface: "#FFFFFF",
      border: "#F3D7E3", rowBorder: "#F8E6EF",
      text: "#5C2C44", sub: "#9E6B81",
      primary: "#E06A9C", primaryDark: "#C24B7E", primaryFg: "#FFFFFF",
      accent: "#E0506E",
      chipBg: "#FBE7F0",
      shadow: "0 2px 6px rgba(190,80,130,.08), 0 10px 30px rgba(190,80,130,.10)",
      btnShadow: "0 6px 16px rgba(224,106,156,.38)",
      sideBg: "linear-gradient(180deg, #F7B9D2, #EF9FC0)",
      sideText: "#7A3658", sideMuted: "#BE7E9C",
      sideActiveBg: "linear-gradient(90deg, #E06A9C, #F197BC)",
      okBg: "#DCF5E8", okFg: "#0E7A4D",
      infoBg: "#FBE7F0", infoFg: "#C24B7E",
      dangerBg: "#FBE0E5", dangerFg: "#C0344F",
      thumbA: "#FaE9F1", thumbB: "#F4D7E4",
      chart2: "#F1A9C7",
      barGrad: "linear-gradient(180deg, #F39BC0, #E06A9C)",
      heroGrad: "linear-gradient(135deg, #EC7FAC, #C24B7E)",
      c1: "#C24B7E", c2: "#F0A0C0", c3: "#E07AA6", c4: "#C24B7E",
      chartPalette: ["#C24B7E", "#E06A9C", "#F0A0C0", "#B83D77", "#EC8FB5", "#D6588C", "#A82E63", "#F2B6CE"],
    },
  },
  neonDark: {
    label: "ดำ-นีออน", swatch: "#0C0C16",
    patch: {
      bg: "#0A0A12", surface: "#14141F",
      border: "#2A2A3D", rowBorder: "#20202D",
      text: "#E6FBFF", sub: "#8A93B5",
      primary: "#00E5D0", primaryDark: "#00B3A2", primaryFg: "#04121A",
      accent: "#FF3DCA",
      chipBg: "#1C1C2A",
      shadow: "0 2px 10px rgba(0,0,0,.55), 0 0 26px rgba(0,229,208,.06)",
      btnShadow: "0 0 18px rgba(0,229,208,.45)",
      sideBg: "linear-gradient(180deg, #0B0B16, #121224)",
      sideText: "#9FB6D6", sideMuted: "#5E6B8C",
      sideActiveBg: "linear-gradient(90deg, #00E5D0, #00A7E0)",
      okBg: "#0F2A1E", okFg: "#3DF5A0",
      warnBg: "#2A2110", warnFg: "#FFC24D",
      infoBg: "#0F2630", infoFg: "#00E5D0",
      dangerBg: "#2A1019", dangerFg: "#FF5C8A",
      mutedBg: "#1C1C2A", mutedFg: "#8A93B5",
      thumbA: "#1A1A26", thumbB: "#242434",
      chart2: "#00A7E0",
      barGrad: "linear-gradient(180deg, #00F0DA, #00A7E0)",
      heroGrad: "linear-gradient(135deg, #00E5D0, #7C3AED)",
      c1: "#00E5D0", c2: "#FF3DCA", c3: "#B14DFF", c4: "#39E0FF",
      chartPalette: ["#00E5D0", "#FF3DCA", "#B14DFF", "#39E0FF", "#FFE14D", "#FF6B3D", "#3DFF9E", "#00A7E0"],
      inputBg: "#10101A", ghostBg: "#1A1A28",
    },
  },
  pastelPurple: {
    label: "ม่วงพาสเทล", swatch: "#A78BE6",
    patch: {
      bg: "#F4F1FC", surface: "#FFFFFF",
      border: "#E0D7F3", rowBorder: "#EBE6F8",
      text: "#3C2C5C", sub: "#7A6B9E",
      primary: "#7C5BD0", primaryDark: "#5E3FB0", primaryFg: "#FFFFFF",
      accent: "#C24B9E",
      chipBg: "#EFE9FB",
      shadow: "0 2px 6px rgba(110,80,190,.08), 0 10px 30px rgba(110,80,190,.10)",
      btnShadow: "0 6px 16px rgba(124,91,208,.36)",
      sideBg: "linear-gradient(180deg, #C9B9F0, #B49FE8)",
      sideText: "#46367A", sideMuted: "#8A7CB0",
      sideActiveBg: "linear-gradient(90deg, #7C5BD0, #A78BE6)",
      okBg: "#E2F3E8", okFg: "#0E7A4D",
      infoBg: "#EFE9FB", infoFg: "#5E3FB0",
      dangerBg: "#FBE0EC", dangerFg: "#B0347E",
      thumbA: "#F0EAFB", thumbB: "#E4DAF4",
      chart2: "#B9A4ED",
      barGrad: "linear-gradient(180deg, #A78BE6, #7C5BD0)",
      heroGrad: "linear-gradient(135deg, #9479DE, #5E3FB0)",
      c1: "#5E3FB0", c2: "#B9A4ED", c3: "#9479DE", c4: "#5E3FB0",
      chartPalette: ["#5E3FB0", "#7C5BD0", "#B9A4ED", "#4E3196", "#A78BE6", "#8A66D6", "#3E2580", "#CBBAF2"],
    },
  },
  emerald: {
    label: "เขียวมรกต", swatch: "#10A37F",
    patch: {
      bg: "#EEF6F2", surface: "#FFFFFF",
      border: "#D2E8DF", rowBorder: "#E4F1EB",
      text: "#103D30", sub: "#5C8073",
      primary: "#10A37F", primaryDark: "#0B7A5E", primaryFg: "#FFFFFF",
      accent: "#E0763A",
      chipBg: "#E3F3EC",
      shadow: "0 2px 6px rgba(16,120,90,.08), 0 10px 30px rgba(16,120,90,.10)",
      btnShadow: "0 6px 16px rgba(16,163,127,.32)",
      sideBg: "linear-gradient(180deg, #0E3D31, #0A6B52)",
      sideText: "#E6F5EF", sideMuted: "#86B3A4",
      sideActiveBg: "linear-gradient(90deg, #10A37F, #2BC79C)",
      okBg: "#DCF3E7", okFg: "#0B7A4E",
      warnBg: "#FCEFD6", warnFg: "#B26A09",
      infoBg: "#E3F3EC", infoFg: "#0B7A5E",
      dangerBg: "#FBE3E0", dangerFg: "#C0432A",
      thumbA: "#E8F4EE", thumbB: "#D8ECE3",
      chart2: "#6FC9AE",
      barGrad: "linear-gradient(180deg, #2BC79C, #10A37F)",
      heroGrad: "linear-gradient(135deg, #14B98E, #0B7A5E)",
      c1: "#0B7A5E", c2: "#6FC9AE", c3: "#14B98E", c4: "#0B7A5E",
      chartPalette: ["#0B7A5E", "#10A37F", "#6FC9AE", "#095C47", "#2BC79C", "#1A8E6E", "#063D2F", "#9CDCC8"],
    },
  },
  turquoise: {
    label: "ฟ้าเทอร์ควอยซ์", swatch: "#06B6D4",
    patch: {
      bg: "#ECF7FA", surface: "#FFFFFF",
      border: "#CDE8EF", rowBorder: "#E0F1F5",
      text: "#0C3A45", sub: "#54818C",
      primary: "#0E9FBF", primaryDark: "#0A7A93", primaryFg: "#FFFFFF",
      accent: "#F0743A",
      chipBg: "#DFF2F7",
      shadow: "0 2px 6px rgba(10,130,155,.08), 0 10px 30px rgba(10,130,155,.10)",
      btnShadow: "0 6px 16px rgba(14,159,191,.32)",
      sideBg: "linear-gradient(180deg, #0B3A47, #0A6C82)",
      sideText: "#E4F5FA", sideMuted: "#82B4C0",
      sideActiveBg: "linear-gradient(90deg, #0E9FBF, #2BCBE6)",
      okBg: "#DBF3E9", okFg: "#0B7A4E",
      warnBg: "#FCEFD6", warnFg: "#B26A09",
      infoBg: "#DFF2F7", infoFg: "#0A7A93",
      dangerBg: "#FBE3E0", dangerFg: "#C0432A",
      thumbA: "#E6F4F8", thumbB: "#D5ECF1",
      chart2: "#62C9DD",
      barGrad: "linear-gradient(180deg, #2BCBE6, #0E9FBF)",
      heroGrad: "linear-gradient(135deg, #14B3D1, #0A7A93)",
      c1: "#0A7A93", c2: "#62C9DD", c3: "#14B3D1", c4: "#0A7A93",
      chartPalette: ["#0A7A93", "#0E9FBF", "#62C9DD", "#085E72", "#2BCBE6", "#1690AC", "#063E4C", "#97DEEC"],
    },
  },
  neonBlue: {
    label: "น้ำเงินนีออน", swatch: "#0A1024",
    patch: {
      bg: "#060A18", surface: "#0E1530",
      border: "#1E2C52", rowBorder: "#172241",
      text: "#DCEBFF", sub: "#7E92C0",
      primary: "#1FA2FF", primaryDark: "#1577D6", primaryFg: "#04101F",
      accent: "#A24BFF",
      chipBg: "#142046",
      shadow: "0 2px 10px rgba(0,0,0,.55), 0 0 26px rgba(31,162,255,.10)",
      btnShadow: "0 0 18px rgba(31,162,255,.50)",
      sideBg: "linear-gradient(180deg, #070C1E, #0C1838)",
      sideText: "#AEC4F0", sideMuted: "#5C6E9C",
      sideActiveBg: "linear-gradient(90deg, #1FA2FF, #36D2FF)",
      okBg: "#0C2A22", okFg: "#3DF5C0",
      warnBg: "#2A2410", warnFg: "#FFD24D",
      infoBg: "#0E2546", infoFg: "#36D2FF",
      dangerBg: "#2A1224", dangerFg: "#FF4D9E",
      mutedBg: "#142046", mutedFg: "#7E92C0",
      thumbA: "#121C3C", thumbB: "#1A2750",
      chart2: "#36D2FF",
      barGrad: "linear-gradient(180deg, #36D2FF, #1FA2FF)",
      heroGrad: "linear-gradient(135deg, #1FA2FF, #A24BFF)",
      c1: "#1FA2FF", c2: "#36D2FF", c3: "#A24BFF", c4: "#2BE0B0",
      chartPalette: ["#1FA2FF", "#36D2FF", "#A24BFF", "#2BE0B0", "#5B8CFF", "#7C5BFF", "#22E0FF", "#3DF5C0"],
      inputBg: "#0A1024", ghostBg: "#142046",
    },
  },
  ironman: {
    label: "Crimson Arc",  swatch: "#7A0E12",
    patch: {
      bg: "#1A0608", surface: "#2A0C10",
      border: "#5A1A1E", rowBorder: "#3D1216",
      text: "#FCE9C8", sub: "#C99A6E",
      primary: "#E6B23A", primaryDark: "#B8860D", primaryFg: "#1A0608",
      accent: "#3CC8FF",
      chipBg: "#3A1216",
      shadow: "0 2px 10px rgba(0,0,0,.6), 0 0 28px rgba(230,178,58,.10)",
      btnShadow: "0 0 18px rgba(230,178,58,.45)",
      sideBg: "linear-gradient(180deg, #7A0E12 0%, #4A0A0D 60%, #2A0608 100%)",
      sideText: "#F5D9A8", sideMuted: "#C08A5C",
      sideActiveBg: "linear-gradient(95deg, #E6B23A, #F0C95A)",
      okBg: "#0E2A1E", okFg: "#3DF5A0",
      warnBg: "#3A2410", warnFg: "#FFC24D",
      infoBg: "#0E2838", infoFg: "#3CC8FF",
      dangerBg: "#3A0E14", dangerFg: "#FF5C6E",
      mutedBg: "#3A1216", mutedFg: "#C99A6E",
      thumbA: "#3A1216", thumbB: "#4A1A1E",
      chart2: "#3CC8FF",
      barGrad: "linear-gradient(180deg, #F0C95A, #C9171D)",
      heroGrad: "linear-gradient(135deg, #C9171D 0%, #8A0E12 50%, #E6B23A 100%)",
      c1: "#E6B23A", c2: "#3CC8FF", c3: "#C9171D", c4: "#F0C95A",
      chartPalette: ["#E6B23A", "#C9171D", "#3CC8FF", "#F0C95A", "#8A0E12", "#FFD76A", "#5BD6FF", "#A8141A"],
      inputBg: "#1A0608", ghostBg: "#3A1216",
    },
  },
  loki: {
    label: "Emerald Sorcery", swatch: "#0B3D2E",
    patch: {
      bg: "#04140E", surface: "#0A2218",
      border: "#1E4D3A", rowBorder: "#143527",
      text: "#D8F5E2", sub: "#7FB89C",
      primary: "#1FB877", primaryDark: "#147A4F", primaryFg: "#03140C",
      accent: "#E8C24A",
      chipBg: "#103326",
      shadow: "0 2px 10px rgba(0,0,0,.6), 0 0 28px rgba(31,184,119,.12)",
      btnShadow: "0 0 18px rgba(31,184,119,.45)",
      sideBg: "linear-gradient(180deg, #0B3D2E 0%, #073226 55%, #04140E 100%)",
      sideText: "#BFEBD2", sideMuted: "#6FA98E",
      sideActiveBg: "linear-gradient(95deg, #1FB877, #2BE08C)",
      okBg: "#0E2A1E", okFg: "#3DF5A0",
      warnBg: "#2E2810", warnFg: "#E8C24A",
      infoBg: "#0C2A28", infoFg: "#2BE0C0",
      dangerBg: "#2E1014", dangerFg: "#FF5C6E",
      mutedBg: "#103326", mutedFg: "#7FB89C",
      thumbA: "#103326", thumbB: "#184430",
      chart2: "#E8C24A",
      barGrad: "linear-gradient(180deg, #2BE08C, #147A4F)",
      heroGrad: "linear-gradient(135deg, #147A4F 0%, #0B3D2E 50%, #E8C24A 100%)",
      c1: "#1FB877", c2: "#E8C24A", c3: "#2BE0C0", c4: "#3DF5A0",
      chartPalette: ["#1FB877", "#E8C24A", "#2BE0C0", "#3DF5A0", "#0E7A4E", "#F0D466", "#5BE0B8", "#147A4F"],
      inputBg: "#04140E", ghostBg: "#103326",
    },
  },
  blueCrystal: {
    label: "Blue Crystal", swatch: "#0A2A6B",
    patch: {
      bg: "linear-gradient(160deg, #0A1E4E 0%, #0C2D72 40%, #081A45 100%)",
      surface: "linear-gradient(165deg, #123A8C 0%, #0E2C6E 60%, #0B2459 100%)",
      border: "#C9A24A", rowBorder: "#1E4D9E",
      text: "#F4DDA0", sub: "#C9B888",
      primary: "#E8C24A", primaryDark: "#C49A2A", primaryFg: "#0A1E4E",
      accent: "#FFD86A",
      chipBg: "#143A86",
      shadow: "0 1px 2px rgba(0,0,0,.4), 0 8px 24px rgba(6,18,55,.55), 0 18px 50px rgba(10,40,110,.45), inset 0 1px 0 rgba(190,160,90,.30)",
      btnShadow: "0 4px 14px rgba(232,194,74,.45), 0 1px 3px rgba(0,0,0,.4), inset 0 1px 0 rgba(255,240,190,.55)",
      sideBg: "linear-gradient(180deg, #0C2C74 0%, #0A2058 55%, #06143A 100%)",
      sideText: "#F0D898", sideMuted: "#A89A6E",
      sideActiveBg: "linear-gradient(95deg, #E8C24A, #F5D976)",
      okBg: "#0C2A2E", okFg: "#5FE0C0",
      warnBg: "#2E2810", warnFg: "#FFD24D",
      infoBg: "#12347E", infoFg: "#FFD86A",
      dangerBg: "#2E1422", dangerFg: "#FF6E8E",
      mutedBg: "#143A86", mutedFg: "#C9B888",
      thumbA: "#143A86", thumbB: "#1A4598",
      chart2: "#5B8CE8",
      barGrad: "linear-gradient(180deg, #F5D976, #C49A2A)",
      heroGrad: "linear-gradient(135deg, #1A52B8 0%, #0C2D72 50%, #E8C24A 100%)",
      c1: "#E8C24A", c2: "#5B8CE8", c3: "#F5D976", c4: "#3C6BD0",
      chartPalette: ["#E8C24A", "#5B8CE8", "#F5D976", "#3C6BD0", "#C49A2A", "#7CA6F0", "#FFD86A", "#1A52B8"],
      inputBg: "#0A2058", ghostBg: "#143A86",
    },
  },
};
const THEME_KEY = "ssphone_theme";
const _themeListeners = new Set();
function applyTheme(key) {
  const th = THEMES[key] || THEMES.default;
  Object.assign(T, T_DEFAULT, th.patch);
  try { localStorage.setItem(THEME_KEY, key); } catch (e) {}
}
function currentThemeKey() {
  try { return localStorage.getItem(THEME_KEY) || "default"; } catch (e) { return "default"; }
}
function setTheme(key) {
  applyTheme(key);
  _themeListeners.forEach((fn) => fn(key));
}
function useTheme() {
  const [key, setKey] = React.useState(currentThemeKey());
  React.useEffect(() => {
    _themeListeners.add(setKey);
    return () => _themeListeners.delete(setKey);
  }, []);
  return [key, setTheme];
}
// ใช้ theme ที่บันทึกไว้ทันทีตอนโหลด
applyTheme(currentThemeKey());

const fmtMoney = (n) => "฿" + (n || 0).toLocaleString("th-TH");
const fmtDate = (ts) => new Date(ts).toLocaleDateString("th-TH", { day: "numeric", month: "short", year: "2-digit" });
const fmtTime = (ts) => new Date(ts).toLocaleTimeString("th-TH", { hour: "2-digit", minute: "2-digit" });
const fmtDT = (ts) => fmtDate(ts) + " " + fmtTime(ts);

/* ── primitives ── */
function Card({ children, style, onClick, hover }) {
  return (
    <div onClick={onClick} className={hover ? "ss-card ss-hover" : "ss-card"} style={{
      background: T.surface, border: `1px solid ${T.border}`, borderRadius: T.radius,
      boxShadow: T.shadow, ...style,
    }}>{children}</div>
  );
}

function Btn({ children, kind = "primary", sm, style, onClick, disabled, type }) {
  const kinds = {
    primary: { background: T.primary, color: "#fff", border: "1px solid transparent", boxShadow: T.btnShadow },
    soft: { background: T.infoBg, color: T.primary, border: "1px solid transparent" },
    ghost: { background: T.ghostBg, color: T.text, border: `1px solid ${T.border}` },
    danger: { background: T.accent, color: "#fff", border: "1px solid transparent", boxShadow: "0 6px 16px rgba(229,48,47,.35)" },
    dangerSoft: { background: T.dangerBg, color: T.dangerFg, border: "1px solid transparent" },
    ok: { background: "#11A05F", color: "#fff", border: "1px solid transparent", boxShadow: "0 6px 16px rgba(17,160,95,.35)" },
    warn: { background: "#E8943A", color: "#fff", border: "1px solid transparent", boxShadow: "0 6px 16px rgba(232,148,58,.35)" },
  };
  return (
    <button type={type || "button"} disabled={disabled} onClick={onClick} className="ss-btn" style={{
      ...kinds[kind], fontFamily: T.bodyFont, fontWeight: 700,
      fontSize: sm ? 12.5 : 14, padding: sm ? "6px 12px" : "10px 18px",
      borderRadius: sm ? 9 : 11, cursor: disabled ? "not-allowed" : "pointer",
      opacity: disabled ? 0.45 : 1, display: "inline-flex", alignItems: "center", gap: 7,
      whiteSpace: "nowrap", ...style,
    }}>{children}</button>
  );
}

function Badge({ tone = "muted", children, sm }) {
  const map = {
    ok: [T.okBg, T.okFg], warn: [T.warnBg, T.warnFg], info: [T.infoBg, T.infoFg],
    danger: [T.dangerBg, T.dangerFg], muted: [T.mutedBg, T.mutedFg],
  };
  const [bg, fg] = map[tone] || map.muted;
  return <span style={{ background: bg, color: fg, fontSize: sm ? 10.5 : 11.5, fontWeight: 700, padding: sm ? "2px 8px" : "3px 10px", borderRadius: 999, whiteSpace: "nowrap" }}>{children}</span>;
}

const REPAIR_TONES = { "รับเครื่องแล้ว": "info", "กำลังซ่อม": "info", "รออะไหล่": "warn", "ซ่อมเสร็จ": "ok", "รอลูกค้ารับ": "ok", "ส่งมอบแล้ว": "muted", "ยกเลิก": "danger" };

function hueOf(str) { let h = 0; for (const c of str) h = (h * 31 + c.charCodeAt(0)) % 360; return h; }

function Avatar({ name, img, size = 32, hue }) {
  if (img) return <img src={img} alt={name} style={{ width: size, height: size, borderRadius: "50%", objectFit: "cover", flex: "none" }} />;
  const h = hue != null ? hue : hueOf(name || "?");
  return (
    <div style={{
      width: size, height: size, borderRadius: "50%", flex: "none",
      background: `linear-gradient(135deg, oklch(0.72 0.13 ${h}), oklch(0.55 0.15 ${(h + 50) % 360}))`,
      color: "#fff", display: "flex", alignItems: "center", justifyContent: "center",
      fontSize: size * 0.42, fontWeight: 700, fontFamily: T.headFont,
    }}>{(name || "?")[0]}</div>
  );
}

function ProductImg({ p, size = 38, radius = 9 }) {
  if (p && p.img) return <img src={p.img} alt={p.name} style={{ width: size, height: size, borderRadius: radius, objectFit: "cover", flex: "none", border: `1px solid ${T.border}` }} />;
  return (
    <div style={{
      width: size, height: size, borderRadius: radius, flex: "none",
      background: `repeating-linear-gradient(45deg, ${T.thumbA} 0 4px, ${T.thumbB} 4px 8px)`,
      border: `1px solid ${T.border}`, display: "flex", alignItems: "center", justifyContent: "center",
      color: "#A8B4CC", fontSize: size * 0.42,
    }}>{p && p.cat === "เครื่อง" ? "▯" : "◌"}</div>
  );
}

/* ── form ── */
function Field({ label, children, style, req }) {
  return (
    <label style={{ display: "flex", flexDirection: "column", gap: 5, fontSize: 12.5, fontWeight: 600, color: T.sub, ...style }}>
      <span>{label} {req && <span style={{ color: T.accent }}>*</span>}</span>
      {children}
    </label>
  );
}

function inputStyle() {
  return {
    fontFamily: T.bodyFont, fontSize: 14, color: T.text, background: T.inputBg,
    border: `1.5px solid ${T.border}`, borderRadius: 10, padding: "9px 12px",
    outline: "none", width: "100%", boxSizing: "border-box", transition: "border-color .15s, box-shadow .15s",
  };
}

function TextInput(props) { return <input {...props} className="ss-input" style={{ ...inputStyle(), ...props.style }} />; }
function NumInput(props) { return <input type="number" {...props} className="ss-input" style={{ ...inputStyle(), ...props.style }} />; }
function TextArea(props) { return <textarea {...props} className="ss-input" style={{ ...inputStyle(), resize: "vertical", minHeight: 64, ...props.style }} />; }
function Select({ options, value, onChange, style, placeholder }) {
  return (
    <select className="ss-input" value={value} onChange={(e) => onChange(e.target.value)} style={{ ...inputStyle(), cursor: "pointer", ...style }}>
      {placeholder && <option value="">{placeholder}</option>}
      {options.map((o) => <option key={o.value != null ? o.value : o} value={o.value != null ? o.value : o}>{o.label != null ? o.label : o}</option>)}
    </select>
  );
}

function SearchBox({ value, onChange, placeholder, style, autoFocus }) {
  return (
    <div style={{ position: "relative", ...style }}>
      <span style={{ position: "absolute", left: 12, top: "50%", transform: "translateY(-50%)", color: T.sub, fontSize: 14 }}>⌕</span>
      <input className="ss-input" autoFocus={autoFocus} value={value} onChange={(e) => onChange(e.target.value)} placeholder={placeholder}
        style={{ ...inputStyle(), paddingLeft: 32 }} />
    </div>
  );
}

/* ── modal ── */
function Modal({ open, onClose, title, children, width = 560, footer, noPad }) {
  if (!open) return null;
  return (
    <div className="ss-modal-bg" onMouseDown={(e) => { if (e.target === e.currentTarget) onClose(); }}
      style={{ position: "fixed", inset: 0, background: "rgba(10,18,42,.45)", backdropFilter: "blur(3px)", zIndex: 90, display: "flex", alignItems: "center", justifyContent: "center", padding: 20 }}>
      <div className="ss-modal" style={{ background: T.surface, borderRadius: 18, width, maxWidth: "94vw", maxHeight: "90vh", display: "flex", flexDirection: "column", boxShadow: "0 24px 64px rgba(10,18,42,.35)", overflow: "hidden" }}>
        <div style={{ display: "flex", alignItems: "center", padding: "16px 22px 12px", flex: "none" }}>
          <div style={{ fontFamily: T.headFont, fontSize: 17, fontWeight: 700 }}>{title}</div>
          <button onClick={onClose} className="ss-btn" style={{ marginLeft: "auto", border: "none", background: T.chipBg, borderRadius: 8, width: 30, height: 30, cursor: "pointer", fontSize: 14, color: T.sub }}>✕</button>
        </div>
        <div style={{ padding: noPad ? 0 : "4px 22px 20px", overflowY: "auto", flex: 1 }}>{children}</div>
        {footer && <div style={{ padding: "14px 22px", borderTop: `1px solid ${T.border}`, display: "flex", gap: 10, justifyContent: "flex-end", flexWrap: "wrap", flex: "none", background: T.chipBg }}>{footer}</div>}
      </div>
    </div>
  );
}

/* ── toast ── */
const ToastCtx = React.createContext(null);
function ToastProvider({ children }) {
  const [toasts, setToasts] = React.useState([]);
  const push = React.useCallback((msg, tone = "ok") => {
    const id = Math.random().toString(36).slice(2);
    setToasts((t) => [...t, { id, msg, tone }]);
    setTimeout(() => setToasts((t) => t.filter((x) => x.id !== id)), 3400);
  }, []);
  React.useEffect(() => { window.__ssToast = push; }, [push]);
  return (
    <ToastCtx.Provider value={push}>
      {children}
      <div style={{ position: "fixed", bottom: 22, left: "50%", transform: "translateX(-50%)", zIndex: 200, display: "flex", flexDirection: "column", gap: 8, alignItems: "center" }}>
        {toasts.map((t) => (
          <div key={t.id} className="ss-toast" style={{
            background: t.tone === "ok" ? "#0F2D1E" : t.tone === "danger" ? "#3A1212" : "#101B33",
            color: "#fff", borderRadius: 12, padding: "11px 18px", fontSize: 13.5, fontWeight: 600,
            boxShadow: "0 12px 32px rgba(10,18,42,.4)", display: "flex", gap: 9, alignItems: "center",
          }}>
            <span style={{ color: t.tone === "ok" ? "#4ADE80" : t.tone === "danger" ? "#F87171" : "#8FB3FB", fontSize: 15 }}>
              {t.tone === "ok" ? "✓" : t.tone === "danger" ? "✕" : "ℹ"}
            </span>{t.msg}
          </div>
        ))}
      </div>
    </ToastCtx.Provider>
  );
}
const useToast = () => React.useContext(ToastCtx);

/* ── image upload (data URL) ── */
function ImgUpload({ value, onChange, size = 72, shape = "rounded", hint }) {
  const ref = React.useRef();
  const radius = shape === "circle" ? "50%" : 12;
  const pick = (e) => {
    const f = e.target.files[0];
    if (!f) return;
    const rd = new FileReader();
    rd.onload = () => {
      // downscale to keep localStorage small
      const img = new Image();
      img.onload = () => {
        const c = document.createElement("canvas");
        const s = Math.min(1, 1400 / Math.max(img.width, img.height));
        c.width = Math.round(img.width * s); c.height = Math.round(img.height * s);
        c.getContext("2d").drawImage(img, 0, 0, c.width, c.height);
        onChange(c.toDataURL("image/jpeg", 0.85));
      };
      img.src = rd.result;
    };
    rd.readAsDataURL(f);
    e.target.value = "";
  };
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
      <label className="ss-hover" style={{
        width: size, height: size, borderRadius: radius, cursor: "pointer", flex: "none",
        background: value ? `url(${value}) center/cover` : `repeating-linear-gradient(45deg, ${T.thumbA} 0 5px, ${T.thumbB} 5px 10px)`,
        border: `1.5px dashed ${value ? "transparent" : "#B6C2DA"}`, display: "flex", alignItems: "center", justifyContent: "center",
        color: "#8A98B8", fontSize: 20,
      }}>{!value && "+"}
        <input ref={ref} type="file" accept="image/*" style={{ display: "none" }} onChange={pick} />
      </label>
      <div style={{ fontSize: 11.5, color: T.sub, lineHeight: 1.5 }}>
        {hint || "คลิกเพื่ออัปโหลดรูป"}<br />
        {value && <a style={{ color: T.accent, cursor: "pointer", fontWeight: 600 }} onClick={() => onChange(null)}>ลบรูป</a>}
      </div>
    </div>
  );
}

/* ── multi-image upload (array of data URLs) ── */
function MultiImgUpload({ value, onChange, size = 56, hint, onView }) {
  const ref = React.useRef();
  const imgs = Array.isArray(value) ? value : (value ? [value] : []);
  const pick = (e) => {
    const files = [...e.target.files];
    if (!files.length) return;
    let pending = files.length;
    const out = [];
    files.forEach((file) => {
      const rd = new FileReader();
      rd.onload = () => {
        const img = new Image();
        img.onload = () => {
          const c = document.createElement("canvas");
          const s = Math.min(1, 1400 / Math.max(img.width, img.height));
          c.width = Math.round(img.width * s); c.height = Math.round(img.height * s);
          c.getContext("2d").drawImage(img, 0, 0, c.width, c.height);
          out.push(c.toDataURL("image/jpeg", 0.85));
          if (--pending === 0) onChange([...imgs, ...out]);
        };
        img.src = rd.result;
      };
      rd.readAsDataURL(file);
    });
    e.target.value = "";
  };
  const removeAt = (i) => onChange(imgs.filter((_, idx) => idx !== i));
  return (
    <div style={{ display: "flex", flexWrap: "wrap", gap: 8, alignItems: "center" }}>
      {imgs.map((src, i) => (
        <div key={i} style={{ position: "relative", width: size, height: size, flex: "none" }}>
          <div onClick={() => onView && onView(i)} style={{ width: size, height: size, borderRadius: 12, background: `url(${src}) center/cover`, cursor: onView ? "zoom-in" : "default" }}></div>
          <div onClick={() => removeAt(i)} title="ลบรูป" style={{ position: "absolute", top: -6, right: -6, width: 18, height: 18, borderRadius: "50%", background: "#E0444F", color: "#fff", fontSize: 12, lineHeight: "18px", textAlign: "center", cursor: "pointer", boxShadow: "0 1px 3px rgba(0,0,0,.3)" }}>×</div>
        </div>
      ))}
      <label className="ss-hover" style={{
        width: size, height: size, borderRadius: 12, cursor: "pointer", flex: "none",
        background: `repeating-linear-gradient(45deg, ${T.thumbA} 0 5px, ${T.thumbB} 5px 10px)`,
        border: "1.5px dashed #B6C2DA", display: "flex", alignItems: "center", justifyContent: "center", color: "#8A98B8", fontSize: 20,
      }}>+
        <input ref={ref} type="file" accept="image/*" multiple style={{ display: "none" }} onChange={pick} />
      </label>
      {imgs.length > 0
        ? <span style={{ fontSize: 11, color: T.sub }}>{imgs.length} รูป</span>
        : (hint && <div style={{ fontSize: 11.5, color: T.sub, lineHeight: 1.5, maxWidth: 160 }}>{hint}</div>)}
    </div>
  );
}

/* ── lightbox: ดูรูปขนาดใหญ่ + เลื่อนดูหลายรูป ── */
function Lightbox({ images, index, onClose, onIndex }) {
  const imgs = images || [];
  React.useEffect(() => {
    if (index == null) return;
    const onKey = (e) => {
      if (e.key === "Escape") onClose();
      else if (e.key === "ArrowRight") onIndex((index + 1) % imgs.length);
      else if (e.key === "ArrowLeft") onIndex((index - 1 + imgs.length) % imgs.length);
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [index, imgs.length]);
  if (index == null || !imgs.length) return null;
  const go = (d, e) => { e.stopPropagation(); onIndex((index + d + imgs.length) % imgs.length); };
  const multi = imgs.length > 1;
  return (
    <div onClick={onClose} style={{ position: "fixed", inset: 0, zIndex: 5000, background: "rgba(10,14,24,.86)", display: "flex", alignItems: "center", justifyContent: "center", backdropFilter: "blur(3px)", animation: "ss-fade .15s ease" }}>
      <div onClick={(e) => e.stopPropagation()} style={{ width: 44, height: 44, position: "absolute", top: 20, right: 22, borderRadius: "50%", background: "rgba(255,255,255,.14)", color: "#fff", fontSize: 24, display: "flex", alignItems: "center", justifyContent: "center", cursor: "pointer" }} onMouseDown={onClose}>×</div>
      {multi && <div onClick={(e) => go(-1, e)} style={{ position: "absolute", left: 18, top: "50%", transform: "translateY(-50%)", width: 52, height: 52, borderRadius: "50%", background: "rgba(255,255,255,.14)", color: "#fff", fontSize: 30, display: "flex", alignItems: "center", justifyContent: "center", cursor: "pointer" }}>‹</div>}
      <img src={imgs[index]} alt="" style={{ width: "min(96vw, 1400px)", maxHeight: "94vh", objectFit: "contain", borderRadius: 12, boxShadow: "0 20px 60px rgba(0,0,0,.5)" }} />
      {multi && <div onClick={(e) => go(1, e)} style={{ position: "absolute", right: 18, top: "50%", transform: "translateY(-50%)", width: 52, height: 52, borderRadius: "50%", background: "rgba(255,255,255,.14)", color: "#fff", fontSize: 30, display: "flex", alignItems: "center", justifyContent: "center", cursor: "pointer" }}>›</div>}
      {multi && <div style={{ position: "absolute", bottom: 26, left: "50%", transform: "translateX(-50%)", display: "flex", gap: 7, alignItems: "center" }}>
        {imgs.map((_, i) => <span key={i} onClick={(e) => { e.stopPropagation(); onIndex(i); }} style={{ width: i === index ? 22 : 8, height: 8, borderRadius: 99, background: i === index ? "#fff" : "rgba(255,255,255,.45)", cursor: "pointer", transition: "width .2s" }}></span>)}
      </div>}
      {multi && <div style={{ position: "absolute", top: 24, left: "50%", transform: "translateX(-50%)", color: "#fff", fontSize: 13, fontWeight: 600, background: "rgba(255,255,255,.12)", padding: "5px 13px", borderRadius: 99 }}>{index + 1} / {imgs.length}</div>}
    </div>
  );
}

/* ── count-up number ── */
function useCountUp(target, dur = 700) {
  const [v, setV] = React.useState(0);
  React.useEffect(() => {
    let raf, t0;
    const step = (t) => {
      if (!t0) t0 = t;
      const p = Math.min(1, (t - t0) / dur);
      setV(target * (1 - Math.pow(1 - p, 3)));
      if (p < 1) raf = requestAnimationFrame(step);
    };
    raf = requestAnimationFrame(step);
    // fallback: if rAF never fires (hidden/frozen timeline), snap to target
    const tm = setTimeout(() => setV(target), dur + 250);
    return () => { cancelAnimationFrame(raf); clearTimeout(tm); };
  }, [target, dur]);
  return v;
}

function StatCard({ label, value, money, sub, accent, hero, delay = 0 }) {
  const n = useCountUp(value);
  const disp = money ? fmtMoney(Math.round(n)) : Math.round(n).toLocaleString("th-TH");
  return (
    <Card className="ss-card" style={{
      padding: "15px 18px", animation: `ss-rise .45s ${delay}ms cubic-bezier(.2,.9,.3,1.2) backwards`,
      ...(hero ? { background: T.heroGrad, color: "#fff", border: "1px solid transparent" } : {}),
    }}>
      <div style={{ fontSize: 12.5, fontWeight: 600, color: hero ? "rgba(255,255,255,.82)" : T.sub }}>{label}</div>
      <div style={{ fontFamily: T.headFont, fontSize: 27, fontWeight: 800, margin: "3px 0 2px" }}>{disp}{!money && value > 0 && typeof sub === "undefined" ? "" : ""}</div>
      <div style={{ fontSize: 11.5, color: hero ? "rgba(255,255,255,.82)" : accent ? T.accent : T.sub, fontWeight: 600 }}>{sub}</div>
    </Card>
  );
}

/* ── misc ── */
function EmptyState({ icon = "◎", text, sub }) {
  return (
    <div style={{ textAlign: "center", padding: "36px 20px", color: T.sub }}>
      <div style={{ fontSize: 34, opacity: 0.5, marginBottom: 8 }}>{icon}</div>
      <div style={{ fontSize: 14, fontWeight: 700 }}>{text}</div>
      {sub && <div style={{ fontSize: 12.5, marginTop: 4 }}>{sub}</div>}
    </div>
  );
}

function SectionTitle({ children, right }) {
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 10 }}>
      <div style={{ fontFamily: T.headFont, fontSize: 15.5, fontWeight: 700 }}>{children}</div>
      {right && <div style={{ marginLeft: "auto" }}>{right}</div>}
    </div>
  );
}

// ── line chart (SVG, multi-series, animated) ──
function LineChart({ series, labels, height = 170, money = true, yLabel }) {
  const W = 720, H = height, padL = 52, padR = 14, padT = 14, padB = 26;
  const n = labels.length;
  const allVals = series.flatMap((s) => s.data);
  const maxV = Math.max(1, ...allVals);
  const niceMax = Math.ceil(maxV / 4) * 4 || 4;
  const x = (i) => padL + (n <= 1 ? 0 : (i * (W - padL - padR)) / (n - 1));
  const y = (v) => padT + (1 - v / niceMax) * (H - padT - padB);
  const fmtY = (v) => money ? (v >= 1000 ? (v / 1000).toFixed(v >= 10000 ? 0 : 1) + "k" : v) : v;
  const [shown, setShown] = React.useState(false);
  React.useEffect(() => {
    const raf = requestAnimationFrame(() => setShown(true));
    // fallback: if rAF is throttled/frozen, still reveal the lines
    const tm = setTimeout(() => setShown(true), 80);
    return () => { cancelAnimationFrame(raf); clearTimeout(tm); };
  }, []);

  const ticks = [0, 1, 2, 3, 4].map((k) => (niceMax / 4) * k);
  const linePath = (data) => data.map((v, i) => `${i === 0 ? "M" : "L"}${x(i).toFixed(1)},${y(v).toFixed(1)}`).join(" ");

  return (
    <div style={{ width: "100%" }}>
      <svg viewBox={`0 0 ${W} ${H}`} style={{ width: "100%", height: H, overflow: "visible" }}>
        {ticks.map((t, i) => (
          <g key={i}>
            <line x1={padL} y1={y(t)} x2={W - padR} y2={y(t)} stroke={T.rowBorder} strokeWidth="1" />
            <text x={padL - 8} y={y(t) + 3.5} textAnchor="end" fontSize="10" fill={T.sub}>{fmtY(t)}</text>
          </g>
        ))}
        {series.map((s, si) => {
          const area = `${linePath(s.data)} L${x(n - 1)},${y(0)} L${x(0)},${y(0)} Z`;
          return (
            <g key={si}>
              {s.fill !== false && <path d={area} fill={s.color} opacity={shown ? 0.10 : 0} style={{ transition: "opacity .6s" }} />}
              <path d={linePath(s.data)} fill="none" stroke={s.color} strokeWidth={s.width || 2.5} strokeLinejoin="round" strokeLinecap="round"
                style={{ strokeDasharray: 2000, strokeDashoffset: shown ? 0 : 2000, transition: `stroke-dashoffset 1s ${si * 120}ms cubic-bezier(.4,.8,.3,1)` }} />
              {s.data.map((v, i) => (
                <circle key={i} cx={x(i)} cy={y(v)} r={shown ? 3 : 0} fill="#fff" stroke={s.color} strokeWidth="2"
                  style={{ transition: `r .3s ${si * 120 + i * 50 + 400}ms` }} />
              ))}
            </g>
          );
        })}
        {labels.map((l, i) => (
          <text key={i} x={x(i)} y={H - 8} textAnchor="middle" fontSize="10.5" fill={T.sub} fontWeight="600">{l}</text>
        ))}
      </svg>
    </div>
  );
}

// ── grouped vertical bar chart ──
function BarChart({ groups, series, height = 180, money = true }) {
  const allVals = series.flatMap((s) => s.data);
  const maxV = Math.max(1, ...allVals);
  const niceMax = Math.ceil(maxV / 4) * 4 || 4;
  const [shown, setShown] = React.useState(false);
  React.useEffect(() => {
    const raf = requestAnimationFrame(() => setShown(true));
    const tm = setTimeout(() => setShown(true), 80);
    return () => { cancelAnimationFrame(raf); clearTimeout(tm); };
  }, []);
  const ticks = [4, 3, 2, 1, 0].map((k) => (niceMax / 4) * k);
  const fmtY = (v) => v >= 1000 ? (v / 1000).toFixed(v >= 10000 ? 0 : 1) + "k" : Math.round(v);

  return (
    <div style={{ display: "flex", gap: 8, height }}>
      <div style={{ display: "flex", flexDirection: "column", justifyContent: "space-between", paddingBottom: 20, fontSize: 10, color: T.sub, textAlign: "right", width: 34, flex: "none" }}>
        {ticks.map((t, i) => <div key={i}>{money ? fmtY(t) : Math.round(t)}</div>)}
      </div>
      <div style={{ flex: 1, display: "flex", flexDirection: "column" }}>
        <div style={{ flex: 1, display: "flex", alignItems: "stretch", position: "relative", borderLeft: `1px solid ${T.border}`, borderBottom: `1px solid ${T.border}` }}>
          {ticks.slice(0, 4).map((t, i) => (
            <div key={i} style={{ position: "absolute", left: 0, right: 0, top: `${(i / 4) * 100}%`, borderTop: `1px dashed ${T.rowBorder}` }}></div>
          ))}
          {groups.map((g, gi) => (
            <div key={gi} style={{ flex: 1, display: "flex", alignItems: "flex-end", justifyContent: "center", gap: Math.max(2, 7 - series.length), padding: "0 4px", position: "relative", zIndex: 1 }}>
              {series.map((s, si) => {
                const v = s.data[gi] || 0;
                const h = (v / niceMax) * 100;
                return (
                  <div key={si} title={`${s.label}: ${money ? "฿" + v.toLocaleString("th-TH") : v}`}
                    style={{ flex: 1, maxWidth: 26, height: "100%", display: "flex", flexDirection: "column", justifyContent: "flex-end" }}>
                    <div style={{ height: shown ? `${h}%` : "0%", minHeight: v > 0 ? 3 : 0, background: s.color, borderRadius: "5px 5px 0 0", transition: `height .6s cubic-bezier(.2,.8,.3,1.1) ${gi * 50 + si * 40}ms` }}></div>
                  </div>
                );
              })}
            </div>
          ))}
        </div>
        <div style={{ display: "flex", height: 20, paddingTop: 4 }}>
          {groups.map((g, gi) => <div key={gi} style={{ flex: 1, textAlign: "center", fontSize: 10.5, color: T.sub, fontWeight: 600 }}>{g}</div>)}
        </div>
      </div>
    </div>
  );
}

function ChartLegend({ items }) {
  return (
    <div style={{ display: "flex", gap: 14, fontSize: 11.5, color: T.sub, flexWrap: "wrap" }}>
      {items.map((it) => (
        <span key={it.label} style={{ display: "flex", alignItems: "center", gap: 6 }}>
          <span style={{ width: 14, height: 4, borderRadius: 2, background: it.color }}></span>{it.label}
        </span>
      ))}
    </div>
  );
}

function BarcodeArt({ value, height = 38 }) {
  // Simple Code39 barcode
  const encoded = { '0': '101001101', '1': '110100101', '2': '101100101', '3': '110110010', '4': '101001110', '5': '110100110', '6': '101110010', '7': '101010011', '8': '110101001', '9': '101101001', 'A': '110010101', 'B': '101011010', 'C': '110101010', '-': '100001101', '.': '110000101', ' ': '100100101', '*': '100001001' };
  const str = String(value).toUpperCase().substring(0, 16);
  let pattern = '100001001';
  for (const c of str) pattern += (encoded[c] || '101010101') + '0';
  pattern += '100001001';
  const rects = [];
  let x = 0;
  for (let i = 0; i < pattern.length; i++) {
    const w = pattern[i] === '1' ? 3 : 1;
    if (pattern[i] === '1') rects.push({ x, w: 3 });
    x += w;
  }
  const rectElements = [];
  for (let i = 0; i < rects.length; i++) {
    rectElements.push(React.createElement('rect', { key: i, x: rects[i].x, y: '0', width: rects[i].w, height: height, fill: '#000' }));
  }
  return React.createElement('div', { style: { display: 'inline-flex', flexDirection: 'column', alignItems: 'center', gap: 4 } },
    React.createElement('svg', { width: '100%', height: height, viewBox: `0 0 ${x} ${height}`, preserveAspectRatio: 'xMidYMid meet', style: { border: '1px solid #ddd', background: '#fff', maxWidth: '100%', display: 'block' } },
      rectElements
    ),
    React.createElement('div', { style: { fontSize: 11, letterSpacing: 1, color: '#000', fontFamily: 'monospace', fontWeight: 600 } }, str)
  );
}

/* ── ThemePicker — ไอคอนจานสีมุมขวาบน ── */
function ThemePicker() {
  const [themeKey, setThemeKey] = useTheme();
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);
  React.useEffect(() => {
    const h = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener("mousedown", h);
    return () => document.removeEventListener("mousedown", h);
  }, []);
  const keys = Object.keys(THEMES);
  return (
    <div ref={ref} style={{ position: "relative" }}>
      <div title="เปลี่ยนธีมสี" onClick={() => setOpen((o) => !o)}
        style={{ width: 36, height: 36, borderRadius: 10, background: T.chipBg, border: `1px solid ${T.border}`, display: "flex", alignItems: "center", justifyContent: "center", fontSize: 16, cursor: "pointer", color: T.primary }}>
        ◑
      </div>
      {open && (
        <div style={{ position: "absolute", top: 44, right: 0, width: 220, background: T.surface, border: `1px solid ${T.border}`, borderRadius: 14, boxShadow: T.shadow, padding: 8, zIndex: 50 }}>
          <div style={{ fontSize: 11.5, fontWeight: 700, color: T.sub, padding: "4px 8px 8px" }}>เลือกธีมสี</div>
          <div style={{ display: "flex", flexDirection: "column", gap: 2 }}>
            {keys.map((k) => {
              const th = THEMES[k];
              const active = themeKey === k;
              return (
                <div key={k} onClick={() => { setThemeKey(k); setOpen(false); }}
                  style={{ display: "flex", alignItems: "center", gap: 10, padding: "9px 10px", borderRadius: 10, cursor: "pointer", background: active ? T.chipBg : "transparent", border: `1.5px solid ${active ? T.primary : "transparent"}` }}>
                  <span style={{ width: 20, height: 20, borderRadius: "50%", flex: "none", background: th.swatch, border: "1px solid rgba(0,0,0,.12)" }}></span>
                  <span style={{ fontSize: 13, fontWeight: active ? 700 : 500, color: T.text }}>{th.label}</span>
                  {active && <span style={{ marginLeft: "auto", color: T.primary, fontWeight: 800, fontSize: 12 }}>✓</span>}
                </div>
              );
            })}
          </div>
        </div>
      )}
    </div>
  );
}

Object.assign(window, {
  T, fmtMoney, fmtDate, fmtTime, fmtDT, hueOf,
  THEMES, setTheme, useTheme, currentThemeKey, applyTheme, ThemePicker,
  Card, Btn, Badge, Avatar, ProductImg, Field, TextInput, NumInput, TextArea, Select, SearchBox,
  Modal, ToastProvider, useToast, ImgUpload, MultiImgUpload, Lightbox, useCountUp, StatCard, EmptyState, SectionTitle,
  LineChart, BarChart, ChartLegend,
  REPAIR_TONES, ssInputStyle: inputStyle,
});