/* NutriOS 6 — core: theme, api, helpers, UI atoms */
window.NX = window.NX || {};
(function(NX){
const { useState, useEffect, useRef, useCallback, createContext, useContext } = React;

/* ── API ── */
const API = "/api";
let TOKEN = localStorage.getItem("nx_token") || null;
NX.setToken = (t) => { TOKEN = t; t ? localStorage.setItem("nx_token", t) : localStorage.removeItem("nx_token"); };
NX.getToken = () => TOKEN;

async function req(method, path, body) {
  const h = { "Content-Type": "application/json" };
  if (TOKEN) h.Authorization = "Bearer " + TOKEN;
  const res = await fetch(API + path, { method, headers: h, body: body ? JSON.stringify(body) : undefined });
  const data = await res.json().catch(() => ({}));
  if (!res.ok) throw new Error(data.error || ("שגיאה " + res.status));
  return data;
}
NX.api = {
  login:    (email, password) => req("POST", "/auth/login", { email, password }),
  register: (d) => req("POST", "/auth/register", d),
  me:       () => req("GET", "/auth/me"),
  updateProfile: (d) => req("PUT", "/auth/profile", d).catch(()=>req("PUT","/auth/update",d)),
  changePassword: (current_password, new_password) => req("PUT", "/auth/password", { current_password, new_password }),
  plan:     () => req("GET", "/plan").catch(()=>null),
  // train
  exercises: () => req("GET", "/train/exercises"),
  programs:  () => req("GET", "/train/programs"),
  logWorkout:(d) => req("POST", "/train/log", d),
  workoutLogs:() => req("GET", "/train/logs"),
  trainStats:() => req("GET", "/train/stats"),
  challenges:      () => req("GET", "/train/challenges").catch(()=>({ challenges:[] })),
  challengeDetail: (key) => req("GET", "/train/challenges/"+key),
  challengeDay:    (key, dayNumber) => req("GET", "/train/challenges/"+key+"/day/"+dayNumber),
  challengeStart:  (key, body) => req("POST", "/train/challenges/"+key+"/start", body),
  challengeFinish: (key, body) => req("POST", "/train/challenges/"+key+"/finish", body),
  challengeReset:  (key) => req("POST", "/train/challenges/"+key+"/reset"),
  // custom user-built workouts
  myPrograms:    () => req("GET", "/train/my-programs").catch(()=>({ programs:[] })),
  createProgram: (d)  => req("POST", "/train/my-programs", d),
  updateProgram: (id, d) => req("PUT", "/train/my-programs/"+id, d),
  deleteProgram: (id) => req("DELETE", "/train/my-programs/"+id),
  // nutrition
  dailyNutrition: () => req("GET", "/nutrition/daily").catch(()=>({meals:[]})),
  addMeal:  (d) => req("POST", "/nutrition/meals", d),
  addItem:  (mealId, d) => req("POST", "/nutrition/meals/"+mealId+"/items", d),
  delItem:  (id) => req("DELETE", "/nutrition/items/"+id),
  updateItem: (id, amount_g) => req("PUT", "/nutrition/items/"+id, { amount_g }),
  // food scan + custom foods
  scanFood:   (image) => req("POST", "/ai/scan-food", { image }),
  scanMeal:   (image) => req("POST", "/ai/scan-meal", { image }),
  customFoods:() => req("GET", "/nutrition/foods").catch(()=>({ foods:[] })),
  saveFood:   (d) => req("POST", "/nutrition/foods", d),
  deleteFood: (id) => req("DELETE", "/nutrition/foods/"+id),
  savedMeals: () => req("GET", "/nutrition/saved-meals").catch(()=>({ meals:[] })),
  saveMeal:   (d) => req("POST", "/nutrition/saved-meals", d),
  deleteSavedMeal: (id) => req("DELETE", "/nutrition/saved-meals/"+id),
  // water / weight / reports
  logWater: (d) => req("POST", "/water/log", d),
  waterToday: () => req("GET", "/water/today").catch(()=>({ amount_ml:0 })),
  logWeight:(d) => req("POST", "/reports/weight", d),
  resetWeight: (d) => req("POST", "/reports/weight/reset", d),
  weightReport: () => req("GET", "/reports/weight").catch(()=>({points:[]})),
  weekly: () => req("GET", "/reports/weekly").catch(()=>null),
  fastingCurrent: () => req("GET", "/fasting/current").catch(()=>({fast:null})),
  fastingStart: (d) => req("POST", "/fasting/start", d),
  fastingEnd: () => req("POST", "/fasting/end"),
  journalToday: () => req("GET", "/journal/" + NX.todayIL()).catch(()=>null),
  journalSave: (d) => req("POST", "/journal", d),
  progressPhotos: () => req("GET", "/reports/photos").catch(()=>({ photos:[] })),
  addProgressPhoto: (d) => req("POST", "/reports/photos", d),
  deleteProgressPhoto: (id) => req("DELETE", "/reports/photos/"+id),
  exportData: () => req("GET", "/reports/export"),
  barcodeLookup: (code) => req("GET", "/nutrition/barcode/" + encodeURIComponent(code)),
  saveBarcodeProduct: (d) => req("POST", "/nutrition/barcode", d),
  achievements: () => req("GET", "/reports/achievements").catch(()=>null),
  // ai
  aiChat:   (message, history) => req("POST", "/ai/chat", { message, history }),
  aiQuota:  () => req("GET", "/ai/quota").catch(()=>null),
  // push notifications
  pushVapidKey:    () => req("GET", "/push/vapid-key"),
  pushSubscribe:   (subscription) => req("POST", "/push/subscribe", { subscription }),
  pushUnsubscribe: (endpoint) => req("POST", "/push/unsubscribe", { endpoint }),
  pushStatus:      () => req("GET", "/push/status").catch(()=>({ subscribed:false })),
  pushTest:        () => req("POST", "/push/test"),
  // admin
  adminUsers:      () => req("GET", "/admin/users"),
  adminSetPerms:   (id, permissions) => req("PUT", "/admin/users/"+id+"/permissions", { permissions }),
  adminDeleteUser: (id) => req("DELETE", "/admin/users/"+id),
  adminResetUser:  (id) => req("POST", "/admin/reset-user/"+id),
  aiUsage:         () => req("GET", "/ai/admin/usage").catch(()=>null),
  adminUserOverview: (id) => req("GET", "/admin/users/"+id+"/overview"),
  resetAiUsage:    () => req("POST", "/ai/admin/usage/reset"),
  raw: req,
};

/* ── theme ── */
NX.useTheme = () => {
  const [theme, setTheme] = useState(localStorage.getItem("nx_theme") || "dark");
  const toggle = () => {
    const n = theme === "dark" ? "light" : "dark";
    setTheme(n); localStorage.setItem("nx_theme", n);
    document.body.setAttribute("data-theme", n);
    const mc = document.querySelector("meta[name=theme-color]");
    if (mc) mc.setAttribute("content", n === "light" ? "#f3f7f5" : "#0a0f0d");
  };
  useEffect(() => { document.body.setAttribute("data-theme", theme); }, [theme]);
  return { theme, toggle, dark: theme === "dark" };
};

/* ── helpers ── */
NX.cls = (...a) => a.filter(Boolean).join(" ");
NX.fmt = (n) => (n == null || isNaN(n)) ? "—" : Math.round(n).toLocaleString("he-IL");
NX.clamp = (n, a, b) => Math.max(a, Math.min(b, n));
NX.todayKey = () => new Date().toISOString().slice(0, 10);
NX.todayIL = () => new Date().toLocaleDateString("en-CA", { timeZone: "Asia/Jerusalem" });
// ── barcode scanning (lazy-load ZXing, decode from a captured photo) ──
NX.loadZXing = () => new Promise((resolve, reject) => {
  if (window.ZXing) return resolve(window.ZXing);
  const s = document.createElement("script");
  s.src = "https://unpkg.com/@zxing/library@0.21.3/umd/index.min.js";
  s.onload = () => window.ZXing ? resolve(window.ZXing) : reject(new Error("טעינת הסורק נכשלה"));
  s.onerror = () => reject(new Error("טעינת סורק הברקוד נכשלה"));
  document.head.appendChild(s);
});
NX.scanBarcode = async (file) => {
  const ZX = await NX.loadZXing();
  const url = await NX.compressImage(file, 1400, 0.9);
  const reader = new ZX.BrowserMultiFormatReader();
  const result = await reader.decodeFromImageUrl(url);
  return result.getText();
};
// ── web push: subscribe this device for notifications ──
NX.urlB64ToUint8Array = (b64) => {
  const padding = "=".repeat((4 - b64.length % 4) % 4);
  const base64 = (b64 + padding).replace(/-/g, "+").replace(/_/g, "/");
  const raw = atob(base64), arr = new Uint8Array(raw.length);
  for (let i = 0; i < raw.length; i++) arr[i] = raw.charCodeAt(i);
  return arr;
};
NX.enablePush = async () => {
  if (!("serviceWorker" in navigator) || !("PushManager" in window)) throw new Error("המכשיר/דפדפן לא תומך בהתראות");
  const perm = await Notification.requestPermission();
  if (perm !== "granted") throw new Error("ההתראות לא אושרו במכשיר");
  const reg = await navigator.serviceWorker.ready;
  const { key } = await NX.api.pushVapidKey();
  if (!key) throw new Error("התראות לא מוגדרות בשרת");
  let sub = await reg.pushManager.getSubscription();
  if (!sub) sub = await reg.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: NX.urlB64ToUint8Array(key) });
  await NX.api.pushSubscribe(sub.toJSON ? sub.toJSON() : sub);
  return true;
};
// compress an image File to a JPEG data URL (longest edge <= max), keeping uploads under the body limit
NX.compressImage = (file, max=1024, q=0.7) => new Promise((resolve, reject) => {
  const img = new Image();
  img.onload = () => {
    let w = img.width, h = img.height;
    if (w > h && w > max) { h = Math.round(h*max/w); w = max; }
    else if (h >= w && h > max) { w = Math.round(w*max/h); h = max; }
    const c = document.createElement("canvas"); c.width = w; c.height = h;
    c.getContext("2d").drawImage(img, 0, 0, w, h);
    resolve(c.toDataURL("image/jpeg", q));
  };
  img.onerror = () => reject(new Error("טעינת התמונה נכשלה"));
  img.src = URL.createObjectURL(file);
});
// Mifflin-St Jeor + activity + deficit for weight loss
NX.calcPlan = (u) => {
  if (!u) return null;
  const w = u.weight_kg || 75, h = u.height_cm || 172, age = u.age || 30;
  const male = (u.gender || "male") !== "female";
  const bmr = Math.round(10*w + 6.25*h - 5*age + (male ? 5 : -161));
  const mult = { low:1.375, light:1.375, intermediate:1.55, moderate:1.55, high:1.725, athlete:1.725 }[u.activity_level] || 1.55;
  const tdee = Math.round(bmr * mult);
  // weight-loss deficit ~20%, with floor
  let goalCals = Math.round(tdee * 0.80);
  const floor = male ? 1500 : 1200;
  if (goalCals < floor) goalCals = floor;
  const protein = Math.round(w * 2.0);          // g
  const fat = Math.round((goalCals * 0.25) / 9); // g
  const carbs = Math.round((goalCals - protein*4 - fat*9) / 4);
  const water = Math.round((w * 35 + (u.workouts_per_week||3)*150)); // ml
  const bmi = +(w / Math.pow(h/100, 2)).toFixed(1);
  return { bmr, tdee, goalCals, protein, fat, carbs, water, bmi, deficit: tdee - goalCals };
};

/* ── UI atoms ── */
NX.Spinner = ({ size=22 }) => <div className="spinner" style={{ width:size, height:size }}/>;

NX.Tog = ({ on, set, label }) => (
  <button type="button" role="switch" aria-checked={!!on} aria-label={label} onClick={()=>set(!on)}
    style={{ width:46, height:26, borderRadius:99, border:"none", padding:3, cursor:"pointer", flexShrink:0,
      background: on ? "var(--acc)" : "var(--line2)", transition:"background .2s",
      display:"flex", justifyContent: on ? "flex-end" : "flex-start", boxShadow: on ? "0 0 10px var(--accGlow)" : "none" }}>
    <span style={{ width:20, height:20, borderRadius:"50%", background:"#fff", boxShadow:"0 1px 3px rgba(0,0,0,.4)", transition:"all .2s", display:"block" }}/>
  </button>
);

NX.Card = ({ children, style, className, glow, ...p }) =>
  <div className={NX.cls("card", className)} style={{ padding:16, ...(glow?{borderColor:"var(--acc)",boxShadow:"0 0 0 1px var(--accDim),0 10px 40px var(--accDim)"}:{}), ...style }} {...p}>{children}</div>;

NX.Btn = ({ children, variant="primary", style, ...p }) =>
  <button className={NX.cls("btn", variant==="primary"?"btn-primary":"btn-ghost")} style={style} {...p}>{children}</button>;

NX.Ring = ({ value, max, size=120, stroke=11, color="var(--acc)", label, sub }) => {
  const pct = NX.clamp(max ? value/max : 0, 0, 1);
  const r = (size - stroke) / 2, c = 2*Math.PI*r;
  return (
    <div style={{ position:"relative", width:size, height:size }}>
      <svg width={size} height={size} style={{ transform:"rotate(-90deg)" }}>
        <circle cx={size/2} cy={size/2} r={r} fill="none" stroke="var(--line2)" strokeWidth={stroke}/>
        <circle cx={size/2} cy={size/2} r={r} fill="none" stroke={color} strokeWidth={stroke} strokeLinecap="round"
          strokeDasharray={c} strokeDashoffset={c*(1-pct)} style={{ transition:"stroke-dashoffset 1s cubic-bezier(.22,1,.36,1)" }}/>
      </svg>
      <div style={{ position:"absolute", inset:0, display:"flex", flexDirection:"column", alignItems:"center", justifyContent:"center" }}>
        <div className="num" style={{ fontSize:size*0.24, fontWeight:800, lineHeight:1 }}>{label}</div>
        {sub && <div style={{ fontSize:11, color:"var(--tx2)", marginTop:3, fontWeight:600 }}>{sub}</div>}
      </div>
    </div>
  );
};

NX.Bar = ({ value, max, color="var(--acc)", height=8 }) => (
  <div style={{ width:"100%", height, background:"var(--line2)", borderRadius:height, overflow:"hidden" }}>
    <div style={{ height:"100%", width:NX.clamp(max?value/max*100:0,0,100)+"%", background:color, borderRadius:height, transition:"width .8s cubic-bezier(.22,1,.36,1)" }}/>
  </div>
);

NX.Sheet = ({ open, onClose, children, title }) => {
  // lift the sheet above the on-screen keyboard (iOS doesn't resize the layout viewport)
  const [kb, setKb] = useState(0);
  useEffect(() => {
    if (!open) { setKb(0); return; }
    const vv = window.visualViewport;
    if (!vv) return;
    const onResize = () => setKb(Math.max(0, window.innerHeight - vv.height - vv.offsetTop));
    onResize();
    vv.addEventListener("resize", onResize);
    vv.addEventListener("scroll", onResize);
    return () => { vv.removeEventListener("resize", onResize); vv.removeEventListener("scroll", onResize); };
  }, [open]);
  if (!open) return null;
  return (
    <div onClick={(e)=>{ if(e.target===e.currentTarget) onClose(); }}
      style={{ position:"fixed", inset:0, zIndex:300, display:"flex", alignItems:"flex-end", justifyContent:"center", background:"rgba(0,0,0,.6)", backdropFilter:"blur(6px)", animation:"fadeIn .2s ease", paddingBottom:kb, transition:"padding-bottom .18s ease" }}>
      <div style={{ width:"100%", maxWidth:520, maxHeight: kb>0 ? "calc(100svh - "+kb+"px - 16px)" : "90svh", background:"var(--bg2)", borderRadius:"26px 26px 0 0", border:"1px solid var(--line)", display:"flex", flexDirection:"column", animation:"fadeUp .3s cubic-bezier(.22,1,.36,1)" }}>
        <div style={{ flexShrink:0, padding:"14px 18px 6px" }}>
          <div style={{ width:38, height:4, borderRadius:2, background:"var(--line2)", margin:"0 auto 12px" }}/>
          {title && <div style={{ display:"flex", justifyContent:"space-between", alignItems:"center" }}>
            <div style={{ fontSize:17, fontWeight:800 }}>{title}</div>
            <button onClick={onClose} style={{ width:30, height:30, borderRadius:"50%", background:"var(--card2)", color:"var(--tx2)", fontSize:15 }}>✕</button>
          </div>}
        </div>
        <div style={{ flex:1, overflowY:"auto", padding:"6px 18px max(20px,env(safe-area-inset-bottom))" }}>{children}</div>
      </div>
    </div>
  );
};

NX.Toast = ({ msg, type }) => {
  if (!msg) return null;
  const c = type==="bad" ? "var(--danger)" : type==="ok" ? "var(--acc)" : "var(--info)";
  return <div style={{ position:"fixed", top:"max(16px,env(safe-area-inset-top))", left:"50%", transform:"translateX(-50%)", zIndex:9999, background:"var(--card)", border:"1px solid "+c, color:"var(--tx)", padding:"11px 18px", borderRadius:14, fontWeight:700, fontSize:14, boxShadow:"0 10px 40px rgba(0,0,0,.5)", animation:"pop .3s both", whiteSpace:"nowrap", maxWidth:"92vw", overflow:"hidden", textOverflow:"ellipsis" }}>{msg}</div>;
};
NX.useToast = () => {
  const [toast, set] = useState(null);
  const notify = useCallback((msg, type) => { set({ msg, type }); setTimeout(()=>set(null), 2600); }, []);
  return { toast, notify };
};

})(window.NX);
/* NutriOS 6 — exercise demos: real-photo crossfade (start↔end) + SVG fallback */
window.NX = window.NX || {};
(function(NX){
const { useState, useEffect } = React;

// exercises that have real photos under /ex/<key>/{0,1}.jpg
const PHOTO = new Set(["pushup","knee_pushup","incline_pushup","pike_pushup","dips","pullup","australian_row","inverted_row","squat","lunge","glute_bridge","calf_raise","plank","leg_raise","bicycle_crunch","jumping_jack","mountain_climber",
  "superman","side_plank","jump_squat","glute_kickback","flutter_kicks","reverse_crunch","situp","scissor_kick",
  "burpee","high_knees","hollow_hold",
  "wide_pushup","decline_pushup","chinup","hanging_leg_raise","step_up","oblique_crunch","toe_touch","spider_crawl",
  "diamond_pushup","clock_pushup","handstand_pushup","single_leg_bridge","crunch","cross_body_crunch","russian_twist","dead_bug",
  "heel_touches","leg_pull_in","inchworm","lateral_bound","knee_tuck_jump","double_leg_butt_kick"]);

// SVG fallback motion map (for the 3 without photos: hollow_hold, high_knees, burpee + any error)
const MAP = {
  pushup:{m:"pushup",o:"floor"}, incline_pushup:{m:"pushup",o:"floor"}, knee_pushup:{m:"kneepush",o:"floor"},
  pike_pushup:{m:"pike",o:"floor"}, mountain_climber:{m:"mtn",o:"floor"}, plank:{m:"plank",o:"floor"},
  dips:{m:"dips",o:"stand"}, pullup:{m:"pullup",o:"stand"}, australian_row:{m:"row",o:"stand"}, inverted_row:{m:"row",o:"stand"},
  squat:{m:"squat",o:"stand"}, lunge:{m:"lunge",o:"stand"}, calf_raise:{m:"calf",o:"stand"},
  jumping_jack:{m:"jack",o:"stand"}, high_knees:{m:"highknees",o:"stand"}, burpee:{m:"burpee",o:"stand"},
  glute_bridge:{m:"bridge",o:"lying"}, leg_raise:{m:"legraise",o:"lying"}, hollow_hold:{m:"hollow",o:"lying"}, bicycle_crunch:{m:"bicycle",o:"lying"},
  superman:{m:"hollow",o:"lying"}, side_plank:{m:"plank",o:"floor"}, jump_squat:{m:"squat",o:"stand"}, glute_kickback:{m:"bridge",o:"lying"},
  flutter_kicks:{m:"bicycle",o:"lying"}, reverse_crunch:{m:"legraise",o:"lying"}, situp:{m:"bicycle",o:"lying"}, scissor_kick:{m:"bicycle",o:"lying"},
  wide_pushup:{m:"pushup",o:"floor"}, decline_pushup:{m:"pushup",o:"floor"}, chinup:{m:"pullup",o:"stand"}, hanging_leg_raise:{m:"legraise",o:"lying"},
  step_up:{m:"squat",o:"stand"}, oblique_crunch:{m:"bicycle",o:"lying"}, toe_touch:{m:"legraise",o:"lying"}, spider_crawl:{m:"mtn",o:"floor"},
  // no-equipment home additions (photo-backed; these movements are the SVG fallback if an image fails)
  diamond_pushup:{m:"pushup",o:"floor"}, clock_pushup:{m:"pushup",o:"floor"}, handstand_pushup:{m:"pike",o:"floor"},
  single_leg_bridge:{m:"bridge",o:"lying"}, crunch:{m:"bicycle",o:"lying"}, cross_body_crunch:{m:"bicycle",o:"lying"},
  russian_twist:{m:"bicycle",o:"lying"}, dead_bug:{m:"legraise",o:"lying"}, heel_touches:{m:"bicycle",o:"lying"}, leg_pull_in:{m:"legraise",o:"lying"},
  inchworm:{m:"burpee",o:"stand"}, lateral_bound:{m:"lunge",o:"stand"}, knee_tuck_jump:{m:"squat",o:"stand"}, double_leg_butt_kick:{m:"squat",o:"stand"},
};
NX.familyFor = (k) => MAP[k] || { m:"plank", o:"stand" };

/* ── SVG silhouette fallback ── */
function StandFig(){ return (
  <g className="fig">
    <g className="ab-uarm"><path className="pt back" d="M100 64 L92 96"/><g className="ab-farm"><path className="pt back" d="M92 96 L92 124"/></g></g>
    <g className="lb-thigh"><path className="pt back" d="M100 120 L92 162"/><g className="lb-shin"><path className="pt back" d="M92 162 L92 200"/></g></g>
    <circle className="head" cx="100" cy="34" r="15"/><path className="pt neck" d="M100 49 L100 58"/><path className="torso" d="M100 60 L100 120"/>
    <g className="a-uarm"><path className="pt" d="M100 64 L108 96"/><g className="a-farm"><path className="pt" d="M108 96 L108 124"/></g></g>
    <g className="l-thigh"><path className="pt" d="M100 120 L108 162"/><g className="l-shin"><path className="pt" d="M108 162 L108 200"/><path className="pt foot" d="M108 200 L120 200"/></g></g>
  </g>); }
function FloorFig(){ return (
  <g className="fig">
    <circle className="head" cx="158" cy="120" r="14"/><path className="torso" d="M146 124 L82 138"/>
    <g className="l-thigh"><path className="pt" d="M82 138 L54 150"/><g className="l-shin"><path className="pt" d="M54 150 L34 156"/><path className="pt foot" d="M34 156 L30 168"/></g></g>
    <g className="a-uarm"><path className="pt" d="M146 124 L146 156"/><g className="a-farm"><path className="pt" d="M146 156 L132 156"/></g></g>
  </g>); }
function LyingFig(){ return (
  <g className="fig">
    <circle className="head" cx="44" cy="146" r="13"/><path className="torso" d="M58 150 L118 150"/>
    <g className="l-thigh"><path className="pt" d="M118 150 L140 132"/><g className="l-shin"><path className="pt" d="M140 132 L156 120"/></g></g>
    <path className="pt back" d="M64 150 L96 156"/>
  </g>); }

function SVGFigure({ exerciseKey, size }){
  const { m, o } = NX.familyFor(exerciseKey);
  const Fig = o==="floor"?FloorFig : o==="lying"?LyingFig : StandFig;
  const gy = o==="stand"?206:170;
  return (
    <div className={"exfig o-"+o+" m-"+m} style={{ width:size, height:size }}>
      <svg viewBox="0 0 200 220" className="exfig-svg">
        <defs><radialGradient id="exg" cx="50%" cy="42%" r="58%"><stop offset="0%" stopColor="var(--accDim)"/><stop offset="100%" stopColor="transparent"/></radialGradient></defs>
        <ellipse cx="100" cy="105" rx="98" ry="100" fill="url(#exg)"/>
        <line className="exfig-ground" x1="18" y1={gy} x2="182" y2={gy}/>
        <Fig/>
      </svg>
    </div>
  );
}

/* ── real-photo crossfade demo ── */
function PhotoFigure({ exerciseKey, size }){
  const [f, setF] = useState(0);
  const [err, setErr] = useState(false);
  useEffect(()=>{ const t=setInterval(()=>setF(x=>x?0:1), 950); return ()=>clearInterval(t); }, []);
  if (err) return <SVGFigure exerciseKey={exerciseKey} size={size}/>;
  const base = "/ex/"+exerciseKey+"/";
  const imgStyle = (on)=>({ position:"absolute", inset:0, width:"100%", height:"100%", objectFit:"cover", opacity:on?1:0, transition:"opacity .5s ease" });
  return (
    <div style={{ position:"relative", width:size, height:size, borderRadius:16, overflow:"hidden", background:"#f4f6f5" }}>
      <img src={base+"0.jpg"} alt="" onError={()=>setErr(true)} style={imgStyle(f===0)}/>
      <img src={base+"1.jpg"} alt="" onError={()=>setErr(true)} style={imgStyle(f===1)}/>
      {/* subtle frame indicator */}
      <div style={{ position:"absolute", bottom:6, insetInlineStart:6, display:"flex", gap:4 }}>
        {[0,1].map(i=><span key={i} style={{ width:6, height:6, borderRadius:"50%", background:f===i?"var(--acc)":"rgba(0,0,0,.25)", transition:"background .3s" }}/>)}
      </div>
    </div>
  );
}

NX.ExerciseFigure = ({ exerciseKey, size }) =>
  PHOTO.has(exerciseKey)
    ? <PhotoFigure exerciseKey={exerciseKey} size={size}/>
    : <SVGFigure exerciseKey={exerciseKey} size={size}/>;

NX.ExerciseThumb = ({ exerciseKey, size=56 }) =>
  <div style={{ width:size, height:size, flexShrink:0, borderRadius:14, overflow:"hidden", display:"flex", alignItems:"center", justifyContent:"center", background:"var(--card2)" }}>
    <NX.ExerciseFigure exerciseKey={exerciseKey} size={size}/>
  </div>;

})(window.NX);
/* NutriOS 6 — common foods DB (values per 100g / 100ml) */
window.NX = window.NX || {};
(function(NX){
NX.FOOD_CATS = [
  ["protein","🍗 חלבון"], ["carb","🍚 פחמימות"], ["legume","🫘 קטניות"], ["dairy","🥛 מחלבה"],
  ["fruit","🍎 פירות"], ["veg","🥦 ירקות"], ["fat","🥜 שומנים ואגוזים"], ["snack","🍫 חטיפים"],
  ["drink","🥤 משקאות"], ["fast","🍔 מזון מהיר"],
];
// kcal, p(protein g), c(carbs g), f(fat g) — per 100g
const F = (he,emo,cat,kcal,p,c,f)=>({ he,emo,cat,kcal,p,c,f });
NX.FOODS = [
  // ── protein ──
  F("חזה עוף","🍗","protein",165,31,0,3.6), F("שניצל עוף","🍗","protein",290,21,15,16),
  F("כרעיים עוף","🍗","protein",209,26,0,11), F("כבד עוף","🍖","protein",119,17,0.7,5),
  F("חזה הודו","🦃","protein",104,17,4,2), F("הודו טחון","🦃","protein",135,29,0,1),
  F("בשר בקר רזה","🥩","protein",250,26,0,15), F("בשר טחון רזה","🥩","protein",200,27,0,10),
  F("אנטריקוט","🥩","protein",291,24,0,21), F("סטייק סינטה","🥩","protein",206,27,0,11),
  F("כבש","🍖","protein",294,25,0,21), F("קבב","🍢","protein",215,15,3,15),
  F("נקניקיות","🌭","protein",290,11,4,25),
  F("סלמון","🐟","protein",208,20,0,13), F("סלמון מעושן","🐟","protein",117,18,0,4.3),
  F("טונה במים","🐟","protein",116,26,0,1), F("דג בקלה","🐟","protein",82,18,0,0.7),
  F("דניס","🐟","protein",120,20,0,4), F("לברק","🐟","protein",97,18,0,2.5),
  F("מושט (אמנון)","🐟","protein",96,20,0,1.7), F("שרימפס","🦐","protein",99,24,0,0.3),
  F("ביצה","🥚","protein",155,13,1.1,11), F("חביתה","🍳","protein",154,11,1,11),
  F("חלבון ביצה","🥚","protein",52,11,0.7,0.2),
  F("טופו","🧊","protein",76,8,1.9,4.8), F("סייטן","🌾","protein",370,75,14,1.9),
  F("אבקת חלבון","💪","protein",400,80,8,7),
  // ── carbs ──
  F("אורז לבן מבושל","🍚","carb",130,2.7,28,0.3), F("אורז מלא מבושל","🍚","carb",112,2.6,24,0.9),
  F("פסטה מבושלת","🍝","carb",131,5,25,1.1), F("אטריות","🍜","carb",138,4.5,25,2),
  F("תפוח אדמה","🥔","carb",77,2,17,0.1), F("בטטה","🍠","carb",86,1.6,20,0.1),
  F("לחם מלא","🍞","carb",247,13,41,3.4), F("לחם לבן","🍞","carb",265,9,49,3.2),
  F("לחם שיפון","🍞","carb",259,9,48,3.3), F("פיתה","🫓","carb",275,9,55,1.2),
  F("לחמנייה","🥖","carb",290,9,52,4.5), F("טורטייה","🫓","carb",310,8,52,7),
  F("שיבולת שועל","🥣","carb",389,17,66,7), F("גרנולה","🥣","carb",471,10,64,20),
  F("קורנפלקס","🥣","carb",357,8,84,0.4), F("קינואה מבושלת","🍚","carb",120,4.4,21,1.9),
  F("קוסקוס מבושל","🍚","carb",112,3.8,23,0.2), F("בורגול מבושל","🍚","carb",83,3,18,0.2),
  F("גריסים מבושלים","🥣","carb",123,2.3,28,0.4), F("פתיתים","🍚","carb",157,5,32,0.5),
  F("תירס","🌽","carb",96,3.4,21,1.5), F("מצה","🫓","carb",354,10,73,1.4),
  F("בייגלה","🥨","carb",380,10,80,3),
  // ── legume ──
  F("עדשים מבושלות","🫘","legume",116,9,20,0.4), F("עדשים כתומות","🫘","legume",116,9,20,0.4),
  F("חומוס (גרגירים)","🫘","legume",164,8,27,2.6), F("חומוס ממרח","🥣","legume",177,8,20,9),
  F("שעועית לבנה","🫘","legume",139,9,25,0.5), F("שעועית אדומה","🫘","legume",127,9,23,0.5),
  F("פול","🫘","legume",110,8,19,0.4), F("אפונה","🫛","legume",81,5,14,0.4),
  F("סויה (אדממה)","🫛","legume",122,11,10,5), F("מש","🫘","legume",105,7,19,0.4),
  F("מג'דרה","🍚","legume",165,5,28,4),
  // ── dairy ──
  F("קוטג' 5%","🧀","dairy",98,11,3,5), F("גבינה לבנה 5%","🧀","dairy",90,9,4,5),
  F("בולגרית 5%","🧀","dairy",153,15,3,9), F("פטה","🧀","dairy",264,14,4,21),
  F("מוצרלה","🧀","dairy",280,22,2,22), F("גבינה צהובה","🧀","dairy",350,25,2,27),
  F("גבינת שמנת","🧀","dairy",255,6,4,25), F("ריקוטה","🧀","dairy",174,11,3,13),
  F("יוגורט יווני 0%","🥛","dairy",59,10,3.6,0.4), F("יוגורט טבעי 3%","🥛","dairy",61,3.5,4.7,3.3),
  F("יוגורט פרי","🥛","dairy",95,3.5,15,2.5), F("לבן","🥛","dairy",60,3.5,4.5,3),
  F("שמנת חמוצה","🥛","dairy",198,2.4,4,20), F("חלב 3%","🥛","dairy",61,3.3,4.8,3.6),
  F("חלב 1%","🥛","dairy",42,3.4,5,1), F("חלב סויה","🥛","dairy",54,3.3,6,1.8),
  F("חלב שקדים","🥛","dairy",17,0.6,0.6,1.2), F("מילקי / פודינג","🍮","dairy",130,3,18,5),
  // ── fruit ──
  F("בננה","🍌","fruit",89,1.1,23,0.3), F("תפוח","🍎","fruit",52,0.3,14,0.2),
  F("אגס","🍐","fruit",57,0.4,15,0.1), F("תפוז","🍊","fruit",47,0.9,12,0.1),
  F("קלמנטינה","🍊","fruit",47,0.9,12,0.2), F("אשכולית","🍊","fruit",42,0.8,11,0.1),
  F("אבטיח","🍉","fruit",30,0.6,8,0.2), F("מלון","🍈","fruit",34,0.8,8,0.2),
  F("ענבים","🍇","fruit",69,0.7,18,0.2), F("תות","🍓","fruit",32,0.7,8,0.3),
  F("אוכמניות","🫐","fruit",57,0.7,14,0.3), F("אפרסק","🍑","fruit",39,0.9,10,0.3),
  F("נקטרינה","🍑","fruit",44,1,11,0.3), F("שזיף","🍑","fruit",46,0.7,11,0.3),
  F("משמש","🍑","fruit",48,1.4,11,0.4), F("קיווי","🥝","fruit",61,1.1,15,0.5),
  F("מנגו","🥭","fruit",60,0.8,15,0.4), F("אננס","🍍","fruit",50,0.5,13,0.1),
  F("רימון","🍈","fruit",83,1.7,19,1.2), F("תמר","🌴","fruit",282,2.5,75,0.4),
  F("צימוקים","🍇","fruit",299,3.1,79,0.5), F("אבוקדו","🥑","fruit",160,2,9,15),
  // ── veg ──
  F("מלפפון","🥒","veg",15,0.7,3.6,0.1), F("עגבנייה","🍅","veg",18,0.9,3.9,0.2),
  F("עגבניות שרי","🍅","veg",18,0.9,3.9,0.2), F("גזר","🥕","veg",41,0.9,10,0.2),
  F("בצל","🧅","veg",40,1.1,9,0.1), F("פלפל","🫑","veg",31,1,6,0.3),
  F("ברוקולי","🥦","veg",34,2.8,7,0.4), F("כרובית","🥦","veg",25,1.9,5,0.3),
  F("כרוב","🥬","veg",25,1.3,6,0.1), F("חסה","🥬","veg",15,1.4,2.9,0.2),
  F("תרד","🥬","veg",23,2.9,3.6,0.4), F("חציל","🍆","veg",25,1,6,0.2),
  F("קישוא","🥒","veg",17,1.2,3.1,0.3), F("פטריות","🍄","veg",22,3.1,3.3,0.3),
  F("סלק","🫜","veg",43,1.6,10,0.2), F("דלעת","🎃","veg",26,1,6.5,0.1),
  F("שעועית ירוקה","🫛","veg",31,1.8,7,0.2), F("אספרגוס","🥬","veg",20,2.2,3.9,0.1),
  // ── fat / nuts ──
  F("שקדים","🌰","fat",579,21,22,49), F("אגוזי מלך","🌰","fat",654,15,14,65),
  F("אגוזי לוז","🌰","fat",628,15,17,61), F("פקאן","🌰","fat",691,9,14,72),
  F("קשיו","🥜","fat",553,18,30,44), F("פיסטוק","🥜","fat",560,20,28,45),
  F("בוטנים","🥜","fat",567,26,16,49), F("חמאת בוטנים","🥜","fat",588,25,20,50),
  F("גרעיני חמנייה","🌻","fat",584,21,20,51), F("גרעיני דלעת","🎃","fat",559,30,11,49),
  F("זרעי צ'יה","🌱","fat",486,17,42,31), F("זרעי פשתן","🌱","fat",534,18,29,42),
  F("טחינה גולמית","🥣","fat",595,17,21,53), F("שמן זית","🫒","fat",884,0,0,100),
  F("זיתים","🫒","fat",115,0.8,6,11), F("חמאה","🧈","fat",717,0.9,0.1,81),
  F("מיונז","🥚","fat",680,1,1,75), F("קוקוס מגורד","🥥","fat",660,7,24,64),
  // ── snack ──
  F("שוקולד מריר","🍫","snack",546,5,61,31), F("שוקולד חלב","🍫","snack",535,7,59,30),
  F("חלבה","🍯","snack",540,12,40,37), F("במבה","🥜","snack",520,13,50,31),
  F("ביסלי","🥨","snack",470,9,62,21), F("צ'יפס תפו״א","🥔","snack",536,7,53,35),
  F("פופקורן","🍿","snack",387,12,78,4), F("עוגיות","🍪","snack",480,6,64,22),
  F("קרקר מתוק","🍪","snack",450,7,71,15), F("ופל","🧇","snack",460,5,68,19),
  F("קרואסון","🥐","snack",406,8,46,21), F("דונאט","🍩","snack",452,5,51,25),
  F("עוגת גבינה","🍰","snack",321,5.5,26,22), F("מרשמלו","🍬","snack",318,1.8,81,0.2),
  F("גלידה","🍦","snack",207,3.5,24,11),
  // ── drink (per 100ml) ──
  F("מים","💧","drink",0,0,0,0), F("קולה","🥤","drink",42,0,11,0),
  F("קולה זירו","🥤","drink",1,0,0,0), F("ספרייט","🥤","drink",40,0,10,0),
  F("מיץ תפוזים","🧃","drink",45,0.7,10,0.2), F("מיץ תפוחים","🧃","drink",46,0.1,11,0.1),
  F("לימונדה","🍋","drink",40,0,10,0), F("משקה אנרגיה","⚡","drink",45,0,11,0),
  F("שוקו","🥛","drink",83,3.2,12,2.5), F("קפה הפוך","☕","drink",55,3,5,2.5),
  F("אספרסו","☕","drink",2,0.1,0,0), F("תה","🍵","drink",1,0,0.3,0),
  F("בירה","🍺","drink",43,0.5,3.6,0), F("יין","🍷","drink",83,0.1,2.6,0),
  F("וודקה","🍸","drink",231,0,0,0), F("וויסקי","🥃","drink",250,0,0,0),
  // ── fast food ──
  F("פיצה","🍕","fast",266,11,33,10), F("המבורגר","🍔","fast",295,17,24,14),
  F("צ'יזבורגר","🍔","fast",303,15,23,16), F("הוט דוג","🌭","fast",290,10,24,17),
  F("נאגטס","🍗","fast",296,15,16,19), F("צ'יפס","🍟","fast",312,3.4,41,15),
  F("פלאפל","🧆","fast",333,13,32,18), F("שווארמה","🌯","fast",350,20,15,24),
  F("בורקס","🥟","fast",360,7,32,22), F("סמבוסק","🥟","fast",320,8,35,16),
  F("מלאווח","🫓","fast",360,6,35,22), F("ג'חנון","🫓","fast",380,7,40,21),
  F("בוריטו","🌯","fast",206,9,24,8), F("סושי","🍣","fast",150,5,30,1),
];
})(window.NX);
/* NutriOS 6 — screens */
window.NX = window.NX || {};
(function(NX){
const { useState, useEffect, useRef, useCallback } = React;
const { Card, Btn, Ring, Bar, Sheet, Spinner, ExerciseFigure, ExerciseThumb, fmt, cls } = NX;

/* hoisted form helpers — MUST be module-scope (stable identity) so inputs keep focus while typing */
const Labeled = ({ label, children, style }) => <div style={{ marginBottom:13, ...style }}><div style={{ fontSize:12, fontWeight:700, color:"var(--tx2)", marginBottom:6 }}>{label}</div>{children}</div>;
const ChipSeg = ({ value, opts, onPick }) => <div style={{ display:"flex", gap:7, flexWrap:"wrap" }}>{opts.map(([v,l])=>
  <button key={v} onClick={()=>onPick(v)} className={cls("chip", value===v&&"on")} style={{ flex:"1 1 auto" }}>{l}</button>)}</div>;
const NumField = ({ label, value, onChange, type="number" }) => <Labeled label={label} style={{ flex:1 }}><input className={cls("inp", type==="number"&&"num")} type={type} value={value??""} onChange={onChange}/></Labeled>;

const CAT = {
  home:{he:"ביתי · ללא ציוד",emo:"🏠",col:"#22d3ee"},
  push:{he:"דחיפה",emo:"💪",col:"#10b981"}, pull:{he:"משיכה",emo:"🧗",col:"#0ea5e9"},
  legs:{he:"רגליים",emo:"🦵",col:"#a855f7"}, core:{he:"ליבה",emo:"🎯",col:"#f59e0b"},
  cardio:{he:"שריפת שומן",emo:"🔥",col:"#ef4444"},
};
const DIFF = { 1:{he:"מתחיל",col:"#10b981"}, 2:{he:"בינוני",col:"#f59e0b"}, 3:{he:"מתקדם",col:"#ef4444"} };

/* convert a user-built workout {id,name,items:[{key,sets,reps}]} into the program shape the player expects */
const customToProgram = (p) => ({
  prog_key: "custom_"+p.id, name_he: p.name, description_he: "אימון אישי שבנית",
  days_per_week: 1, days: [{ name: p.name, items: p.items }],
});
const stepBtn = { width:22, height:22, borderRadius:6, background:"var(--card)", border:"1px solid var(--line2)", fontSize:14, fontWeight:800, color:"var(--tx2)", lineHeight:1, display:"flex", alignItems:"center", justifyContent:"center", flexShrink:0 };
const miniBtn = { width:22, height:15, fontSize:9, color:"var(--tx3)", lineHeight:1, background:"transparent" };

/* nutrition shape: API returns meals as object {breakfast:[],lunch:[],snack:[],dinner:[]}, items use food_name/protein_g */
const MEAL_LABELS = { breakfast:"ארוחת בוקר", lunch:"צהריים", dinner:"ערב", snack:"ארוחת ביניים" };
const MEAL_ORDER = ["breakfast","lunch","dinner","snack"];
const HE_TO_TYPE = { "ארוחת בוקר":"breakfast", "צהריים":"lunch", "ערב":"dinner", "ארוחת ביניים":"snack" };
function flattenMeals(nut){
  const m = nut && nut.meals;
  if (!m) return [];
  if (Array.isArray(m)) return m.map(x=>({ name:x.name||MEAL_LABELS[x.meal_type]||x.meal_type, meal_type:x.meal_type, items:Array.isArray(x.items)?x.items:[] }));
  return MEAL_ORDER.filter(k=>Array.isArray(m[k]) && m[k].length).map(k=>({ name:MEAL_LABELS[k], meal_type:k, items:m[k] }));
}
const itemCals = (i)=>(+i.calories||0);
const itemProt = (i)=>(+i.protein_g||+i.protein||0);
const itemName = (i)=>(i.food_name||i.name||"");
const sumCals = (ms)=>ms.reduce((s,me)=>s+me.items.reduce((x,i)=>x+itemCals(i),0),0);
const sumProt = (ms)=>ms.reduce((s,me)=>s+me.items.reduce((x,i)=>x+itemProt(i),0),0);

/* ════════ AUTH + ONBOARDING ════════ */
NX.AuthScreen = ({ onAuth, notify }) => {
  const [mode, setMode] = useState("login");
  const [step, setStep] = useState(1);
  const [busy, setBusy] = useState(false);
  const [f, setF] = useState({ name:"", email:"", password:"", age:30, gender:"male", height_cm:172, weight_kg:80, target_weight_kg:72, activity_level:"intermediate", workouts_per_week:3, goal:"lose" });
  const set = (k,v) => setF(p=>({ ...p, [k]:v }));

  const login = async () => {
    if (!f.email || !f.password) return notify("מלא אימייל וסיסמה", "bad");
    setBusy(true);
    try { const d = await NX.api.login(f.email, f.password); NX.setToken(d.token); await onAuth(); }
    catch(e){ notify(e.message, "bad"); } setBusy(false);
  };
  const register = async () => {
    setBusy(true);
    try { const d = await NX.api.register(f); NX.setToken(d.token); await onAuth(); }
    catch(e){ notify(e.message, "bad"); } setBusy(false);
  };

  const Field = Labeled, Seg = ChipSeg; // stable identity (defined at module scope)

  return (
    <div style={{ minHeight:"100svh", display:"flex", alignItems:"center", justifyContent:"center", padding:"24px 18px", position:"relative", overflow:"hidden" }}>
      <div style={{ position:"fixed", top:"-15%", right:"-10%", width:380, height:380, background:"radial-gradient(circle,var(--accDim),transparent 70%)", pointerEvents:"none" }}/>
      <div style={{ width:"100%", maxWidth:430, position:"relative" }}>
        <div className="fu" style={{ textAlign:"center", marginBottom:26 }}>
          <div style={{ fontSize:54, animation:"floaty 3s ease-in-out infinite" }}>🤸</div>
          <h1 style={{ fontSize:34, fontWeight:900, letterSpacing:"-1px", marginTop:6 }}><span className="grad-tx">NutriOS</span></h1>
          <p style={{ color:"var(--tx2)", fontSize:14, marginTop:4, fontWeight:600 }}>ירידה במשקל · קליסטניקס · תזונה</p>
        </div>

        {mode==="login" ? (
          <Card className="fu fu1" style={{ padding:24 }}>
            <Field label="אימייל"><input className="inp" type="email" value={f.email} onChange={e=>set("email",e.target.value)} onKeyDown={e=>e.key==="Enter"&&login()}/></Field>
            <Field label="סיסמה"><input className="inp" type="password" value={f.password} onChange={e=>set("password",e.target.value)} onKeyDown={e=>e.key==="Enter"&&login()}/></Field>
            <Btn onClick={login} disabled={busy} style={{ width:"100%", marginTop:6 }}>{busy?<Spinner size={18}/>:"התחברות"}</Btn>
            <p style={{ textAlign:"center", marginTop:16, fontSize:14, color:"var(--tx2)" }}>אין חשבון? <b className="grad-tx" style={{ cursor:"pointer" }} onClick={()=>{setMode("register");setStep(1);}}>הרשמה</b></p>
          </Card>
        ) : (
          <Card className="fu fu1" style={{ padding:24 }}>
            <div style={{ display:"flex", gap:6, marginBottom:18 }}>{[1,2].map(s=><div key={s} style={{ flex:1, height:4, borderRadius:2, background:s<=step?"var(--acc)":"var(--line2)" }}/>)}</div>
            {step===1 ? <>
              <Field label="שם"><input className="inp" value={f.name} onChange={e=>set("name",e.target.value)} placeholder="איך לקרוא לך?"/></Field>
              <Field label="אימייל"><input className="inp" type="email" value={f.email} onChange={e=>set("email",e.target.value)}/></Field>
              <Field label="סיסמה (6+ תווים)"><input className="inp" type="password" value={f.password} onChange={e=>set("password",e.target.value)}/></Field>
              <Btn onClick={()=>{ if(!f.name||!f.email||f.password.length<6) return notify("מלא שם, אימייל וסיסמה (6+)","bad"); setStep(2); }} style={{ width:"100%", marginTop:6 }}>המשך ←</Btn>
            </> : <>
              <Field label="מין"><Seg value={f.gender} opts={[["male","גבר"],["female","אישה"]]} onPick={v=>set("gender",v)}/></Field>
              <div style={{ display:"flex", gap:10 }}>
                <Field label="גיל"><input className="inp num" type="number" value={f.age} onChange={e=>set("age",+e.target.value)}/></Field>
                <Field label="גובה (ס״מ)"><input className="inp num" type="number" value={f.height_cm} onChange={e=>set("height_cm",+e.target.value)}/></Field>
              </div>
              <div style={{ display:"flex", gap:10 }}>
                <Field label="משקל נוכחי"><input className="inp num" type="number" value={f.weight_kg} onChange={e=>set("weight_kg",+e.target.value)}/></Field>
                <Field label="משקל יעד"><input className="inp num" type="number" value={f.target_weight_kg} onChange={e=>set("target_weight_kg",+e.target.value)}/></Field>
              </div>
              <Field label="רמת פעילות"><Seg value={f.activity_level} opts={[["low","מעט"],["intermediate","בינוני"],["high","גבוה"]]} onPick={v=>set("activity_level",v)}/></Field>
              <div style={{ display:"flex", gap:10, marginTop:4 }}>
                <Btn variant="ghost" onClick={()=>setStep(1)} style={{ flex:"0 0 auto" }}>→ חזרה</Btn>
                <Btn onClick={register} disabled={busy} style={{ flex:1 }}>{busy?<Spinner size={18}/>:"בוא נתחיל 🚀"}</Btn>
              </div>
            </>}
            <p style={{ textAlign:"center", marginTop:16, fontSize:14, color:"var(--tx2)" }}>כבר רשום? <b className="grad-tx" style={{ cursor:"pointer" }} onClick={()=>setMode("login")}>התחברות</b></p>
          </Card>
        )}
      </div>
    </div>
  );
};

/* ════════ HOME ════════ */
NX.Home = ({ user, plan, notify, go, openPlayer, programs }) => {
  const [nut, setNut] = useState(null);
  const [stats, setStats] = useState(null);
  useEffect(()=>{ NX.api.dailyNutrition().then(setNut).catch(()=>setNut({meals:[]})); NX.api.trainStats().then(setStats).catch(()=>{}); },[]);
  const consumed = sumCals(flattenMeals(nut));
  const remain = plan ? plan.goalCals - consumed : 0;
  const hour = new Date().getHours();
  const greet = hour<12?"בוקר טוב":hour<18?"צהריים טובים":"ערב טוב";
  const prog = programs?.[0];
  const todayDay = prog ? prog.days[(new Date().getDate()) % prog.days.length] : null;

  return (
    <div style={{ padding:"4px 0 12px" }}>
      <div className="fu" style={{ marginBottom:16 }}>
        <div style={{ color:"var(--tx2)", fontSize:14, fontWeight:600 }}>{greet},</div>
        <div style={{ fontSize:26, fontWeight:900, letterSpacing:"-.5px" }}>{user?.name||"מתאמן"} 👋</div>
      </div>

      {/* hero calories ring */}
      <Card className="fu fu1" glow style={{ display:"flex", alignItems:"center", gap:18, marginBottom:14 }}>
        <Ring value={remain>0?consumed:plan?.goalCals||0} max={plan?.goalCals||2000} size={118}
          label={fmt(Math.abs(remain))} sub={remain>=0?"קלוריות נותרו":"מעל היעד"} color={remain>=0?"var(--acc)":"var(--danger)"}/>
        <div style={{ flex:1 }}>
          <div style={{ fontSize:13, fontWeight:700, color:"var(--tx2)", marginBottom:8 }}>תקציב קלורי יומי</div>
          <Row k="יעד (גירעון)" v={fmt(plan?.goalCals)+" קק״ל"}/>
          <Row k="נצרך" v={fmt(consumed)+" קק״ל"}/>
          <Row k="גירעון מתוכנן" v={"−"+fmt(plan?.deficit)+" קק״ל"} c="var(--acc2)"/>
        </div>
      </Card>

      {/* metrics */}
      <div className="fu fu2" style={{ display:"grid", gridTemplateColumns:"1fr 1fr 1fr", gap:10, marginBottom:14 }}>
        <Metric emo="🔥" val={stats?.streak||0} label="ימי רצף"/>
        <Metric emo="⚖️" val={(user?.weight_kg||"—")} label="משקל" suf="ק״ג"/>
        <Metric emo="🎯" val={(user?.target_weight_kg||"—")} label="יעד" suf="ק״ג"/>
      </div>

      {/* today's workout CTA */}
      {todayDay && <Card className="fu fu3" style={{ marginBottom:14, position:"relative", overflow:"hidden" }}>
        <div style={{ position:"absolute", top:-30, left:-30, width:120, height:120, background:"radial-gradient(circle,var(--accDim),transparent 70%)" }}/>
        <div style={{ display:"flex", alignItems:"center", gap:14, position:"relative" }}>
          <div style={{ width:64, height:64 }}><ExerciseFigure exerciseKey={todayDay.items?.[0]?.key||"squat"} size={64}/></div>
          <div style={{ flex:1, minWidth:0 }}>
            <div style={{ fontSize:11, color:"var(--acc2)", fontWeight:800 }}>האימון של היום</div>
            <div style={{ fontSize:17, fontWeight:800 }}>{todayDay.name}</div>
            <div style={{ fontSize:12, color:"var(--tx2)", marginTop:2 }}>{todayDay.items.length} תרגילים · {prog.name_he}</div>
          </div>
        </div>
        <Btn onClick={()=>openPlayer(prog, prog.days.indexOf(todayDay))} style={{ width:"100%", marginTop:14 }}>התחל אימון 🚀</Btn>
      </Card>}

      {/* water quick */}
      <WaterCard plan={plan} notify={notify}/>
      <FastingCard notify={notify}/>
    </div>
  );
};
const Row = ({ k, v, c }) => <div style={{ display:"flex", justifyContent:"space-between", fontSize:13, marginBottom:4 }}><span style={{ color:"var(--tx2)" }}>{k}</span><b className="num" style={{ color:c||"var(--tx)" }}>{v}</b></div>;
const Metric = ({ emo, val, label, suf }) => <Card style={{ textAlign:"center", padding:"14px 8px" }}><div style={{ fontSize:22 }}>{emo}</div><div className="num" style={{ fontSize:22, fontWeight:900, marginTop:4 }}>{val}{suf&&<span style={{fontSize:11,color:"var(--tx2)"}}> {suf}</span>}</div><div style={{ fontSize:11, color:"var(--tx2)", fontWeight:600, marginTop:2 }}>{label}</div></Card>;

/* ════════ INTERMITTENT FASTING TIMER ════════ */
const FAST_PROTOCOLS = [["16:8",16],["18:6",18],["20:4",20],["OMAD",23]];
const FastingCard = ({ notify }) => {
  const [fast, setFast] = useState(undefined);   // undefined=loading, null=none, obj=active
  const [now, setNow] = useState(Date.now());
  const [picking, setPicking] = useState(false);
  const load = ()=> NX.api.fastingCurrent().then(d=>setFast(d.fast||null)).catch(()=>setFast(null));
  useEffect(()=>{ load(); },[]);
  useEffect(()=>{ if(!fast) return; const t=setInterval(()=>setNow(Date.now()),1000); return ()=>clearInterval(t); }, [fast]);
  const startFast = async (h)=>{ try{ await NX.api.fastingStart({ target_hours:h }); notify("צום התחיל! 🔥","ok"); setPicking(false); load(); }catch(e){ notify(e.message,"bad"); } };
  const endFast = async ()=>{ try{ await NX.api.fastingEnd(); notify("צום הסתיים 👏","ok"); load(); }catch(e){ notify(e.message,"bad"); } };
  if (fast === undefined) return null;
  if (!fast) return (
    <Card className="fu" style={{ marginBottom:14 }}>
      <div style={{ display:"flex", alignItems:"center", justifyContent:"space-between", marginBottom:picking?12:0 }}>
        <div style={{ fontWeight:800 }}>⏳ צום לסירוגין</div>
        {!picking ? <Btn onClick={()=>setPicking(true)} style={{ padding:"7px 14px", fontSize:13 }}>התחל צום</Btn>
                  : <button onClick={()=>setPicking(false)} style={{ color:"var(--tx3)", fontSize:13, background:"transparent" }}>ביטול</button>}
      </div>
      {picking && <div style={{ display:"flex", gap:8 }}>{FAST_PROTOCOLS.map(([l,h])=>
        <button key={l} onClick={()=>startFast(h)} style={{ flex:1, padding:"10px 4px", borderRadius:12, background:"var(--accDim)", border:"1px solid var(--acc)" }}>
          <div style={{ fontSize:14, fontWeight:800 }}>{l}</div><div style={{ fontSize:10, color:"var(--tx2)" }}>{h} שעות</div>
        </button>)}</div>}
    </Card>
  );
  const started = new Date(fast.started_at).getTime();
  const elapsedMs = Math.max(0, now - started);
  const targetMs = fast.target_hours*3600*1000;
  const pct = NX.clamp(elapsedMs/targetMs, 0, 1), done = elapsedMs >= targetMs;
  const f2 = (n)=>String(n).padStart(2,"0");
  const hh = Math.floor(elapsedMs/3600000), mm = Math.floor((elapsedMs%3600000)/60000), ss = Math.floor((elapsedMs%60000)/1000);
  return (
    <Card className="fu" glow={done} style={{ marginBottom:14, display:"flex", alignItems:"center", gap:16 }}>
      <Ring value={pct*100} max={100} size={104} stroke={10} color={done?"var(--acc2)":"var(--acc)"} label={f2(hh)+":"+f2(mm)} sub={done?"✓ הושלם":f2(ss)+" שנ׳"}/>
      <div style={{ flex:1 }}>
        <div style={{ fontWeight:800, marginBottom:4 }}>⏳ צום פעיל · {fast.target_hours} שעות</div>
        <div style={{ fontSize:12, color:"var(--tx2)", marginBottom:10 }}>{done?"מצוין! עברת את היעד 🔥":"נשארו ~"+Math.ceil((targetMs-elapsedMs)/3600000)+" שעות ליעד"}</div>
        <Btn variant="ghost" onClick={endFast} style={{ width:"100%" }}>סיים צום</Btn>
      </div>
    </Card>
  );
};

/* ════════ DAILY JOURNAL ════════ */
const MOODS = [[1,"😞"],[2,"😕"],[3,"😐"],[4,"🙂"],[5,"😄"]];
const JournalCard = ({ notify }) => {
  const [mood, setMood] = useState(0);
  const [text, setText] = useState("");
  const [saved, setSaved] = useState(false);
  useEffect(()=>{ NX.api.journalToday().then(e=>{ if(e){ setMood(e.mood||0); setText(e.entry_text||""); setSaved(true); } }).catch(()=>{}); },[]);
  const save = async ()=>{ try{ await NX.api.journalSave({ mood:mood||3, text }); notify("נשמר ביומן ✓","ok"); setSaved(true); }catch(e){ notify(e.message,"bad"); } };
  return (
    <Card className="fu fu3" style={{ marginBottom:14 }}>
      <div style={{ fontWeight:800, marginBottom:10 }}>💭 יומן יומי{saved && <span style={{ fontSize:11, color:"var(--acc2)", fontWeight:600 }}> · נשמר היום</span>}</div>
      <div style={{ display:"flex", justifyContent:"space-around", marginBottom:10 }}>
        {MOODS.map(([v,e])=><button key={v} onClick={()=>setMood(v)} style={{ fontSize:28, padding:"4px 8px", borderRadius:12, background:mood===v?"var(--accDim)":"transparent", border:"1px solid "+(mood===v?"var(--acc)":"transparent"), filter:mood===v?"none":"grayscale(.5)", opacity:mood===v?1:0.65, transition:"all .15s" }}>{e}</button>)}
      </div>
      <textarea className="inp" value={text} onChange={e=>setText(e.target.value)} placeholder="איך עבר היום? מחשבות, אתגרים, הצלחות…" rows={3} style={{ resize:"none", marginBottom:10, fontFamily:"inherit", lineHeight:1.5 }}/>
      <Btn onClick={save} style={{ width:"100%" }}>שמור ביומן</Btn>
    </Card>
  );
};

/* ════════ PROGRESS PHOTOS ════════ */
const ProgressPhotos = ({ notify }) => {
  const [photos, setPhotos] = useState([]);
  const [view, setView] = useState(null);
  const [busy, setBusy] = useState(false);
  const fileRef = useRef();
  const load = ()=> NX.api.progressPhotos().then(d=>setPhotos(d.photos||[])).catch(()=>{});
  useEffect(()=>{ load(); },[]);
  const onFile = async (e)=>{
    const f = e.target.files && e.target.files[0]; if(!f) return; setBusy(true);
    try { const url = await NX.compressImage(f, 1280, 0.75); await NX.api.addProgressPhoto({ image:url }); notify("תמונה נוספה 📸","ok"); load(); }
    catch(err){ notify(err.message||"שגיאה","bad"); } finally { setBusy(false); e.target.value=""; }
  };
  const del = async (id)=>{ try{ await NX.api.deleteProgressPhoto(id); setView(null); load(); }catch(e){} };
  return (
    <Card className="fu fu3" style={{ marginBottom:14 }}>
      <div style={{ display:"flex", alignItems:"center", justifyContent:"space-between", marginBottom:photos.length?10:8 }}>
        <div style={{ fontWeight:800 }}>📸 תמונות התקדמות</div>
        <input ref={fileRef} type="file" accept="image/*" capture="environment" onChange={onFile} style={{ display:"none" }}/>
        <Btn onClick={()=>fileRef.current&&fileRef.current.click()} disabled={busy} style={{ padding:"7px 14px", fontSize:13 }}>{busy?<Spinner size={14}/>:"+ הוסף"}</Btn>
      </div>
      {photos.length>0
        ? <div style={{ display:"grid", gridTemplateColumns:"1fr 1fr 1fr", gap:8 }}>
            {photos.map(p=><button key={p.id} onClick={()=>setView(p)} style={{ position:"relative", aspectRatio:"1/1", borderRadius:12, overflow:"hidden", background:"var(--card2)", padding:0 }}>
              <img src={p.image_url} alt="" style={{ width:"100%", height:"100%", objectFit:"cover", display:"block" }}/>
              <span style={{ position:"absolute", bottom:0, left:0, right:0, fontSize:9, background:"rgba(0,0,0,.55)", color:"#fff", padding:"2px 4px" }} className="num">{p.date}{p.weight_kg?` · ${p.weight_kg}ק״ג`:""}</span>
            </button>)}
          </div>
        : <div style={{ fontSize:12.5, color:"var(--tx2)", lineHeight:1.6 }}>צלם תמונת גוף מדי שבוע כדי לראות את השינוי לאורך זמן 💪</div>}
      <Sheet open={!!view} onClose={()=>setView(null)} title={view?("📸 "+view.date):""}>
        {view && <>
          <img src={view.image_url} alt="" style={{ width:"100%", borderRadius:14, marginBottom:12 }}/>
          {view.weight_kg && <div style={{ textAlign:"center", marginBottom:10, fontSize:14 }} className="num">⚖️ {view.weight_kg} ק״ג</div>}
          <Btn variant="ghost" onClick={()=>del(view.id)} style={{ width:"100%", color:"var(--danger)", borderColor:"rgba(239,68,68,.3)" }}>🗑 מחק תמונה</Btn>
        </>}
      </Sheet>
    </Card>
  );
};

const WaterCard = ({ plan, notify }) => {
  const goal = plan?.water || 2500; const cup = 250;
  const [ml, setMl] = useState(0);
  const [loaded, setLoaded] = useState(false);
  // load today's saved water on mount (persists across sessions; resets at midnight = new date)
  useEffect(()=>{ NX.api.waterToday().then(d=>{ setMl(d.amount_ml||0); setLoaded(true); }).catch(()=>setLoaded(true)); }, []);
  const cups = Math.round(goal/cup);
  const setTotal = (n) => { n=Math.max(0, Math.min(n, goal+cup*4)); setMl(n); NX.api.logWater({ amount_ml:n }).catch(()=>{}); }; // send cumulative total
  const pct = Math.round(ml/goal*100);
  return <Card className="fu fu4">
    <div style={{ display:"flex", justifyContent:"space-between", alignItems:"center", marginBottom:10 }}>
      <div style={{ fontWeight:800, fontSize:15 }}>💧 שתיית מים</div>
      <div className="num" style={{ fontSize:13, color:ml>=goal?"var(--acc2)":"var(--tx2)" }}>{fmt(ml)} / {fmt(goal)} מ״ל{ml>=goal?" ✓":""}</div>
    </div>
    <div style={{ display:"flex", gap:5, flexWrap:"wrap", marginBottom:12 }}>
      {Array.from({length:Math.min(cups,12)}).map((_,i)=><div key={i} style={{ flex:1, minWidth:16, height:30, borderRadius:6, background:(i*cup)<ml?"var(--acc)":"var(--card2)", border:"1px solid var(--line2)", transition:"background .3s" }}/>)}
    </div>
    <div style={{ display:"flex", gap:8 }}>
      <Btn variant="ghost" onClick={()=>setTotal(ml-cup)} disabled={ml<=0} style={{ flex:"0 0 auto", padding:"11px 16px" }}>−</Btn>
      <Btn variant="ghost" onClick={()=>setTotal(ml+cup)} style={{ flex:1 }}>+ כוס מים (250 מ״ל)</Btn>
    </div>
  </Card>;
};

/* ════════ TRAIN ════════ */
/* ════════ WORKOUT BUILDER (build your own workout) ════════ */
NX.WorkoutBuilder = ({ open, exercises=[], onClose, onSave, notify, editProgram=null }) => {
  const [name, setName] = useState("");
  const [items, setItems] = useState([]);    // {key,sets,reps}
  const [catF, setCatF] = useState("all");
  const [busy, setBusy] = useState(false);
  // opening for edit → prefill name + items from the existing program; otherwise start blank
  useEffect(()=>{ if(open){
    setName(editProgram?.name || "");
    setItems(editProgram ? (editProgram.items||[]).map(it=>({ key:it.key, sets:Math.max(1,Math.min(10,parseInt(it.sets)||3)), reps:String(it.reps==null?"10":it.reps) })) : []);
    setCatF("all"); setBusy(false);
  } }, [open]);

  const cats = ["all", ...Object.keys(CAT)];
  const list = exercises.filter(e => catF==="all" || e.category===catF);
  const exMapLocal = {}; exercises.forEach(e=>exMapLocal[e.anim_key]=e);
  const has = (k)=> items.some(i=>i.key===k);
  const toggle = (e)=> setItems(prev => prev.some(i=>i.key===e.anim_key)
    ? prev.filter(i=>i.key!==e.anim_key)
    : [...prev, { key:e.anim_key, sets:e.default_sets||3, reps:String(e.default_reps||"10") }]);
  const setField = (k,field,val)=> setItems(prev => prev.map(i=> i.key===k ? { ...i, [field]:val } : i));
  const remove = (k)=> setItems(prev => prev.filter(i=>i.key!==k));
  const move = (idx,dir)=> setItems(prev => { const a=[...prev], j=idx+dir; if(j<0||j>=a.length) return prev; [a[idx],a[j]]=[a[j],a[idx]]; return a; });

  const save = async ()=>{
    if(!name.trim()) return notify("תן שם לאימון","bad");
    if(!items.length) return notify("בחר לפחות תרגיל אחד","bad");
    setBusy(true);
    try { await onSave(name.trim(), items); } catch(e){ notify(e.message,"bad"); setBusy(false); }
  };

  return (
    <Sheet open={open} onClose={onClose} title={editProgram ? "✏️ ערוך אימון" : "🛠️ בנה אימון משלך"}>
      <Labeled label="שם האימון">
        <input className="inp" value={name} onChange={e=>setName(e.target.value)} placeholder="לדוגמה: אימון בטן · בוקר"/>
      </Labeled>

      <div style={{ fontSize:13, fontWeight:800, margin:"4px 0 8px" }}>תרגילים שנבחרו ({items.length})</div>
      {items.length===0
        ? <div style={{ padding:16, textAlign:"center", color:"var(--tx2)", fontSize:13, border:"1px dashed var(--line2)", borderRadius:14, marginBottom:14 }}>בחר תרגילים מהרשימה למטה ⬇️</div>
        : <div style={{ display:"flex", flexDirection:"column", gap:8, marginBottom:14 }}>
            {items.map((it,idx)=>{
              const ex = exMapLocal[it.key]||{};
              return (
                <div key={it.key} style={{ display:"flex", alignItems:"center", gap:9, padding:8, background:"var(--card2)", borderRadius:12 }}>
                  <span className="num" style={{ width:16, textAlign:"center", color:"var(--tx3)", fontWeight:800, fontSize:12 }}>{idx+1}</span>
                  <ExerciseThumb exerciseKey={it.key} size={40}/>
                  <div style={{ flex:1, minWidth:0 }}>
                    <div style={{ fontSize:13, fontWeight:700, whiteSpace:"nowrap", overflow:"hidden", textOverflow:"ellipsis" }}>{ex.name_he||it.key}</div>
                    <div style={{ display:"flex", gap:6, alignItems:"center", marginTop:5 }}>
                      <button onClick={()=>setField(it.key,"sets",Math.max(1,it.sets-1))} style={stepBtn}>−</button>
                      <span className="num" style={{ minWidth:30, textAlign:"center", fontSize:12, fontWeight:700 }}>{it.sets}</span>
                      <button onClick={()=>setField(it.key,"sets",Math.min(10,it.sets+1))} style={stepBtn}>+</button>
                      <span style={{ fontSize:10, color:"var(--tx3)" }}>סט</span>
                      <input className="inp num" value={it.reps} onChange={e=>setField(it.key,"reps",e.target.value)} style={{ padding:"5px 6px", width:62, fontSize:12, textAlign:"center" }}/>
                      <span style={{ fontSize:10, color:"var(--tx3)" }}>חזרות</span>
                    </div>
                  </div>
                  <div style={{ display:"flex", flexDirection:"column", gap:2 }}>
                    <button onClick={()=>move(idx,-1)} style={miniBtn}>▲</button>
                    <button onClick={()=>move(idx,1)} style={miniBtn}>▼</button>
                  </div>
                  <button onClick={()=>remove(it.key)} style={{ color:"var(--danger)", fontSize:15, padding:4 }}>✕</button>
                </div>
              );
            })}
          </div>}

      <div style={{ fontSize:13, fontWeight:800, marginBottom:8 }}>הוסף תרגילים</div>
      <div style={{ display:"flex", gap:7, flexWrap:"wrap", marginBottom:10 }}>
        {cats.map(c=><button key={c} onClick={()=>setCatF(c)} className={cls("chip", catF===c&&"on")}>{c==="all"?"הכל":CAT[c].emo+" "+CAT[c].he}</button>)}
      </div>
      <div style={{ display:"grid", gridTemplateColumns:"1fr 1fr", gap:9, marginBottom:16 }}>
        {list.map(e=>{
          const on = has(e.anim_key);
          return (
            <button key={e.id} onClick={()=>toggle(e)} style={{ position:"relative", padding:8, borderRadius:14, textAlign:"start", cursor:"pointer",
              background: on?"var(--accDim)":"var(--card2)", border:"1px solid "+(on?"var(--acc)":"var(--line)") }}>
              <div style={{ display:"flex", alignItems:"center", gap:8 }}>
                <ExerciseThumb exerciseKey={e.anim_key} size={36}/>
                <div style={{ minWidth:0 }}>
                  <div style={{ fontSize:12, fontWeight:700, whiteSpace:"nowrap", overflow:"hidden", textOverflow:"ellipsis" }}>{e.name_he}</div>
                  <div style={{ fontSize:10, color:"var(--tx3)" }}>{CAT[e.category]?.emo} {e.default_sets}×{e.default_reps}</div>
                </div>
              </div>
              <div style={{ position:"absolute", top:6, insetInlineEnd:8, fontSize:13, fontWeight:800, color:on?"var(--acc)":"var(--tx3)" }}>{on?"✓":"+"}</div>
            </button>
          );
        })}
      </div>

      <Btn onClick={save} disabled={busy} style={{ width:"100%" }}>{busy ? <Spinner size={18}/> : (editProgram?"עדכן אימון":"שמור אימון")+" ("+items.length+" תרגילים)"}</Btn>
    </Sheet>
  );
};

/* ════════ TRAIN ════════ */
NX.Train = ({ exercises, programs, myPrograms=[], reloadMy=()=>{}, challenges=[], reloadChallenges=()=>{}, exMap={}, openPlayer, notify }) => {
  const [tab, setTab] = useState("programs");
  const [catF, setCatF] = useState("all");
  const [detail, setDetail] = useState(null);
  const [building, setBuilding] = useState(false);
  const [editingProgram, setEditingProgram] = useState(null);   // null = create, else the program being edited
  const [openChallenge, setOpenChallenge] = useState(null);     // challenge_key of the one being viewed, or null
  const cats = ["all", ...Object.keys(CAT)];
  const list = exercises.filter(e => catF==="all" || e.category===catF);

  const closeBuilder = ()=>{ setBuilding(false); setEditingProgram(null); };
  const saveBuild = async (name, items)=>{
    if(editingProgram){ await NX.api.updateProgram(editingProgram.id, { name, items }); notify("האימון עודכן! 💪","ok"); }
    else { await NX.api.createProgram({ name, items }); notify("האימון נשמר! 💪","ok"); }
    closeBuilder(); reloadMy();
  };
  const delMy = async (p)=>{
    try { await NX.api.deleteProgram(p.id); notify("האימון נמחק","ok"); reloadMy(); }
    catch(e){ notify(e.message,"bad"); }
  };

  return (
    <div style={{ padding:"4px 0 12px" }}>
      <h1 className="fu" style={{ fontSize:24, fontWeight:900, marginBottom:14 }}>🤸 אימון</h1>
      <div className="fu fu1" style={{ display:"flex", gap:8, marginBottom:16 }}>
        {[["programs","תוכניות"],["library","ספריית תרגילים"]].map(([v,l])=>
          <button key={v} onClick={()=>setTab(v)} className={cls("btn", tab===v?"btn-primary":"btn-ghost")} style={{ flex:1, padding:"11px" }}>{l}</button>)}
      </div>

      {tab==="programs" ? (
        <div style={{ display:"flex", flexDirection:"column", gap:12 }}>
          {/* ── challenges ── */}
          {challenges.map(c => (
            <Card key={c.challenge_key} className="fu fu1" glow style={{ cursor:"pointer" }} onClick={()=>setOpenChallenge(c.challenge_key)}>
              <div style={{ display:"flex", alignItems:"center", gap:14 }}>
                <Ring value={c.completed_count} max={c.duration_days} size={64} stroke={7} color={c.color}
                  label={c.completed_count} sub={"/ "+c.duration_days}/>
                <div style={{ flex:1, minWidth:0 }}>
                  <div style={{ fontSize:16, fontWeight:800 }}>{c.icon} {c.name_he}</div>
                  <div style={{ fontSize:12, color:"var(--tx2)", marginTop:3 }}>{c.description_he}</div>
                  {c.streak>0 && <div style={{ fontSize:12, fontWeight:700, color:"var(--warn)", marginTop:4 }}>🔥 רצף {c.streak} ימים</div>}
                </div>
              </div>
            </Card>
          ))}

          {/* ── my custom workouts ── */}
          <Card className="fu fu1" glow={myPrograms.length>0}>
            <div style={{ display:"flex", alignItems:"center", justifyContent:"space-between", marginBottom: myPrograms.length?10:0 }}>
              <div style={{ fontSize:16, fontWeight:800 }}>💪 האימונים שלי</div>
              <Btn onClick={()=>{ setEditingProgram(null); setBuilding(true); }} style={{ padding:"7px 14px", fontSize:13 }}>+ בנה אימון</Btn>
            </div>
            {myPrograms.length>0
              ? <div style={{ display:"flex", flexDirection:"column", gap:8 }}>
                  {myPrograms.map(p=>(
                    <div key={p.id} style={{ display:"flex", alignItems:"center", gap:10, padding:"9px 11px", background:"var(--card2)", borderRadius:12 }}>
                      <div style={{ width:42, height:42, flexShrink:0, borderRadius:11, overflow:"hidden", background:"var(--card)" }}><ExerciseFigure exerciseKey={p.items[0]?.key||"squat"} size={42}/></div>
                      <div style={{ flex:1, minWidth:0 }}>
                        <div style={{ fontWeight:700, fontSize:14, whiteSpace:"nowrap", overflow:"hidden", textOverflow:"ellipsis" }}>{p.name}</div>
                        <div style={{ fontSize:11, color:"var(--tx2)" }}>{p.items.length} תרגילים</div>
                      </div>
                      <Btn onClick={()=>openPlayer(customToProgram(p), 0)} style={{ padding:"7px 15px", fontSize:13 }}>התחל</Btn>
                      <button onClick={()=>{ setEditingProgram(p); setBuilding(true); }} title="ערוך" style={{ color:"var(--tx3)", fontSize:15, padding:4 }}>✏️</button>
                      <button onClick={()=>delMy(p)} title="מחק" style={{ color:"var(--tx3)", fontSize:15, padding:4 }}>🗑</button>
                    </div>
                  ))}
                </div>
              : <div style={{ fontSize:13, color:"var(--tx2)", lineHeight:1.6 }}>בנה אימון משלך מהתרגילים שבמערכת — בחר תרגילים, קבע סטים וחזרות, והתחל לאמן 🔥</div>}
          </Card>

          {programs.map((p,i)=>(
            <Card key={p.id} className={cls("fu","fu"+Math.min(i+2,6))} style={{ position:"relative", overflow:"hidden" }}>
              <div style={{ display:"flex", alignItems:"center", gap:12, marginBottom:10 }}>
                <div style={{ width:54, height:54 }}><ExerciseFigure exerciseKey={p.days[0]?.items[0]?.key||"squat"} size={54}/></div>
                <div style={{ flex:1 }}>
                  <div style={{ fontSize:17, fontWeight:800 }}>{p.name_he}</div>
                  <div style={{ fontSize:12, color:"var(--tx2)", marginTop:2 }}>{p.days_per_week} ימים/שבוע · {p.days.length} אימונים</div>
                </div>
              </div>
              <p style={{ fontSize:13, color:"var(--tx2)", lineHeight:1.6, marginBottom:12 }}>{p.description_he}</p>
              <div style={{ display:"flex", flexDirection:"column", gap:7 }}>
                {p.days.map((d,di)=>(
                  <div key={di} style={{ display:"flex", alignItems:"center", justifyContent:"space-between", padding:"10px 12px", background:"var(--card2)", borderRadius:12 }}>
                    <div><div style={{ fontWeight:700, fontSize:14 }}>{d.name}</div><div style={{ fontSize:11, color:"var(--tx2)" }}>{d.items.length} תרגילים</div></div>
                    <Btn onClick={()=>openPlayer(p, di)} style={{ padding:"8px 16px", fontSize:13 }}>התחל</Btn>
                  </div>
                ))}
              </div>
            </Card>
          ))}
        </div>
      ) : (
        <>
          <div className="fu" style={{ display:"flex", gap:7, flexWrap:"wrap", marginBottom:12 }}>
            {cats.map(c=><button key={c} onClick={()=>setCatF(c)} className={cls("chip", catF===c&&"on")}>{c==="all"?"הכל":CAT[c].emo+" "+CAT[c].he}</button>)}
          </div>
          <div style={{ display:"grid", gridTemplateColumns:"1fr 1fr", gap:11 }}>
            {list.map((e,i)=>(
              <Card key={e.id} onClick={()=>setDetail(e)} className="fi" style={{ padding:12, cursor:"pointer", animationDelay:(i*0.03)+"s" }}>
                <div style={{ aspectRatio:"1/1", marginBottom:8, borderRadius:12, background:"var(--card2)", overflow:"hidden" }}><ExerciseFigure exerciseKey={e.anim_key} size="100%"/></div>
                <div style={{ fontSize:14, fontWeight:800, lineHeight:1.2 }}>{e.name_he}</div>
                <div style={{ display:"flex", gap:6, marginTop:6, alignItems:"center", flexWrap:"wrap" }}>
                  <span style={{ fontSize:10, fontWeight:700, color:CAT[e.category].col }}>{CAT[e.category].emo} {CAT[e.category].he}</span>
                  <span style={{ fontSize:10, fontWeight:700, color:DIFF[e.difficulty].col }}>● {DIFF[e.difficulty].he}</span>
                </div>
              </Card>
            ))}
          </div>
        </>
      )}

      <NX.ExerciseDetail ex={detail} onClose={()=>setDetail(null)}/>
      <NX.WorkoutBuilder open={building} exercises={exercises} onClose={closeBuilder} onSave={saveBuild} notify={notify} editProgram={editingProgram}/>
      {openChallenge && <NX.ChallengeDetail challengeKey={openChallenge} exMap={exMap}
        openPlayer={openPlayer} onClose={()=>setOpenChallenge(null)} notify={notify}/>}
    </div>
  );
};

/* ════════ CHALLENGE DETAIL (30-day grid) ════════ */
/* Shared photo+weight capture step — used by both the challenge Start
   screen and the Finish/Achievement screen. Both fields optional; "המשך"
   submits whatever was filled in (possibly nothing), "דלג" submits empty. */
const ChallengeCapture = ({ title, subtitle, busy, onSubmit, onSkip }) => {
  const [img, setImg] = useState(null);
  const [weight, setWeight] = useState("");
  const fileRef = useRef();
  const onFile = async (e) => {
    const f = e.target.files && e.target.files[0]; if (!f) return;
    try { setImg(await NX.compressImage(f, 1280, 0.75)); } catch (err) {}
    e.target.value = "";
  };
  return (
    <div style={{ display:"flex", flexDirection:"column", alignItems:"center", textAlign:"center", gap:12, padding:"4px 4px" }}>
      <div style={{ fontSize:16, fontWeight:800 }}>{title}</div>
      {subtitle && <p style={{ fontSize:13, color:"var(--tx2)", lineHeight:1.6, margin:0 }}>{subtitle}</p>}
      <input ref={fileRef} type="file" accept="image/*" capture="environment" onChange={onFile} style={{ display:"none" }}/>
      <button onClick={()=>fileRef.current && fileRef.current.click()} style={{ width:"100%", aspectRatio:"4/3", borderRadius:14, background:"var(--card2)", border:"1px dashed var(--line2)", display:"flex", alignItems:"center", justifyContent:"center", overflow:"hidden", padding:0 }}>
        {img ? <img src={img} alt="" style={{ width:"100%", height:"100%", objectFit:"cover" }}/> : <span style={{ fontSize:13, color:"var(--tx2)" }}>📷 הוסף תמונה (אופציונלי)</span>}
      </button>
      <input className="inp" type="number" inputMode="decimal" placeholder="משקל בק״ג (אופציונלי)" value={weight} onChange={e=>setWeight(e.target.value)} style={{ width:"100%" }}/>
      <Btn onClick={()=>onSubmit({ image: img, weight_kg: weight ? +weight : null })} disabled={busy} style={{ width:"100%" }}>
        {busy ? <Spinner size={16}/> : "המשך 🔥"}
      </Btn>
      <button onClick={onSkip} disabled={busy} style={{ fontSize:12, color:"var(--tx2)", padding:6, background:"transparent" }}>דלג</button>
    </div>
  );
};

/* Shown once the 30-day window has elapsed (current_day_number resolves to
   null), regardless of how many days were actually completed. Calls
   /finish automatically on first view (idempotent) so completed_at gets
   set even if the user never explicitly "finishes"; offers an optional
   after-photo/weight capture, then shows the before/after summary. */
NX.ChallengeAchievement = ({ challengeKey, data, notify, reload, onReset }) => {
  const [busy, setBusy] = useState(!data.completed_at);
  const [capturing, setCapturing] = useState(!data.completed_at);

  useEffect(() => {
    if (data.completed_at) return;
    NX.api.challengeFinish(challengeKey, {}).then(()=>reload()).catch(e=>notify(e.message,"bad")).finally(()=>setBusy(false));
    // eslint-disable-next-line
  }, []);

  const submitAfter = async ({ image, weight_kg }) => {
    setBusy(true);
    try { await NX.api.challengeFinish(challengeKey, { image, weight_kg }); await reload(); setCapturing(false); }
    catch(e) { notify(e.message, "bad"); } finally { setBusy(false); }
  };

  if (busy && !data.completed_at) return <div style={{ display:"flex", justifyContent:"center", padding:40 }}><Spinner size={26}/></div>;

  if (capturing) return (
    <ChallengeCapture title="📸 תיעוד סיום" subtitle="רוצה לתעד את התוצאה שלך? (אופציונלי)" busy={busy}
      onSubmit={submitAfter} onSkip={()=>setCapturing(false)}/>
  );

  const delta = (data.before?.weight_kg != null && data.after?.weight_kg != null)
    ? +(data.after.weight_kg - data.before.weight_kg).toFixed(1) : null;
  const doneCount = data.days.filter(d => d.status === "done").length;

  const Slot = ({ label, url }) => (
    <div style={{ flex:1 }}>
      <div style={{ fontSize:11, color:"var(--tx2)", marginBottom:6 }}>{label}</div>
      {url
        ? <img src={url} alt="" style={{ width:"100%", aspectRatio:"3/4", objectFit:"cover", borderRadius:12 }}/>
        : <div style={{ width:"100%", aspectRatio:"3/4", borderRadius:12, background:"var(--card2)", display:"flex", alignItems:"center", justifyContent:"center", fontSize:12, color:"var(--tx3)" }}>לא צולם</div>}
    </div>
  );

  return (
    <div style={{ display:"flex", flexDirection:"column", alignItems:"center", textAlign:"center", gap:14, padding:"4px 4px" }}>
      <div style={{ fontSize:44 }}>🏆</div>
      <div style={{ fontSize:18, fontWeight:800 }}>סיימת את האתגר!</div>
      <div style={{ display:"flex", gap:10, width:"100%" }}>
        <Slot label="לפני" url={data.before?.photo_url}/>
        <Slot label="אחרי" url={data.after?.photo_url}/>
      </div>
      {delta != null && <div style={{ fontSize:15, fontWeight:800 }}>{delta > 0 ? "+" : ""}{delta} ק״ג</div>}
      <div style={{ display:"flex", gap:16 }}>
        <div><div className="num" style={{ fontSize:22, fontWeight:900 }}>{doneCount}/{data.challenge.duration_days}</div><div style={{ fontSize:11, color:"var(--tx2)" }}>ימים הושלמו</div></div>
        <div><div className="num" style={{ fontSize:22, fontWeight:900 }}>{data.streak}</div><div style={{ fontSize:11, color:"var(--tx2)" }}>רצף ימים</div></div>
      </div>
      <button onClick={onReset} style={{ marginTop:6, fontSize:12, color:"var(--tx2)", padding:8, background:"transparent" }}>🔄 אפס והתחל מחדש</button>
    </div>
  );
};

NX.ChallengeDetail = ({ challengeKey, exMap, openPlayer, onClose, notify }) => {
  const [data, setData] = useState(null);      // full GET /challenges/:key response
  const [busyDay, setBusyDay] = useState(null); // day_number currently loading its items
  const [viewDay, setViewDay] = useState(null); // completed day being shown read-only
  const [busy, setBusy] = useState(false);      // start/finish capture in flight

  const load = useCallback(async () => {
    try { setData(await NX.api.challengeDetail(challengeKey)); }
    catch(e) { notify(e.message, "bad"); onClose(); }
  }, [challengeKey, notify, onClose]);
  useEffect(() => { load(); }, [load]);

  const openDay = async (day) => {
    setBusyDay(day.day_number);
    try {
      const full = await NX.api.challengeDay(challengeKey, day.day_number);
      if (day.status === "done") {
        setViewDay({ day_number: day.day_number, name_he: day.name_he, items: full.items });
      } else {
        const days = [];
        days[day.day_number - 1] = { name: day.name_he, items: full.items };
        openPlayer({ prog_key: "challenge_" + challengeKey, days }, day.day_number - 1);
      }
    } catch(e) { notify(e.message, "bad"); }
    finally { setBusyDay(null); }
  };

  const startCapture = async ({ image, weight_kg }) => {
    setBusy(true);
    try { await NX.api.challengeStart(challengeKey, { image, weight_kg }); await load(); }
    catch(e) { notify(e.message, "bad"); } finally { setBusy(false); }
  };

  const resetChallenge = async () => {
    if (!window.confirm("לאפס את האתגר ולהתחיל מחדש מיום 1? ההתקדמות הנוכחית לא תוצג יותר (האימונים עצמם נשארים בהיסטוריה שלך).")) return;
    setBusy(true);
    try { await NX.api.challengeReset(challengeKey); await load(); }
    catch(e) { notify(e.message, "bad"); } finally { setBusy(false); }
  };

  if (!data) return (
    <Sheet open={true} onClose={onClose} title="אתגר">
      <div style={{ display:"flex", justifyContent:"center", padding:40 }}><Spinner size={26}/></div>
    </Sheet>
  );

  const { challenge, days } = data;
  const notStarted = !data.started_at;
  const elapsed = data.started_at && data.current_day_number === null;

  return (
    <>
      <Sheet open={!viewDay} onClose={onClose} title={challenge.icon + " " + challenge.name_he}>
        {notStarted ? (
          <div style={{ display:"flex", flexDirection:"column", alignItems:"center", textAlign:"center", gap:14, padding:"12px 4px 4px" }}>
            <div style={{ fontSize:44 }}>{challenge.icon}</div>
            <div style={{ fontSize:18, fontWeight:800 }}>{challenge.name_he}</div>
            <p style={{ fontSize:13, color:"var(--tx2)", lineHeight:1.6 }}>{challenge.description_he}</p>
            <div style={{ fontSize:12, color:"var(--tx2)", background:"var(--card2)", borderRadius:12, padding:"10px 14px", lineHeight:1.6 }}>
              ⚕️ לפני שמתחילים — התייעצו עם רופא אם יש לכם מגבלה בריאותית או כאבים.
            </div>
            <ChallengeCapture title="📸 תיעוד לפני שמתחילים" subtitle="רוצה לתעד נקודת התחלה? (אופציונלי)" busy={busy} onSubmit={startCapture} onSkip={()=>startCapture({image:null, weight_kg:null})}/>
          </div>
        ) : elapsed ? (
          <NX.ChallengeAchievement challengeKey={challengeKey} data={data} notify={notify} reload={load} onReset={resetChallenge}/>
        ) : (
        <>
        <div style={{ display:"grid", gridTemplateColumns:"repeat(5,1fr)", gap:8 }}>
          {days.map(d => {
            const cfg = {
              done:   { bg: challenge.color+"33", border:`1px solid ${challenge.color}`, icon:"✓", clickable:true,  muted:false, iconColor:undefined },
              missed: { bg: "#ef444422", border:"1px solid #ef444455", icon:"✕", clickable:false, muted:true,  iconColor:"#ef4444" },
              today:  { bg: "var(--card2)", border:`2px solid ${challenge.color}`, icon:null, clickable:true,  muted:false, iconColor:undefined },
              locked: { bg: "var(--card)", border:"1px solid var(--line2)", icon:null, clickable:false, muted:true,  iconColor:undefined },
            }[d.status];
            return (
              <button key={d.day_number} onClick={()=>cfg.clickable && openDay(d)} disabled={busyDay===d.day_number || !cfg.clickable}
                style={{ aspectRatio:"1/1", borderRadius:12, background:cfg.bg, border:cfg.border, display:"flex",
                  flexDirection:"column", alignItems:"center", justifyContent:"center", gap:2,
                  cursor:cfg.clickable?"pointer":"default", opacity:cfg.muted?0.5:1 }}>
                {busyDay===d.day_number ? <Spinner size={14}/> : (
                  <>
                    <div style={{ fontSize:15, fontWeight:800, color:cfg.iconColor }}>{cfg.icon || d.day_number}</div>
                    {d.is_rest_day && <div style={{ fontSize:9 }}>🌿</div>}
                  </>
                )}
              </button>
            );
          })}
        </div>
        <button onClick={resetChallenge} disabled={busy} style={{ marginTop:14, width:"100%", fontSize:12, color:"var(--tx2)", padding:8, background:"transparent" }}>🔄 אפס והתחל מחדש</button>
        </>
        )}
      </Sheet>

      {viewDay && (
        <Sheet open={true} onClose={()=>setViewDay(null)} title={viewDay.name_he}>
          <div style={{ display:"flex", flexDirection:"column", gap:8 }}>
            {viewDay.items.map((it,i) => (
              <div key={i} style={{ display:"flex", alignItems:"center", gap:10, padding:"9px 11px", background:"var(--card2)", borderRadius:12 }}>
                <div style={{ width:36, height:36, flexShrink:0, borderRadius:10, overflow:"hidden" }}><ExerciseThumb exerciseKey={it.key} size={36}/></div>
                <div style={{ flex:1, fontWeight:700, fontSize:14 }}>{(exMap[it.key]||{}).name_he || it.key}</div>
                <div style={{ fontSize:12, color:"var(--tx2)" }}>{it.sets}×{it.reps}</div>
              </div>
            ))}
          </div>
        </Sheet>
      )}
    </>
  );
};

/* ════════ EXERCISE DETAIL ════════ */
NX.ExerciseDetail = ({ ex, onClose }) => {
  if (!ex) return null;
  const Sec = ({ icon, title, items, ol }) => items?.length ? <div style={{ marginBottom:16 }}>
    <div style={{ fontSize:13, fontWeight:800, marginBottom:8 }}>{icon} {title}</div>
    <div style={{ display:"flex", flexDirection:"column", gap:7 }}>
      {items.map((t,i)=><div key={i} style={{ display:"flex", gap:9, fontSize:13.5, color:"var(--tx2)", lineHeight:1.5 }}>
        {ol ? <span className="num" style={{ flexShrink:0, width:22, height:22, borderRadius:7, background:"var(--accDim)", color:"var(--acc2)", display:"flex", alignItems:"center", justifyContent:"center", fontSize:12, fontWeight:800 }}>{i+1}</span>
            : <span style={{ color:"var(--acc)", flexShrink:0 }}>•</span>}
        <span>{t}</span></div>)}
    </div></div> : null;
  return (
    <Sheet open={!!ex} onClose={onClose} title={ex.name_he}>
      <div style={{ aspectRatio:"1.4/1", borderRadius:18, background:"var(--card)", border:"1px solid var(--line)", marginBottom:14, overflow:"hidden", display:"flex", alignItems:"center", justifyContent:"center" }}>
        <NX.ExerciseFigure exerciseKey={ex.anim_key} size="80%"/>
      </div>
      <div style={{ display:"flex", gap:8, flexWrap:"wrap", marginBottom:16 }}>
        <span className="chip on">{CAT[ex.category].emo} {CAT[ex.category].he}</span>
        <span className="chip" style={{ color:DIFF[ex.difficulty].col, borderColor:DIFF[ex.difficulty].col }}>● {DIFF[ex.difficulty].he}</span>
        <span className="chip">🎯 {ex.default_sets}×{ex.default_reps}</span>
      </div>
      {ex.muscles?.length>0 && <div style={{ marginBottom:16 }}>
        <div style={{ fontSize:13, fontWeight:800, marginBottom:8 }}>💥 שרירים</div>
        <div style={{ display:"flex", gap:6, flexWrap:"wrap" }}>{ex.muscles.map(m=><span key={m} className="chip">{m}</span>)}</div>
      </div>}
      <Sec icon="📋" title="איך מבצעים" items={ex.steps} ol/>
      <Sec icon="✅" title="טיפים לביצוע נכון" items={ex.tips}/>
      <Sec icon="⚠️" title="טעויות נפוצות" items={ex.mistakes}/>
    </Sheet>
  );
};

/* ════════ WORKOUT PLAYER (full-screen overlay) ════════ */
NX.WorkoutPlayer = ({ program, dayIndex, exMap, user, onClose, onDone, notify }) => {
  const day = program.days[dayIndex];
  const items = day.items;
  const [idx, setIdx] = useState(0);
  const [setNo, setSetNo] = useState(1);
  const [resting, setResting] = useState(false);
  const [rest, setRest] = useState(45);
  const [startTs] = useState(Date.now());
  const item = items[idx];
  const ex = exMap[item.key] || {};

  useEffect(()=>{ if(!resting) return; if(rest<=0){ setResting(false); return; }
    const t = setTimeout(()=>setRest(r=>r-1), 1000); return ()=>clearTimeout(t); }, [resting, rest]);

  const finishSet = () => {
    if (setNo < item.sets) { setSetNo(s=>s+1); setRest(45); setResting(true); }
    else if (idx < items.length-1) { setIdx(i=>i+1); setSetNo(1); setRest(60); setResting(true); }
    else complete();
  };
  const complete = async () => {
    const dur = Math.round((Date.now()-startTs)/1000);
    const w = user?.weight_kg||80;
    const cal = Math.round(items.reduce((s,it)=>{ const e=exMap[it.key]||{}; return s+(e.met||5)*w*0.0175*(it.sets*1.6); },0));
    try { await NX.api.logWorkout({ prog_key:program.prog_key, day_index:dayIndex, title:day.name, exercises:items, duration_sec:dur, calories:cal }); } catch(e){}
    onDone({ cal, dur });
  };

  const skipRest = () => { setResting(false); setRest(45); };
  const progress = (idx + setNo/item.sets) / items.length;

  return (
    <div style={{ position:"fixed", inset:0, zIndex:500, background:"var(--bg)", display:"flex", flexDirection:"column", animation:"fadeIn .25s ease" }}>
      <div style={{ padding:"max(14px,env(safe-area-inset-top)) 18px 8px", display:"flex", alignItems:"center", justifyContent:"space-between" }}>
        <button onClick={onClose} style={{ width:36, height:36, borderRadius:"50%", background:"var(--card2)", fontSize:16 }}>✕</button>
        <div style={{ fontSize:13, fontWeight:700, color:"var(--tx2)" }}>{day.name}</div>
        <div className="num" style={{ fontSize:13, fontWeight:700, color:"var(--tx2)" }}>{idx+1}/{items.length}</div>
      </div>
      <div style={{ padding:"0 18px" }}><Bar value={progress*100} max={100}/></div>

      {resting ? (
        <div style={{ flex:1, display:"flex", flexDirection:"column", alignItems:"center", justifyContent:"center", gap:18, padding:24 }}>
          <div style={{ fontSize:15, color:"var(--tx2)", fontWeight:700 }}>מנוחה</div>
          <Ring value={45-rest} max={45} size={180} stroke={14} label={rest} sub="שניות" color="var(--info)"/>
          <div style={{ fontSize:14, color:"var(--tx2)" }}>הבא: <b style={{color:"var(--tx)"}}>{(exMap[items[setNo<item.sets?idx:idx+1]?.key]||{}).name_he||"סיום"}</b></div>
          <Btn variant="ghost" onClick={skipRest}>דלג על המנוחה ←</Btn>
        </div>
      ) : (
        <div style={{ flex:1, display:"flex", flexDirection:"column", alignItems:"center", justifyContent:"center", gap:10, padding:"10px 24px" }}>
          <div style={{ width:"100%", maxWidth:300, aspectRatio:"1/1" }}><NX.ExerciseFigure exerciseKey={item.key} size="100%"/></div>
          <div style={{ fontSize:24, fontWeight:900, textAlign:"center" }}>{ex.name_he}</div>
          <div style={{ display:"flex", gap:20, alignItems:"center" }}>
            <div style={{ textAlign:"center" }}><div className="num grad-tx" style={{ fontSize:30, fontWeight:900 }}>{setNo}<span style={{ fontSize:16, color:"var(--tx3)" }}>/{item.sets}</span></div><div style={{ fontSize:11, color:"var(--tx2)" }}>סט</div></div>
            <div style={{ width:1, height:34, background:"var(--line2)" }}/>
            <div style={{ textAlign:"center" }}><div className="num" style={{ fontSize:24, fontWeight:900 }}>{item.reps}</div><div style={{ fontSize:11, color:"var(--tx2)" }}>חזרות</div></div>
          </div>
        </div>
      )}

      {!resting && <div style={{ padding:"10px 18px max(18px,env(safe-area-inset-bottom))", display:"flex", gap:10 }}>
        <Btn onClick={finishSet} style={{ flex:1 }}>{(setNo>=item.sets && idx>=items.length-1) ? "סיים אימון 🏁" : "סיימתי סט ✓"}</Btn>
      </div>}
    </div>
  );
};

/* ════════ NUTRITION ════════ */
const MEAL_CHIPS = ["ארוחת בוקר","צהריים","ערב","ארוחת ביניים"];

/* ════════ FOOD SCAN (photograph a nutrition label) ════════ */
NX.FoodScan = ({ open, onClose, notify, onAdded }) => {
  const [stage, setStage] = useState("pick");   // pick | scanning | review
  const [data, setData] = useState(null);        // {name,kcal,p,c,f,conf} edited as per-100g
  const [imgUrl, setImgUrl] = useState(null);    // compressed photo data URL
  const [grams, setGrams] = useState("100");
  const [meal, setMeal] = useState("ארוחת ביניים");
  const [save, setSave] = useState(true);
  const [busy, setBusy] = useState(false);
  const fileRef = useRef(null);
  useEffect(()=>{ if(open){ setStage("pick"); setData(null); setImgUrl(null); setGrams("100"); setSave(true); setBusy(false); } }, [open]);

  const onFile = async (e) => {
    const file = e.target.files && e.target.files[0]; if(!file) return;
    setStage("scanning");
    try {
      const url = await NX.compressImage(file);
      setImgUrl(url);
      const r = await NX.api.scanFood(url);
      setData({ name:r.name, kcal:r.calories, p:r.protein_g, c:r.carbs_g, f:r.fat_g, conf:r.confidence, serving:r.serving_desc });
      setGrams("100");
      setStage("review");
    } catch(err) {
      notify(err.message || "שגיאת סריקה","bad"); setStage("pick");
    }
  };

  const g = Math.max(0, +grams||0);
  // values are stored/edited as per-100g; amount is grams (consistent with the existing picker)
  const calc = data ? { cal:Math.round(data.kcal*g/100), p:+(data.p*g/100).toFixed(1), c:+(data.c*g/100).toFixed(1), f:+(data.f*g/100).toFixed(1) } : null;
  const setF = (k,v)=> setData(d=>({ ...d, [k]:v }));

  const addToDay = async () => {
    if(!data || g<=0) return notify("בדוק את הערכים והכמות","bad");
    setBusy(true);
    let saveWarn = false;
    try {
      if (save) { try { await NX.api.saveFood({ name:data.name, kcal:+data.kcal, protein_g:+data.p, carbs_g:+data.c, fat_g:+data.f, image:imgUrl }); } catch(e){ saveWarn = true; } }
      const r = await NX.api.addMeal({ meal_type: HE_TO_TYPE[meal]||"snack" });
      await NX.api.addItem(r.meal_id||r.id, { food_name:`${data.name} (${g} ג׳)`, icon:"📷", amount_g:g, calories:calc.cal, protein_g:calc.p, carbs_g:calc.c, fat_g:calc.f });
      notify(saveWarn ? `נוסף ליום ✓ (המוצר לא נשמר)` : `נוסף! 📷 ${calc.cal} קק״ל`, saveWarn ? "info" : "ok"); onAdded && onAdded(); onClose();
    } catch(e){ notify(e.message,"bad"); } finally { setBusy(false); }
  };

  return (
    <Sheet open={open} onClose={onClose} title="📷 סריקת תווית תזונתית">
      {stage==="pick" && (<>
        <div style={{ textAlign:"center", padding:"10px 0 18px", color:"var(--tx2)", fontSize:13.5, lineHeight:1.6 }}>צלם את הטבלה התזונתית על האריזה — ה-AI יקרא את הקלוריות והערכים.</div>
        <input ref={fileRef} type="file" accept="image/*" capture="environment" onChange={onFile} style={{ display:"none" }}/>
        <Btn onClick={()=>fileRef.current && fileRef.current.click()} style={{ width:"100%" }}>📸 צלם / בחר תמונה</Btn>
      </>)}
      {stage==="scanning" && <div style={{ textAlign:"center", padding:"34px 0" }}><Spinner size={30}/><div style={{ marginTop:12, color:"var(--tx2)", fontSize:14 }}>קורא את התווית…</div></div>}
      {stage==="review" && data && (<>
        {imgUrl && <img src={imgUrl} alt="" style={{ width:"100%", maxHeight:150, objectFit:"contain", borderRadius:14, background:"var(--card2)", marginBottom:12 }}/>}
        {data.conf<0.5 && <div style={{ fontSize:12, color:"var(--warn)", marginBottom:10, textAlign:"center" }}>⚠️ ודא את הערכים — הקריאה לא ודאית</div>}
        <Labeled label="שם המוצר"><input className="inp" value={data.name} onChange={e=>setF("name",e.target.value)}/></Labeled>
        <div style={{ fontSize:12, color:"var(--tx3)", marginBottom:8 }}>ערכים ל-100 ג׳ (ניתן לתקן):</div>
        <div style={{ display:"flex", gap:8, marginBottom:12 }}>
          {[["kcal","קלוריות"],["p","חלבון"],["c","פחמ׳"],["f","שומן"]].map(([k,l])=>(
            <div key={k} style={{ flex:1 }}><div style={{ fontSize:10, color:"var(--tx3)", marginBottom:4, textAlign:"center" }}>{l}</div>
              <input className="inp num" type="number" value={data[k]} onChange={e=>setF(k,e.target.value)} style={{ textAlign:"center", padding:"8px 4px" }}/></div>
          ))}
        </div>
        <Labeled label="כמה אכלת (גרם)"><input className="inp num" type="number" value={grams} onChange={e=>setGrams(e.target.value)} style={{ textAlign:"center", fontSize:18 }}/></Labeled>
        <div style={{ display:"flex", gap:6, marginBottom:12 }}>{[50,100,150,200].map(x=><button key={x} onClick={()=>setGrams(String(x))} className={cls("chip",+grams===x&&"on")} style={{ flex:1, justifyContent:"center" }}>{x}</button>)}</div>
        <div style={{ textAlign:"center", padding:"10px", borderRadius:12, background:"var(--accDim)", border:"1px solid var(--acc)", marginBottom:12 }}>
          <span className="num grad-tx" style={{ fontSize:26, fontWeight:900 }}>{calc.cal}</span> <span style={{ fontSize:12, color:"var(--tx2)" }}>קק״ל · {calc.p}ח׳ {calc.c}פ׳ {calc.f}ש׳</span>
        </div>
        <div style={{ marginBottom:12 }}><div style={{ fontSize:12, fontWeight:700, color:"var(--tx2)", marginBottom:6 }}>איזו ארוחה?</div>
          <div style={{ display:"flex", gap:6, flexWrap:"wrap" }}>{MEAL_CHIPS.map(m=><button key={m} onClick={()=>setMeal(m)} className={cls("chip",meal===m&&"on")}>{m}</button>)}</div></div>
        <label style={{ display:"flex", alignItems:"center", gap:8, marginBottom:14, fontSize:13, color:"var(--tx2)", cursor:"pointer" }}>
          <input type="checkbox" checked={save} onChange={e=>setSave(e.target.checked)}/> שמור את המוצר במזונות שלי (עם תמונה)
        </label>
        <Btn onClick={addToDay} disabled={busy} style={{ width:"100%" }}>{busy ? <Spinner size={18}/> : "הוסף ליום "+calc.cal+" קק״ל"}</Btn>
        <button onClick={()=>setStage("pick")} style={{ width:"100%", marginTop:8, color:"var(--tx2)", fontSize:13, fontWeight:600, padding:8, background:"transparent" }}>↺ צלם שוב</button>
      </>)}
    </Sheet>
  );
};

/* ════════ MEAL SCAN (estimate calories from a photo of the dish) ════════ */
NX.MealScan = ({ open, onClose, notify, onAdded }) => {
  const [stage, setStage] = useState("pick");   // pick | scanning | review
  const [items, setItems] = useState([]);        // {name,emoji,grams,k100,p100,c100,f100}
  const [imgUrl, setImgUrl] = useState(null);
  const [meal, setMeal] = useState("ארוחת ביניים");
  const [conf, setConf] = useState(1);
  const [busy, setBusy] = useState(false);
  const fileRef = useRef(null);
  useEffect(()=>{ if(open){ setStage("pick"); setItems([]); setImgUrl(null); setConf(1); setBusy(false); } }, [open]);
  const uid = ()=> Math.random().toString(36).slice(2) + Date.now().toString(36);

  // normalize an AI item (per-portion) to per-100g so editing grams recalculates calories
  const norm = (it)=>{ const g=Math.max(1,+it.grams||100); return { _id:uid(), name:it.name||"מאכל", emoji:it.emoji||"🍽️", grams:g, k100:(+it.calories||0)*100/g, p100:(+it.protein_g||0)*100/g, c100:(+it.carbs_g||0)*100/g, f100:(+it.fat_g||0)*100/g }; };
  const onFile = async (e)=>{
    const file=e.target.files && e.target.files[0]; if(!file) return;
    setStage("scanning");
    try {
      const url=await NX.compressImage(file); setImgUrl(url);
      const r=await NX.api.scanMeal(url);
      setItems((r.items||[]).map(norm)); setConf(r.confidence||0); setStage("review");
    } catch(err){ notify(err.message||"שגיאת זיהוי","bad"); setStage("pick"); }
  };

  const calOf = (it)=> Math.round(it.k100*it.grams/100);
  const total = items.reduce((s,it)=>s+calOf(it),0);
  const totP  = Math.round(items.reduce((s,it)=>s+it.p100*it.grams/100,0));
  const setItem = (i,patch)=> setItems(prev=>prev.map((it,j)=>j===i?{...it,...patch}:it));
  const setGrams = (i,v)=> setItem(i,{ grams:Math.max(1,Math.round(+v||0)) });
  const setCal = (i,v)=> setItems(prev=>prev.map((it,j)=> j===i ? { ...it, k100: it.grams>0 ? (Math.max(0,+v||0)*100/it.grams) : 0 } : it));
  const remove = (i)=> setItems(prev=>prev.filter((_,j)=>j!==i));
  const addItem = ()=> setItems(prev=>[...prev,{ _id:uid(), name:"מאכל חדש", emoji:"🍽️", grams:100, k100:0,p100:0,c100:0,f100:0 }]);

  const addToDay = async ()=>{
    if(!items.length) return notify("אין פריטים להוספה","bad");
    setBusy(true);
    try {
      const r=await NX.api.addMeal({ meal_type: HE_TO_TYPE[meal]||"snack" });
      const mid=r.meal_id||r.id;
      for(const it of items){ const g=it.grams; if(g<=0) continue;
        await NX.api.addItem(mid,{ food_name:`${it.name} (${g} ג׳)`, icon:it.emoji||"📸", amount_g:g, calories:Math.round(it.k100*g/100), protein_g:+(it.p100*g/100).toFixed(1), carbs_g:+(it.c100*g/100).toFixed(1), fat_g:+(it.f100*g/100).toFixed(1) });
      }
      notify(`נוסף! 📸 ${total} קק״ל`,"ok"); onAdded&&onAdded(); onClose();
    } catch(e){ notify(e.message,"bad"); } finally { setBusy(false); }
  };

  return (
    <Sheet open={open} onClose={onClose} title="📸 זיהוי קלוריות מהמנה">
      {stage==="pick" && (<>
        <div style={{ textAlign:"center", padding:"10px 0 18px", color:"var(--tx2)", fontSize:13.5, lineHeight:1.6 }}>צלם את הצלחת — ה-AI יזהה את המאכלים ויעריך את הקלוריות.<br/><span style={{ fontSize:11, color:"var(--tx3)" }}>זו הערכה — אפשר לתקן הכל אחרי הזיהוי.</span></div>
        <input ref={fileRef} type="file" accept="image/*" capture="environment" onChange={onFile} style={{ display:"none" }}/>
        <Btn onClick={()=>fileRef.current&&fileRef.current.click()} style={{ width:"100%" }}>📸 צלם / בחר תמונה</Btn>
      </>)}
      {stage==="scanning" && <div style={{ textAlign:"center", padding:"34px 0" }}><Spinner size={30}/><div style={{ marginTop:12, color:"var(--tx2)", fontSize:14 }}>מזהה את המנה…</div></div>}
      {stage==="review" && (<>
        {imgUrl && <img src={imgUrl} alt="" style={{ width:"100%", maxHeight:140, objectFit:"contain", borderRadius:14, background:"var(--card2)", marginBottom:10 }}/>}
        {conf<0.5 && <div style={{ fontSize:12, color:"var(--warn)", marginBottom:8, textAlign:"center" }}>⚠️ הערכה לא ודאית — בדוק ותקן את הפריטים</div>}
        <div style={{ fontSize:12, color:"var(--tx3)", marginBottom:8 }}>תקן שם / משקל / קלוריות, מחק או הוסף פריט:</div>
        <div style={{ display:"flex", flexDirection:"column", gap:8, marginBottom:10 }}>
          {items.map((it,i)=>(
            <div key={it._id} style={{ background:"var(--card2)", borderRadius:12, padding:8 }}>
              <div style={{ display:"flex", alignItems:"center", gap:6, marginBottom:6 }}>
                <span style={{ fontSize:20 }}>{it.emoji}</span>
                <input className="inp" value={it.name} onChange={e=>setItem(i,{name:e.target.value})} style={{ flex:1, padding:"6px 8px", fontSize:13 }}/>
                <button onClick={()=>remove(i)} title="מחק" style={{ color:"var(--danger)", fontSize:15, padding:4 }}>🗑</button>
              </div>
              <div style={{ display:"flex", gap:6, alignItems:"center" }}>
                <div style={{ flex:1, display:"flex", alignItems:"center", gap:4 }}>
                  <input className="inp num" type="number" value={it.grams} onChange={e=>setGrams(i,e.target.value)} style={{ padding:"5px", textAlign:"center", fontSize:12 }}/>
                  <span style={{ fontSize:11, color:"var(--tx3)" }}>ג׳</span>
                </div>
                <div style={{ flex:1, display:"flex", alignItems:"center", gap:4 }}>
                  <input className="inp num" type="number" value={calOf(it)} onChange={e=>setCal(i,e.target.value)} style={{ padding:"5px", textAlign:"center", fontSize:12 }}/>
                  <span style={{ fontSize:11, color:"var(--tx3)" }}>קק״ל</span>
                </div>
              </div>
            </div>
          ))}
          {items.length===0 && <div style={{ textAlign:"center", color:"var(--tx2)", fontSize:13, padding:14 }}>לא נותרו פריטים — הוסף ידנית ⬇️</div>}
        </div>
        <button onClick={addItem} style={{ width:"100%", padding:10, borderRadius:12, border:"1px dashed var(--line2)", background:"transparent", color:"var(--tx2)", fontSize:13, fontWeight:700, marginBottom:12 }}>+ הוסף פריט</button>
        <div style={{ textAlign:"center", padding:"10px", borderRadius:12, background:"var(--accDim)", border:"1px solid var(--acc)", marginBottom:12 }}>
          <span className="num grad-tx" style={{ fontSize:26, fontWeight:900 }}>{total}</span> <span style={{ fontSize:12, color:"var(--tx2)" }}>קק״ל סה״כ · {totP}ג׳ חלבון</span>
        </div>
        <div style={{ marginBottom:12 }}><div style={{ fontSize:12, fontWeight:700, color:"var(--tx2)", marginBottom:6 }}>איזו ארוחה?</div>
          <div style={{ display:"flex", gap:6, flexWrap:"wrap" }}>{MEAL_CHIPS.map(m=><button key={m} onClick={()=>setMeal(m)} className={cls("chip",meal===m&&"on")}>{m}</button>)}</div></div>
        <Btn onClick={addToDay} disabled={busy||!items.length} style={{ width:"100%" }}>{busy?<Spinner size={18}/>:"הוסף ליום "+total+" קק״ל"}</Btn>
        <button onClick={()=>setStage("pick")} style={{ width:"100%", marginTop:8, color:"var(--tx2)", fontSize:13, fontWeight:600, padding:8, background:"transparent" }}>↺ צלם שוב</button>
      </>)}
    </Sheet>
  );
};

/* ════════ LIVE BARCODE SCANNER ════════ */
NX.BarcodeScanner = ({ open, onClose, onCode, notify }) => {
  const videoRef = useRef();
  const readerRef = useRef(null);
  useEffect(()=>{
    if(!open) return;
    let stopped = false;
    (async()=>{
      try {
        const ZX = await NX.loadZXing();
        // limit to product-barcode formats + work harder per frame → far better detection
        const hints = new Map();
        hints.set(ZX.DecodeHintType.POSSIBLE_FORMATS, [
          ZX.BarcodeFormat.EAN_13, ZX.BarcodeFormat.EAN_8,
          ZX.BarcodeFormat.UPC_A, ZX.BarcodeFormat.UPC_E, ZX.BarcodeFormat.CODE_128,
        ]);
        hints.set(ZX.DecodeHintType.TRY_HARDER, true);
        const reader = new ZX.BrowserMultiFormatReader(hints);
        readerRef.current = reader;
        // request a high-res rear camera — low resolution is the #1 cause of failed barcode decode
        await reader.decodeFromConstraints(
          { video: { facingMode: { ideal:"environment" }, width:{ ideal:1920 }, height:{ ideal:1080 } } },
          videoRef.current,
          (result)=>{ if (result && !stopped) { stopped = true; try{ reader.reset(); }catch(e){} onCode(result.getText()); } }
        );
      } catch(e){ notify(e.message || "לא ניתן לפתוח את המצלמה","bad"); onClose(); }
    })();
    return ()=>{ stopped = true; try{ readerRef.current && readerRef.current.reset(); }catch(e){} };
  }, [open]);
  if(!open) return null;
  return (
    <div style={{ position:"fixed", inset:0, zIndex:600, background:"#000", display:"flex", flexDirection:"column" }}>
      <div style={{ padding:"max(14px,env(safe-area-inset-top)) 18px 12px", display:"flex", alignItems:"center", justifyContent:"space-between", color:"#fff" }}>
        <div style={{ fontWeight:800 }}>🏷️ סרוק ברקוד</div>
        <button onClick={onClose} style={{ width:34, height:34, borderRadius:"50%", background:"rgba(255,255,255,.15)", color:"#fff", fontSize:15 }}>✕</button>
      </div>
      <div style={{ flex:1, position:"relative", overflow:"hidden" }}>
        <video ref={videoRef} muted playsInline style={{ width:"100%", height:"100%", objectFit:"cover" }}/>
        <div style={{ position:"absolute", top:"50%", left:"50%", transform:"translate(-50%,-50%)", width:"72%", height:130, border:"3px solid var(--acc)", borderRadius:16, boxShadow:"0 0 0 9999px rgba(0,0,0,.4)", overflow:"hidden" }}>
          <div style={{ position:"absolute", left:0, right:0, height:2, background:"var(--acc)", boxShadow:"0 0 8px var(--acc)", animation:"scanline 1.8s ease-in-out infinite" }}/>
        </div>
        <div style={{ position:"absolute", bottom:34, left:0, right:0, textAlign:"center", color:"#fff", fontSize:13.5, lineHeight:1.5 }}>כוון את הברקוד למסגרת<br/><span style={{ opacity:.7, fontSize:12 }}>קרב את המכשיר עד שהפסים חדים</span></div>
      </div>
    </div>
  );
};

NX.Nutrition = ({ plan, notify, scanAllowed, isAdmin }) => {
  const [nut, setNut] = useState(null);
  const [add, setAdd] = useState(false);
  const [picked, setPicked] = useState(null);   // selected food from DB
  const [grams, setGrams] = useState("100");
  const [meal, setMeal] = useState("ארוחת ביניים");
  const [search, setSearch] = useState("");
  const [catF, setCatF] = useState("all");
  const [manual, setManual] = useState(false);
  const [mItem, setMItem] = useState({ name:"", calories:"", protein:"" });
  const [scanOpen, setScanOpen] = useState(false);
  const [mealOpen, setMealOpen] = useState(false);
  const [myFoods, setMyFoods] = useState([]);   // user's saved custom foods
  const [savedMeals, setSavedMeals] = useState([]);
  const [saveTarget, setSaveTarget] = useState(null);  // {name, items} being saved as a quick meal
  const [logTarget, setLogTarget] = useState(null);    // saved meal being logged
  const [logType, setLogType] = useState("ארוחת ביניים");
  const load = ()=> NX.api.dailyNutrition().then(setNut).catch(()=>setNut({meals:[]}));
  const loadFoods = ()=> NX.api.customFoods().then(d=>setMyFoods(d.foods||[])).catch(()=>{});
  const loadSaved = ()=> NX.api.savedMeals().then(d=>setSavedMeals(d.meals||[])).catch(()=>{});
  useEffect(()=>{ load(); loadFoods(); loadSaved(); },[]);
  const doSaveMeal = async ()=>{
    if(!saveTarget?.name?.trim()) return notify("תן שם לארוחה","bad");
    try { await NX.api.saveMeal({ name:saveTarget.name.trim(), items:saveTarget.items }); notify("נשמר כארוחה מהירה ⭐","ok"); setSaveTarget(null); loadSaved(); }
    catch(e){ notify(e.message,"bad"); }
  };
  const doLogSaved = async ()=>{
    if(!logTarget) return;
    try {
      const r = await NX.api.addMeal({ meal_type: HE_TO_TYPE[logType]||"snack" });
      const mid = r.meal_id||r.id;
      for(const it of logTarget.items) await NX.api.addItem(mid, it);
      notify(`נוסף! ⭐ ${logTarget.name}`,"ok"); setLogTarget(null); load();
    } catch(e){ notify(e.message,"bad"); }
  };
  const delSaved = async (id)=>{ try{ await NX.api.deleteSavedMeal(id); loadSaved(); }catch(e){} };
  const [barcodeOpen, setBarcodeOpen] = useState(false);
  const [bcEdit, setBcEdit] = useState(false);   // admin chose to edit a catalog product's details
  const [editItem, setEditItem] = useState(null);  // a logged meal item whose quantity is being edited
  const [editG, setEditG] = useState("");
  const handleBarcodeCode = async (code)=>{
    setBarcodeOpen(false); setBcEdit(false);
    notify("מחפש מוצר… 🏷️","info");
    try {
      const prod = await NX.api.barcodeLookup(code);   // found in shared catalog or OpenFoodFacts
      setPicked({ he:prod.name, emo:"🏷️", img:prod.image_url||null, imgData:null, cat:"barcode", barcode:code, source:prod.source, kcal:prod.kcal, p:prod.p, c:prod.c, f:prod.f });
      setGrams("100"); setManual(false); setAdd(true);
      notify((prod.source==="catalog"?"מהמאגר: ":"נמצא: ")+prod.name+" ✓","ok");
    } catch(err){
      // not found anywhere → open an empty editor to create the product
      setPicked({ he:"", emo:"🏷️", img:null, imgData:null, cat:"barcode", barcode:code, source:"new", kcal:0, p:0, c:0, f:0 });
      setGrams("100"); setManual(false); setAdd(true);
      notify("מוצר חדש — מלא פרטים והוסף","info");
    }
  };
  // show the edit fields when the product is new / from OFF, or when an admin chose to edit a catalog entry
  const bcEditable = (p)=> p && p.cat==="barcode" && (p.source!=="catalog" || bcEdit);
  // capture/choose a product photo → keep a compressed data-URL for upload + preview
  const onProductPhoto = async (file)=>{
    if(!file) return;
    try { const dataUrl = await NX.compressImage(file, 800, 0.7); setPicked(p=>({ ...p, img:dataUrl, imgData:dataUrl })); }
    catch(e){ notify("בעיה בטעינת התמונה","bad"); }
  };
  // save to the shared catalog (only when something is editable to save), then optionally log to a meal
  const saveAndAdd = async ()=>{
    if(!picked || g<=0) return notify("הזן משקל","bad");
    if(!String(picked.he||"").trim()) return notify("כתוב שם מוצר","bad");
    if(!(picked.kcal>0)) return notify("הזן קלוריות","bad");
    try {
      if(bcEditable(picked))   // new / OFF / admin → build or refresh the shared catalog
        await NX.api.saveBarcodeProduct({ barcode:picked.barcode, name:picked.he.trim(), kcal:picked.kcal, protein_g:picked.p, carbs_g:picked.c, fat_g:picked.f, image:picked.imgData||null });
      const r = await NX.api.addMeal({ meal_type: HE_TO_TYPE[meal]||"snack" });
      await NX.api.addItem(r.meal_id||r.id, { food_name:`${picked.he.trim()} (${g} ג׳)`, icon:"🏷️", amount_g:g, calories:calc.cal, protein_g:calc.p, carbs_g:calc.c, fat_g:calc.f });
      notify(`נוסף! 🏷️ ${calc.cal} קק״ל`,"ok"); load(); closeSheet();
    } catch(e){ notify(e.message,"bad"); }
  };
  // admin-only: save/update the catalog without logging a meal
  const saveToCatalog = async ()=>{
    if(!String(picked.he||"").trim()) return notify("כתוב שם מוצר","bad");
    if(!(picked.kcal>0)) return notify("הזן קלוריות","bad");
    try {
      await NX.api.saveBarcodeProduct({ barcode:picked.barcode, name:picked.he.trim(), kcal:picked.kcal, protein_g:picked.p, carbs_g:picked.c, fat_g:picked.f, image:picked.imgData||null });
      notify("נשמר למאגר ✓","ok"); closeSheet();
    } catch(e){ notify(e.message,"bad"); }
  };
  // custom foods mapped to the picker's food shape (per-100g)
  const myAsFoods = myFoods.map(x=>({ he:x.name, emo:"📷", cat:"mine", kcal:x.kcal, p:x.protein_g, c:x.carbs_g, f:x.fat_g, img:x.image_url }));
  const meals = flattenMeals(nut);
  const consumed = sumCals(meals);
  const protein = sumProt(meals);

  const closeSheet = ()=>{ setAdd(false); setPicked(null); setManual(false); setBcEdit(false); setSearch(""); setCatF("all"); };

  // live auto-calc from picked food + grams
  const g = Math.max(0, +grams||0);
  const calc = picked ? {
    cal: Math.round(picked.kcal*g/100), p:+(picked.p*g/100).toFixed(1),
    c:+(picked.c*g/100).toFixed(1), f:+(picked.f*g/100).toFixed(1),
  } : null;

  // live recompute for editing a logged item's quantity (scales proportionally from its current amount)
  const eOldG = editItem ? (+editItem.amount_g||0) : 0;
  const eNg = Math.max(0, +editG||0);
  const eRate = eOldG>0 ? eNg/eOldG : 0;
  const ePrev = editItem ? {
    cal: Math.round((+editItem.calories||0)*eRate),
    p:+(((+editItem.protein_g||0)*eRate).toFixed(1)),
    c:+(((+editItem.carbs_g||0)*eRate).toFixed(1)),
    f:+(((+editItem.fat_g||0)*eRate).toFixed(1)),
  } : null;
  const eName = editItem ? itemName(editItem).replace(/\s*\(\s*\d+(\.\d+)?\s*ג׳\s*\)\s*$/,"") : "";
  const doUpdateQty = async ()=>{
    if(eNg<=0) return notify("הזן כמות","bad");
    if(eOldG<=0) return notify("לא ניתן לשנות כמות לפריט זה","bad");
    try { await NX.api.updateItem(editItem.id, eNg); notify("הכמות עודכנה ✓","ok"); setEditItem(null); load(); }
    catch(e){ notify(e.message,"bad"); }
  };

  const addFood = async () => {
    if(!picked || g<=0) return notify("בחר מזון והזן משקל","bad");
    if(picked.cat==="barcode" && !String(picked.he||"").trim()) return notify("כתוב שם מוצר","bad");
    try {
      const r = await NX.api.addMeal({ meal_type: HE_TO_TYPE[meal]||"snack" });
      await NX.api.addItem(r.meal_id||r.id, { food_name:`${picked.he} (${g} ג׳)`, icon:picked.emo, amount_g:g, calories:calc.cal, protein_g:calc.p, carbs_g:calc.c, fat_g:calc.f });
      notify(`נוסף! ${picked.emo} ${calc.cal} קק״ל`,"ok"); load(); closeSheet();
    } catch(e){ notify(e.message,"bad"); }
  };
  const addManual = async () => {
    if(!mItem.name||!mItem.calories) return notify("מלא שם וקלוריות","bad");
    try {
      const r = await NX.api.addMeal({ meal_type: HE_TO_TYPE[meal]||"snack" });
      await NX.api.addItem(r.meal_id||r.id, { food_name:mItem.name, calories:+mItem.calories, protein_g:+mItem.protein||0 });
      notify("נוסף! 🍽️","ok"); setMItem({name:"",calories:"",protein:""}); load(); closeSheet();
    } catch(e){ notify(e.message,"bad"); }
  };

  const baseFoods = catF==="mine" ? [] : (NX.FOODS||[]);
  const prependMine = (catF==="all"||catF==="mine") ? myAsFoods : [];
  const foods = [ ...prependMine, ...baseFoods ]
    .filter(x => (catF==="all"||catF==="mine"||x.cat===catF) && (!search || x.he.includes(search)));
  const MealPicker = () => (
    <div style={{ marginBottom:14 }}>
      <div style={{ fontSize:12, fontWeight:700, color:"var(--tx2)", marginBottom:6 }}>איזו ארוחה?</div>
      <div style={{ display:"flex", gap:6, flexWrap:"wrap" }}>{MEAL_CHIPS.map(m=><button key={m} onClick={()=>setMeal(m)} className={cls("chip",meal===m&&"on")}>{m}</button>)}</div>
    </div>
  );

  return (
    <div style={{ padding:"4px 0 12px" }}>
      <h1 className="fu" style={{ fontSize:24, fontWeight:900, marginBottom:14 }}>🍎 תזונה</h1>
      <Card className="fu fu1" glow style={{ display:"flex", alignItems:"center", gap:18, marginBottom:14 }}>
        <Ring value={consumed} max={plan?.goalCals||2000} size={110} label={fmt(consumed)} sub={"מתוך "+fmt(plan?.goalCals)} color={consumed>(plan?.goalCals||9999)?"var(--danger)":"var(--acc)"}/>
        <div style={{ flex:1 }}>
          <Row k="חלבון" v={fmt(protein)+" / "+fmt(plan?.protein)+" ג׳"} c="var(--info)"/>
          <div style={{ margin:"6px 0 10px" }}><Bar value={protein} max={plan?.protein||150} color="var(--info)"/></div>
          <Row k="נותרו" v={fmt((plan?.goalCals||0)-consumed)+" קק״ל"} c="var(--acc2)"/>
        </div>
      </Card>
      <Btn onClick={()=>setAdd(true)} style={{ width:"100%", marginBottom:10 }}>+ הוסף מזון</Btn>
      <Btn variant="ghost" onClick={()=>setBarcodeOpen(true)} style={{ width:"100%", marginBottom:scanAllowed?10:16 }}>🏷️ סרוק ברקוד מוצר</Btn>
      {scanAllowed && <Btn variant="ghost" onClick={()=>setScanOpen(true)} style={{ width:"100%", marginBottom:10 }}>📷 סרוק תווית תזונתית</Btn>}
      {scanAllowed && <Btn variant="ghost" onClick={()=>setMealOpen(true)} style={{ width:"100%", marginBottom:16 }}>📸 צלם מנה (הערכת קלוריות)</Btn>}

      {savedMeals.length>0 && <div style={{ marginBottom:16 }}>
        <div style={{ fontSize:12, fontWeight:700, color:"var(--tx2)", marginBottom:8 }}>⭐ ארוחות מהירות — הקש להוספה</div>
        <div style={{ display:"flex", gap:8, overflowX:"auto", paddingBottom:4, WebkitOverflowScrolling:"touch" }}>
          {savedMeals.map(sm=>{
            const cal = sm.items.reduce((s,it)=>s+(+it.calories||0),0);
            return <div key={sm.id} style={{ flexShrink:0, display:"flex", alignItems:"center", gap:8, padding:"8px 12px", borderRadius:14, background:"var(--card2)", border:"1px solid var(--line)" }}>
              <button onClick={()=>{ setLogType("ארוחת ביניים"); setLogTarget(sm); }} style={{ textAlign:"start", background:"transparent" }}>
                <div style={{ fontSize:13, fontWeight:700, whiteSpace:"nowrap" }}>{sm.name}</div>
                <div className="num" style={{ fontSize:11, color:"var(--tx3)" }}>{cal} קק״ל · {sm.items.length} פריטים</div>
              </button>
              <button onClick={()=>delSaved(sm.id)} title="מחק" style={{ color:"var(--tx3)", fontSize:13 }}>✕</button>
            </div>;
          })}
        </div>
      </div>}

      {meals.length===0 ? <Card className="fu" style={{ textAlign:"center", padding:30, color:"var(--tx2)" }}>עדיין לא רשמת ארוחות היום 🍽️</Card> :
        meals.map(m=>(
          <Card key={m.id} className="fu" style={{ marginBottom:10 }}>
            <div style={{ display:"flex", justifyContent:"space-between", alignItems:"center", marginBottom:8 }}>
              <div style={{ fontWeight:800 }}>{m.name||m.meal_type}</div>
              {m.items.length>0 && <button onClick={()=>setSaveTarget({ name:(m.name||"ארוחה"), items:m.items.map(it=>({ food_name:itemName(it), icon:it.icon||"🍽️", amount_g:it.amount_g||0, calories:itemCals(it), protein_g:it.protein_g||0, carbs_g:it.carbs_g||0, fat_g:it.fat_g||0 })) })} style={{ fontSize:12, color:"var(--acc2)", fontWeight:700, background:"transparent" }}>⭐ שמור</button>}
            </div>
            {m.items.map(it=>(
              <div key={it.id} style={{ display:"flex", justifyContent:"space-between", alignItems:"center", padding:"7px 0", borderTop:"1px solid var(--line)" }}>
                <span style={{ fontSize:14 }}>{it.icon||"🍽️"} {itemName(it)}</span>
                <span style={{ display:"flex", gap:10, alignItems:"center" }}>
                  <b className="num" style={{ fontSize:14 }}>{fmt(itemCals(it))}</b>
                  <button onClick={()=>{ setEditItem(it); setEditG(String(it.amount_g||100)); }} title="ערוך כמות" style={{ color:"var(--tx3)", fontSize:14 }}>✏️</button>
                  <button onClick={async()=>{ await NX.api.delItem(it.id).catch(()=>{}); load(); }} style={{ color:"var(--tx3)", fontSize:14 }}>🗑</button>
                </span>
              </div>
            ))}
          </Card>
        ))}

      <Sheet open={add} onClose={closeSheet} title={picked?"כמה אכלת?":manual?"הזנה ידנית":"בחר מזון"}>
        {/* ── manual entry ── */}
        {manual ? (<>
          <div style={{ marginBottom:12 }}><div style={{ fontSize:12, fontWeight:700, color:"var(--tx2)", marginBottom:6 }}>שם המזון</div><input className="inp" value={mItem.name} onChange={e=>setMItem({...mItem,name:e.target.value})}/></div>
          <div style={{ display:"flex", gap:10, marginBottom:12 }}>
            <div style={{ flex:1 }}><div style={{ fontSize:12, fontWeight:700, color:"var(--tx2)", marginBottom:6 }}>קלוריות</div><input className="inp num" type="number" value={mItem.calories} onChange={e=>setMItem({...mItem,calories:e.target.value})}/></div>
            <div style={{ flex:1 }}><div style={{ fontSize:12, fontWeight:700, color:"var(--tx2)", marginBottom:6 }}>חלבון (ג׳)</div><input className="inp num" type="number" value={mItem.protein} onChange={e=>setMItem({...mItem,protein:e.target.value})}/></div>
          </div>
          <MealPicker/>
          <Btn onClick={addManual} style={{ width:"100%", marginBottom:8 }}>הוסף</Btn>
          <button onClick={()=>setManual(false)} style={{ width:"100%", color:"var(--tx2)", fontSize:13, fontWeight:600, padding:8 }}>← חזרה לבחירה ויזואלית</button>
        </>)

        /* ── grams + auto-calc for picked food ── */
        : picked ? (<>
          <div style={{ display:"flex", alignItems:"center", gap:12, padding:14, borderRadius:14, background:"var(--card2)", marginBottom: picked.cat==="barcode"?10:14 }}>
            {picked.img ? <img src={picked.img} alt="" style={{ width:46, height:46, borderRadius:10, objectFit:"cover" }}/> : <span style={{ fontSize:34 }}>{picked.emo}</span>}
            <div style={{ flex:1 }}>
              <div style={{ fontWeight:800, fontSize:16 }}>{picked.cat==="barcode" ? (bcEditable(picked) ? "מוצר סרוק 🏷️" : picked.he) : picked.he}</div>
              <div style={{ fontSize:12, color:"var(--tx2)" }}>{picked.kcal} קק״ל ל-100 ג׳{picked.cat==="barcode"&&picked.source==="catalog" ? " · מהמאגר" : ""}</div>
            </div>
          </div>
          {/* admin may opt-in to correct a product that's already in the shared catalog */}
          {picked.cat==="barcode" && picked.source==="catalog" && isAdmin && !bcEdit && (
            <button onClick={()=>setBcEdit(true)} style={{ marginBottom:14, color:"var(--tx2)", fontSize:13, fontWeight:600, background:"transparent" }}>✏️ ערוך פרטים במאגר</button>
          )}
          {/* scanned-product editor: name + photo + per-100g macros (new / OpenFoodFacts / admin-edit) */}
          {bcEditable(picked) && (<>
            <div style={{ marginBottom:12 }}>
              <div style={{ fontSize:12, fontWeight:700, color:"var(--tx2)", marginBottom:6 }}>שם המוצר</div>
              <input className="inp" value={picked.he} onChange={e=>setPicked({ ...picked, he:e.target.value })} placeholder="כתוב שם מוצר…" maxLength={60}/>
            </div>
            <div style={{ marginBottom:12 }}>
              <div style={{ fontSize:12, fontWeight:700, color:"var(--tx2)", marginBottom:6 }}>תמונת מוצר (אופציונלי)</div>
              <label style={{ display:"flex", alignItems:"center", justifyContent:"center", gap:8, padding:"11px", borderRadius:14, border:"1px dashed var(--line2)", color:"var(--tx2)", fontSize:14, fontWeight:700, cursor:"pointer" }}>
                {picked.img ? "📷 החלף תמונה" : "📷 צלם / בחר תמונה"}
                <input type="file" accept="image/*" capture="environment" onChange={e=>onProductPhoto(e.target.files&&e.target.files[0])} style={{ display:"none" }}/>
              </label>
            </div>
            <div style={{ fontSize:12, fontWeight:700, color:"var(--tx2)", marginBottom:6 }}>ערכים ל-100 גרם</div>
            <div style={{ display:"flex", gap:8, marginBottom:14 }}>
              {[["kcal","קלוריות"],["p","חלבון"],["c","פחמ׳"],["f","שומן"]].map(([k,l])=>(
                <div key={k} style={{ flex:1 }}>
                  <input className="inp num" type="number" value={picked[k]} onChange={e=>setPicked({ ...picked, [k]:Math.max(0,+e.target.value||0) })} style={{ textAlign:"center", padding:"9px 4px" }}/>
                  <div style={{ fontSize:10, color:"var(--tx3)", textAlign:"center", marginTop:3 }}>{l}</div>
                </div>
              ))}
            </div>
          </>)}
          <div style={{ fontSize:12, fontWeight:700, color:"var(--tx2)", marginBottom:6 }}>משקל (גרם)</div>
          <input className="inp num" type="number" value={grams} onChange={e=>setGrams(e.target.value)} style={{ marginBottom:8, fontSize:20, textAlign:"center" }}/>
          <div style={{ display:"flex", gap:6, marginBottom:14 }}>{[50,100,150,200,300].map(x=><button key={x} onClick={()=>setGrams(String(x))} className={cls("chip",+grams===x&&"on")} style={{ flex:1, justifyContent:"center" }}>{x}</button>)}</div>
          {/* live preview */}
          <div style={{ display:"flex", gap:10, marginBottom:14 }}>
            <div style={{ flex:1.4, textAlign:"center", padding:"14px 8px", borderRadius:14, background:"var(--accDim)", border:"1px solid var(--acc)" }}>
              <div className="num grad-tx" style={{ fontSize:30, fontWeight:900 }}>{calc.cal}</div><div style={{ fontSize:11, color:"var(--tx2)" }}>קלוריות</div>
            </div>
            {[["חלבון",calc.p,"var(--info)"],["פחמ׳",calc.c,"var(--warn)"],["שומן",calc.f,"var(--tx2)"]].map(([l,v,c])=>
              <div key={l} style={{ flex:1, textAlign:"center", padding:"14px 4px", borderRadius:14, background:"var(--card2)" }}>
                <div className="num" style={{ fontSize:18, fontWeight:800, color:c }}>{v}</div><div style={{ fontSize:10, color:"var(--tx3)" }}>{l} ג׳</div>
              </div>)}
          </div>
          <MealPicker/>
          {picked.cat==="barcode" ? (<>
            <Btn onClick={saveAndAdd} style={{ width:"100%", marginBottom:8 }}>הוסף לארוחה — {calc.cal} קק״ל</Btn>
            {isAdmin && bcEditable(picked) && <Btn variant="ghost" onClick={saveToCatalog} style={{ width:"100%", marginBottom:8 }}>💾 שמור למאגר בלבד</Btn>}
          </>) : (
            <Btn onClick={addFood} style={{ width:"100%", marginBottom:8 }}>הוסף {calc.cal} קק״ל</Btn>
          )}
          <button onClick={()=>setPicked(null)} style={{ width:"100%", color:"var(--tx2)", fontSize:13, fontWeight:600, padding:8 }}>{picked.cat==="barcode" ? "← ביטול" : "← בחר מזון אחר"}</button>
        </>)

        /* ── visual food picker ── */
        : (<>
          <input className="inp" value={search} onChange={e=>setSearch(e.target.value)} placeholder="🔎 חיפוש מזון..." style={{ marginBottom:10 }}/>
          <div style={{ display:"flex", gap:6, flexWrap:"wrap", marginBottom:10 }}>
            <button onClick={()=>setCatF("all")} className={cls("chip",catF==="all"&&"on")}>הכל</button>
            {myFoods.length>0 && <button onClick={()=>setCatF("mine")} className={cls("chip",catF==="mine"&&"on")}>📷 שלי</button>}
            {NX.FOOD_CATS.map(([v,l])=><button key={v} onClick={()=>setCatF(v)} className={cls("chip",catF===v&&"on")}>{l}</button>)}
          </div>
          <div style={{ display:"grid", gridTemplateColumns:"1fr 1fr 1fr", gap:8, marginBottom:12 }}>
            {foods.map((x,i)=>(
              <button key={i} onClick={()=>{ setPicked(x); setGrams("100"); }} style={{ padding:"12px 6px", borderRadius:14, background:"var(--card2)", border:"1px solid var(--line)", textAlign:"center", cursor:"pointer" }}>
                {x.img ? <img src={x.img} alt="" style={{ width:34, height:34, borderRadius:8, objectFit:"cover", display:"block", margin:"0 auto" }}/> : <div style={{ fontSize:26 }}>{x.emo}</div>}
                <div style={{ fontSize:11.5, fontWeight:700, marginTop:4, lineHeight:1.2 }}>{x.he}</div>
                <div className="num" style={{ fontSize:10, color:"var(--tx3)", marginTop:2 }}>{x.kcal}/100ג׳</div>
              </button>
            ))}
            {foods.length===0 && <div style={{ gridColumn:"1/-1", textAlign:"center", color:"var(--tx2)", padding:20, fontSize:13 }}>לא נמצא — נסה הזנה ידנית</div>}
          </div>
          <button onClick={()=>setManual(true)} style={{ width:"100%", padding:12, borderRadius:14, border:"1px dashed var(--line2)", background:"transparent", color:"var(--tx2)", fontSize:14, fontWeight:700 }}>✏️ הזנה ידנית</button>
        </>)}
      </Sheet>

      {/* edit the quantity of an already-logged item */}
      <Sheet open={!!editItem} onClose={()=>setEditItem(null)} title="✏️ עריכת כמות">
        {editItem && <>
          <div style={{ display:"flex", alignItems:"center", gap:12, padding:14, borderRadius:14, background:"var(--card2)", marginBottom:14 }}>
            <span style={{ fontSize:30 }}>{editItem.icon||"🍽️"}</span>
            <div style={{ fontWeight:800, fontSize:16 }}>{eName}</div>
          </div>
          <div style={{ fontSize:12, fontWeight:700, color:"var(--tx2)", marginBottom:6 }}>משקל (גרם)</div>
          <input className="inp num" type="number" value={editG} onChange={e=>setEditG(e.target.value)} style={{ marginBottom:8, fontSize:20, textAlign:"center" }}/>
          <div style={{ display:"flex", gap:6, marginBottom:14 }}>{[50,100,150,200,300].map(x=><button key={x} onClick={()=>setEditG(String(x))} className={cls("chip",+editG===x&&"on")} style={{ flex:1, justifyContent:"center" }}>{x}</button>)}</div>
          <div style={{ display:"flex", gap:10, marginBottom:14 }}>
            <div style={{ flex:1.4, textAlign:"center", padding:"14px 8px", borderRadius:14, background:"var(--accDim)", border:"1px solid var(--acc)" }}>
              <div className="num grad-tx" style={{ fontSize:30, fontWeight:900 }}>{ePrev.cal}</div><div style={{ fontSize:11, color:"var(--tx2)" }}>קלוריות</div>
            </div>
            {[["חלבון",ePrev.p,"var(--info)"],["פחמ׳",ePrev.c,"var(--warn)"],["שומן",ePrev.f,"var(--tx2)"]].map(([l,v,c])=>
              <div key={l} style={{ flex:1, textAlign:"center", padding:"14px 4px", borderRadius:14, background:"var(--card2)" }}>
                <div className="num" style={{ fontSize:18, fontWeight:800, color:c }}>{v}</div><div style={{ fontSize:10, color:"var(--tx3)" }}>{l} ג׳</div>
              </div>)}
          </div>
          <Btn onClick={doUpdateQty} style={{ width:"100%", marginBottom:8 }}>עדכן — {ePrev.cal} קק״ל</Btn>
          <button onClick={()=>setEditItem(null)} style={{ width:"100%", color:"var(--tx2)", fontSize:13, fontWeight:600, padding:8 }}>← ביטול</button>
        </>}
      </Sheet>

      <NX.FoodScan open={scanOpen} onClose={()=>setScanOpen(false)} notify={notify} onAdded={()=>{ load(); loadFoods(); }}/>
      <NX.MealScan open={mealOpen} onClose={()=>setMealOpen(false)} notify={notify} onAdded={()=>{ load(); }}/>
      <NX.BarcodeScanner open={barcodeOpen} onClose={()=>setBarcodeOpen(false)} onCode={handleBarcodeCode} notify={notify}/>

      <Sheet open={!!saveTarget} onClose={()=>setSaveTarget(null)} title="⭐ שמור כארוחה מהירה">
        {saveTarget && <>
          <Labeled label="שם הארוחה"><input className="inp" value={saveTarget.name} onChange={e=>setSaveTarget(t=>({...t,name:e.target.value}))} placeholder="לדוגמה: ארוחת בוקר שלי"/></Labeled>
          <div style={{ fontSize:12, color:"var(--tx2)", marginBottom:12 }}>{saveTarget.items.length} פריטים · {saveTarget.items.reduce((s,it)=>s+(+it.calories||0),0)} קק״ל — תוכל להוסיף אותה בלחיצה אחת בעתיד</div>
          <Btn onClick={doSaveMeal} style={{ width:"100%" }}>שמור ⭐</Btn>
        </>}
      </Sheet>

      <Sheet open={!!logTarget} onClose={()=>setLogTarget(null)} title={logTarget?("הוסף: "+logTarget.name):""}>
        {logTarget && <>
          <div style={{ fontSize:12, fontWeight:700, color:"var(--tx2)", marginBottom:6 }}>לאיזו ארוחה?</div>
          <div style={{ display:"flex", gap:6, flexWrap:"wrap", marginBottom:14 }}>{MEAL_CHIPS.map(mc=><button key={mc} onClick={()=>setLogType(mc)} className={cls("chip",logType===mc&&"on")}>{mc}</button>)}</div>
          <Btn onClick={doLogSaved} style={{ width:"100%" }}>הוסף ליום ({logTarget.items.reduce((s,it)=>s+(+it.calories||0),0)} קק״ל)</Btn>
        </>}
      </Sheet>
    </div>
  );
};

/* ════════ PROGRESS ════════ */
NX.Progress = ({ user, plan, notify, onWeight }) => {
  const [logs, setLogs] = useState([]);
  const [stats, setStats] = useState(null);
  const [wreport, setWreport] = useState(null);
  const [weekly, setWeekly] = useState(null);
  const [achv, setAchv] = useState(null);
  const [wOpen, setWOpen] = useState(false);
  const [wVal, setWVal] = useState(String(user?.weight_kg||""));
  const [wBusy, setWBusy] = useState(false);
  const [confirmReset, setConfirmReset] = useState(false);
  useEffect(()=>{ NX.api.workoutLogs().then(d=>setLogs(Array.isArray(d.logs)?d.logs:[])).catch(()=>{}); NX.api.trainStats().then(setStats).catch(()=>{}); NX.api.weightReport().then(setWreport).catch(()=>{}); NX.api.weekly().then(setWeekly).catch(()=>{}); NX.api.achievements().then(setAchv).catch(()=>{}); },[]);
  useEffect(()=>{ if(wOpen){ setWVal(String(user?.weight_kg||"")); setConfirmReset(false); } }, [wOpen, user]);
  const R = window.Recharts;
  const rawW = (wreport && (wreport.data||wreport.points||wreport.weights||wreport.history)) || [];
  const wpoints = (Array.isArray(rawW)?rawW:[]).map(p=>({ d:String(p.date||p.log_date||p.logged_at||"").slice(5,10), w:p.weight_kg||p.weight })).filter(p=>p.w);
  const curW = wpoints.length ? wpoints[wpoints.length-1].w : (user?.weight_kg||null);
  const goalW = user?.target_weight_kg;
  const startW = wpoints.length ? wpoints[0].w : (user?.weight_kg||null);
  const lost = (startW!=null && curW!=null) ? Math.round((startW-curW)*10)/10 : 0;   // >0 = ירד
  const remaining = (curW!=null && goalW) ? Math.round((curW-goalW)*10)/10 : null;   // >0 = עוד לרדת
  const reached = !!(goalW && curW!=null && ((startW>=goalW && curW<=goalW) || (startW<=goalW && curW>=goalW)));
  const progressPct = (goalW && startW!=null && startW!==goalW) ? NX.clamp((startW-curW)/(startW-goalW)*100, 0, 100) : 0;
  const ws = wpoints.map(p=>p.w);
  const lo = Math.floor(Math.min(...ws, goalW||Infinity) - 1), hi = Math.ceil(Math.max(...ws, goalW||-Infinity) + 1);
  const yDomain = (isFinite(lo) && isFinite(hi)) ? [lo, hi] : ["dataMin-1","dataMax+1"];
  const lastPt = wpoints[wpoints.length-1];
  const fmtTick = (v)=>{ const a=String(v).split("-"); return a.length===2 ? (+a[1])+"."+(+a[0]) : v; };

  const saveWeight = async () => {
    const w = +wVal;
    if(!w || w<=0) return notify("הזן משקל תקין","bad");
    setWBusy(true);
    try {
      await NX.api.logWeight({ weight_kg:w, weight:w });
      try { const me = await NX.api.me(); onWeight && onWeight(me.user||me); } catch(e){}
      const d = await NX.api.weightReport().catch(()=>null); if(d) setWreport(d);
      notify("המשקל נשמר ✓ — עודכן גם בפרופיל","ok"); setWOpen(false);
    } catch(e){ notify(e.message,"bad"); } finally { setWBusy(false); }
  };

  const doReset = async () => {
    const w = +wVal;
    setWBusy(true);
    try {
      await NX.api.resetWeight({ weight_kg: w>0 ? w : undefined });
      try { const me = await NX.api.me(); onWeight && onWeight(me.user||me); } catch(e){}
      const d = await NX.api.weightReport().catch(()=>null); if(d) setWreport(d);
      notify("היסטוריית המשקל אופסה ✓","ok"); setConfirmReset(false); setWOpen(false);
    } catch(e){ notify(e.message,"bad"); } finally { setWBusy(false); }
  };

  return (
    <div style={{ padding:"4px 0 12px" }}>
      <div className="fu" style={{ display:"flex", alignItems:"center", justifyContent:"space-between", marginBottom:14 }}>
        <h1 style={{ fontSize:24, fontWeight:900 }}>📈 התקדמות</h1>
        <button onClick={async()=>{ try{ const {csv}=await NX.api.exportData(); const url=URL.createObjectURL(new Blob([csv],{type:"text/csv;charset=utf-8"})); const a=document.createElement("a"); a.href=url; a.download="nutrios-data.csv"; a.click(); URL.revokeObjectURL(url); notify("הנתונים יוצאו ✓","ok"); }catch(e){ notify(e.message,"bad"); } }} className="chip" style={{ fontSize:11, cursor:"pointer" }}>📤 ייצוא CSV</button>
      </div>
      <div className="fu fu1" style={{ display:"grid", gridTemplateColumns:"1fr 1fr 1fr", gap:10, marginBottom:14 }}>
        <Metric emo="🏋️" val={stats?.total_workouts||0} label="אימונים"/>
        <Metric emo="🔥" val={stats?.streak||0} label="רצף"/>
        <Metric emo="⚡" val={fmt(stats?.total_calories||0)} label="קק״ל שרופות"/>
      </div>

      <Card className="fu fu2" style={{ marginBottom:14 }}>
        <div style={{ fontWeight:800, marginBottom:12 }}>⚖️ המשקל שלי</div>

        {curW!=null && (<>
          {/* start → now → goal, in plain language */}
          <div style={{ display:"flex", alignItems:"stretch", gap:7, marginBottom:12 }}>
            {[["התחלה",startW,"start"],["עכשיו",curW,"now"],["יעד",goalW,"goal"]].map(([lab,val,kind],i)=>(
              <React.Fragment key={lab}>
                {i>0 && <div style={{ alignSelf:"center", color:"var(--tx3)", fontSize:15 }}>←</div>}
                <div style={{ flex:1, textAlign:"center", padding:"10px 4px", borderRadius:14,
                  background: kind==="now"?"var(--accDim)":"var(--card2)", border:"1px solid "+(kind==="now"?"var(--acc)":"var(--line)") }}>
                  <div className="num" style={{ fontSize: kind==="now"?24:18, fontWeight:900, color: kind==="goal"?"var(--acc2)":"var(--tx)" }}>{val!=null?val:"—"}</div>
                  <div style={{ fontSize:11, color:"var(--tx2)", marginTop:2 }}>{lab}{val!=null?" ק״ג":""}</div>
                </div>
              </React.Fragment>
            ))}
          </div>

          {/* progress toward goal */}
          {goalW ? (
            <div style={{ marginBottom:14 }}>
              <div style={{ display:"flex", justifyContent:"space-between", alignItems:"baseline", fontSize:12.5, marginBottom:6 }}>
                <span style={{ fontWeight:800, color: reached?"var(--acc2)":(lost>=0?"var(--acc2)":"var(--danger)") }}>
                  {reached ? "🎉 הגעת ליעד!" : (lost>0 ? "ירדת "+Math.abs(lost)+" ק״ג 🔥" : lost<0 ? "עלית "+Math.abs(lost)+" ק״ג" : "טרם נרשם שינוי")}
                </span>
                {!reached && remaining!=null && <span style={{ color:"var(--tx2)" }}>עוד <b className="num" style={{ color:"var(--tx)" }}>{Math.abs(remaining)}</b> ק״ג ליעד</span>}
              </div>
              <Bar value={progressPct} max={100} height={10} color="var(--grad)"/>
              <div style={{ textAlign:"center", fontSize:11, color:"var(--tx3)", marginTop:5 }}>{Math.round(progressPct)}% מהדרך ליעד</div>
            </div>
          ) : <div style={{ fontSize:12, color:"var(--tx3)", textAlign:"center", marginBottom:12 }}>הגדר משקל יעד בפרופיל כדי לראות התקדמות ליעד</div>}
        </>)}

        {/* trend chart */}
        {R && wpoints.length>=1 ? (
          <R.ResponsiveContainer width="100%" height={185}>
            <R.AreaChart data={wpoints} margin={{ top:16, right:12, left:-14, bottom:0 }}>
              <defs><linearGradient id="wg" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stopColor="var(--acc)" stopOpacity=".45"/><stop offset="100%" stopColor="var(--acc)" stopOpacity="0"/></linearGradient></defs>
              <R.CartesianGrid strokeDasharray="3 3" stroke="var(--line)" vertical={false}/>
              <R.XAxis dataKey="d" tickFormatter={fmtTick} tick={{ fontSize:10, fill:"var(--tx2)" }} tickLine={false} axisLine={{ stroke:"var(--line2)" }}/>
              <R.YAxis domain={yDomain} width={32} tick={{ fontSize:10, fill:"var(--tx2)" }} tickLine={false} axisLine={false}/>
              <R.Tooltip contentStyle={{ background:"var(--card)", border:"1px solid var(--line2)", borderRadius:10, fontSize:12 }}
                formatter={(v)=>[v+" ק״ג","משקל"]} labelFormatter={fmtTick}/>
              {goalW && <R.ReferenceLine y={goalW} stroke="var(--acc2)" strokeDasharray="5 4" strokeWidth={1.5}
                label={{ value:"יעד "+goalW, position:"insideTopRight", fill:"var(--acc2)", fontSize:10, fontWeight:800 }}/>}
              <R.Area type="monotone" dataKey="w" stroke="var(--acc)" strokeWidth={2.5} fill="url(#wg)" dot={{ r:3, fill:"var(--acc)", strokeWidth:0 }} activeDot={{ r:5 }}/>
              {lastPt && <R.ReferenceDot x={lastPt.d} y={lastPt.w} r={5} fill="var(--acc)" stroke="var(--bg)" strokeWidth={2}
                label={{ value:lastPt.w+" ק״ג", position:"top", fill:"var(--tx)", fontSize:11, fontWeight:800 }}/>}
            </R.AreaChart>
          </R.ResponsiveContainer>
        ) : <div style={{ textAlign:"center", padding:"26px 0", color:"var(--tx2)", fontSize:13 }}>רשום משקל ראשון כדי להתחיל מעקב 📉</div>}

        <Btn variant="ghost" onClick={()=>setWOpen(true)} style={{ width:"100%", marginTop:10 }}>+ רשום משקל</Btn>
      </Card>

      {weekly && <Card className="fu fu2" style={{ marginBottom:14 }}>
        <div style={{ fontWeight:800, marginBottom:10 }}>📅 הסיכום השבועי</div>
        <div style={{ display:"flex", justifyContent:"space-between", fontSize:12.5, lineHeight:1.8, marginBottom:weekly.daily?.length?12:0, flexWrap:"wrap", gap:8 }}>
          <span>🔥 ממוצע: <b className="num">{fmt(weekly.avgCal)}</b> קק״ל/יום</span>
          <span>📝 <b className="num">{weekly.daysLogged}</b>/7 ימים נרשמו</span>
          <span>🏋️ <b className="num">{weekly.workouts7}</b> אימונים</span>
          {weekly.weightChange!=null && <span style={{ color:weekly.weightChange<=0?"var(--acc2)":"var(--warn)" }}>⚖️ {weekly.weightChange>0?"+":""}{weekly.weightChange} ק״ג</span>}
        </div>
        {R && (weekly.daily||[]).length>1 && <R.ResponsiveContainer width="100%" height={130}>
          <R.BarChart data={weekly.daily.map(x=>({ d:String(x.d).slice(5).replace("-","."), cal:x.cal }))} margin={{ top:6, right:6, left:-16, bottom:0 }}>
            <R.CartesianGrid strokeDasharray="3 3" stroke="var(--line)" vertical={false}/>
            <R.XAxis dataKey="d" tick={{ fontSize:9, fill:"var(--tx3)" }} tickLine={false} axisLine={false}/>
            <R.YAxis tick={{ fontSize:9, fill:"var(--tx3)" }} width={34} tickLine={false} axisLine={false}/>
            <R.Tooltip contentStyle={{ background:"var(--card)", border:"1px solid var(--line2)", borderRadius:10, fontSize:12 }} formatter={(v)=>[fmt(v)+" קק״ל","נצרך"]}/>
            {plan?.goalCals && <R.ReferenceLine y={plan.goalCals} stroke="var(--acc2)" strokeDasharray="5 4" label={{ value:"יעד", position:"insideTopRight", fill:"var(--acc2)", fontSize:9 }}/>}
            <R.Bar dataKey="cal" fill="var(--acc)" radius={[4,4,0,0]}/>
          </R.BarChart>
        </R.ResponsiveContainer>}
      </Card>}

      <ProgressPhotos notify={notify}/>
      <JournalCard notify={notify}/>

      {achv && <Card className="fu fu2" style={{ marginBottom:14 }}>
        <div style={{ display:"flex", alignItems:"baseline", justifyContent:"space-between", marginBottom:12 }}>
          <div style={{ fontWeight:800 }}>🏅 הישגים</div>
          <div className="num" style={{ fontSize:12, color:"var(--acc2)" }}>{achv.unlocked}/{achv.total}</div>
        </div>
        <div style={{ display:"grid", gridTemplateColumns:"1fr 1fr 1fr", gap:9 }}>
          {achv.achievements.map(a=>(
            <div key={a.id} style={{ textAlign:"center", padding:"12px 6px", borderRadius:14, background:a.done?"var(--accDim)":"var(--card2)", border:"1px solid "+(a.done?"var(--acc)":"var(--line)"), opacity:a.done?1:0.6 }}>
              <div style={{ fontSize:26, filter:a.done?"none":"grayscale(1)" }}>{a.emo}</div>
              <div style={{ fontSize:11.5, fontWeight:800, marginTop:4, lineHeight:1.2 }}>{a.title}</div>
              {a.done ? <div style={{ fontSize:10, color:"var(--acc2)", marginTop:3 }}>✓ הושג</div>
                      : <div style={{ fontSize:10, color:"var(--tx3)", marginTop:3 }} className="num">{Math.floor(a.prog)}/{a.goal}</div>}
            </div>
          ))}
        </div>
      </Card>}

      <Card className="fu fu3">
        <div style={{ fontWeight:800, marginBottom:10 }}>🏆 אימונים אחרונים</div>
        {logs.length===0 ? <div style={{ color:"var(--tx2)", fontSize:13, textAlign:"center", padding:14 }}>עוד לא ביצעת אימונים</div> :
          logs.slice(0,8).map(l=>(
            <div key={l.id} style={{ display:"flex", justifyContent:"space-between", alignItems:"center", padding:"9px 0", borderTop:"1px solid var(--line)" }}>
              <div><div style={{ fontWeight:700, fontSize:14 }}>{l.title}</div><div style={{ fontSize:11, color:"var(--tx2)" }}>{(l.created_at||"").slice(0,10)}</div></div>
              <div style={{ textAlign:"left" }}><div className="num" style={{ fontWeight:800, color:"var(--acc2)" }}>{fmt(l.calories)} קק״ל</div><div className="num" style={{ fontSize:11, color:"var(--tx2)" }}>{Math.round((l.duration_sec||0)/60)} דק׳</div></div>
            </div>
          ))}
      </Card>

      <Sheet open={wOpen} onClose={()=>setWOpen(false)} title="⚖️ רישום משקל">
        <div style={{ fontSize:12, fontWeight:700, color:"var(--tx2)", marginBottom:6 }}>משקל נוכחי (ק״ג)</div>
        <input className="inp num" type="number" inputMode="decimal" step="0.1" value={wVal} onChange={e=>setWVal(e.target.value)}
          onKeyDown={e=>e.key==="Enter"&&saveWeight()} autoFocus style={{ fontSize:24, textAlign:"center", marginBottom:10 }}/>
        <div style={{ display:"flex", gap:6, marginBottom:14 }}>
          {[-1,-0.5,+0.5,+1].map(d=>(
            <button key={d} onClick={()=>setWVal(String(Math.round(((+wVal||0)+d)*10)/10))} className="chip" style={{ flex:1, justifyContent:"center" }}>{d>0?"+":""}{d}</button>
          ))}
        </div>
        <div style={{ fontSize:12, color:"var(--tx3)", textAlign:"center", marginBottom:14 }}>נשמר ביומן המשקל ומעדכן את המשקל הנוכחי בפרופיל</div>
        <Btn onClick={saveWeight} disabled={wBusy} style={{ width:"100%" }}>{wBusy ? <Spinner size={18}/> : "שמור משקל"}</Btn>

        {!confirmReset ? (
          <button onClick={()=>setConfirmReset(true)} style={{ width:"100%", marginTop:12, color:"var(--tx3)", fontSize:13, fontWeight:600, padding:8, background:"transparent" }}>↺ אפס היסטוריית משקל</button>
        ) : (
          <div style={{ marginTop:12, padding:14, borderRadius:14, border:"1px solid var(--danger)", background:"color-mix(in srgb, var(--danger) 8%, transparent)" }}>
            <div style={{ fontSize:13.5, fontWeight:800, marginBottom:4 }}>למחוק את כל היסטוריית המשקל?</div>
            <div style={{ fontSize:12, color:"var(--tx2)", marginBottom:12, lineHeight:1.5 }}>המעקב יתחיל מחדש מ-<b className="num" style={{ color:"var(--tx)" }}>{(+wVal||curW||"—")}</b> ק״ג כנקודת התחלה. לא ניתן לשחזר את הרשומות הקודמות.</div>
            <div style={{ display:"flex", gap:8 }}>
              <Btn variant="ghost" onClick={()=>setConfirmReset(false)} style={{ flex:1 }}>ביטול</Btn>
              <Btn onClick={doReset} disabled={wBusy} style={{ flex:1, background:"var(--danger)", color:"#fff", boxShadow:"none" }}>{wBusy ? <Spinner size={16}/> : "כן, אפס"}</Btn>
            </div>
          </div>
        )}
      </Sheet>
    </div>
  );
};

/* ════════ PROFILE / SETTINGS ════════ */
NX.Profile = ({ open, user, plan, onClose, onSaved, onLogout, theme, toggleTheme, notify, onAdmin }) => {
  const [f, setF] = useState(user||{});
  const [busy, setBusy] = useState(false);
  const [pw, setPw] = useState({ cur:"", next:"", confirm:"" });
  const [pwBusy, setPwBusy] = useState(false);
  useEffect(()=>{ if(open){ setF(user||{}); setPw({ cur:"", next:"", confirm:"" }); } }, [open, user]);
  const changePw = async () => {
    if(!pw.cur || !pw.next) return notify("מלא סיסמה נוכחית וחדשה","bad");
    if(pw.next.length < 6) return notify("הסיסמה החדשה חייבת לפחות 6 תווים","bad");
    if(pw.next !== pw.confirm) return notify("הסיסמאות החדשות לא תואמות","bad");
    setPwBusy(true);
    try { await NX.api.changePassword(pw.cur, pw.next); notify("הסיסמה שונתה ✓","ok"); setPw({ cur:"", next:"", confirm:"" }); }
    catch(e){ notify(e.message,"bad"); }
    finally { setPwBusy(false); }
  };
  const set = (k,v)=>setF(p=>({ ...p, [k]:v }));
  const save = async () => {
    setBusy(true);
    try {
      await NX.api.updateProfile({ name:f.name, age:+f.age, gender:f.gender, height_cm:+f.height_cm, weight_kg:+f.weight_kg, target_weight_kg:+f.target_weight_kg, activity_level:f.activity_level, workouts_per_week:+f.workouts_per_week });
      const me = await NX.api.me(); const u = me.user||me;
      onSaved(u); notify("הפרופיל עודכן ✓","ok"); onClose();
    } catch(e){ notify(e.message,"bad"); } setBusy(false);
  };
  const F = (k) => ({ value:f[k], onChange:e=>set(k,e.target.value) }); // helper: spreads value+onChange
  const [pushOn, setPushOn] = useState(false);
  const [pushBusy, setPushBusy] = useState(false);
  useEffect(()=>{ if(open) NX.api.pushStatus().then(s=>setPushOn(!!s.subscribed)).catch(()=>{}); }, [open]);
  const togglePush = async () => {
    setPushBusy(true);
    try {
      if (pushOn) {
        try { const reg = await navigator.serviceWorker.ready; const sub = await reg.pushManager.getSubscription(); if (sub) { await sub.unsubscribe().catch(()=>{}); await NX.api.pushUnsubscribe(sub.endpoint); } else await NX.api.pushUnsubscribe(null); } catch(e){ await NX.api.pushUnsubscribe(null).catch(()=>{}); }
        setPushOn(false); notify("התראות כובו","info");
      } else { await NX.enablePush(); setPushOn(true); notify("התראות הופעלו! 🔔","ok"); }
    } catch(e){ notify(e.message,"bad"); } finally { setPushBusy(false); }
  };
  const testPush = async () => { try { const r = await NX.api.pushTest(); notify(r.sent>0?"נשלחה התראת בדיקה 🔔":"אין מכשירים רשומים — הפעל התראות","info"); } catch(e){ notify(e.message,"bad"); } };
  if(!open) return null;
  return (
    <Sheet open={open} onClose={onClose} title="פרופיל והגדרות">
      <div style={{ marginBottom:12 }}><div style={{ fontSize:12, fontWeight:700, color:"var(--tx2)", marginBottom:6 }}>שם</div><input className="inp" value={f.name||""} onChange={e=>set("name",e.target.value)}/></div>
      <div style={{ display:"flex", gap:10, marginBottom:12 }}><NumField label="גיל" {...F("age")}/><div style={{ flex:1 }}><div style={{ fontSize:12, fontWeight:700, color:"var(--tx2)", marginBottom:6 }}>מין</div><div style={{ display:"flex", gap:6 }}>{[["male","גבר"],["female","אישה"]].map(([v,l])=><button key={v} onClick={()=>set("gender",v)} className={cls("chip",f.gender===v&&"on")} style={{flex:1}}>{l}</button>)}</div></div></div>
      <div style={{ display:"flex", gap:10, marginBottom:12 }}><NumField label="גובה (ס״מ)" {...F("height_cm")}/><NumField label="משקל נוכחי" {...F("weight_kg")}/></div>
      <div style={{ display:"flex", gap:10, marginBottom:12 }}><NumField label="משקל יעד" {...F("target_weight_kg")}/><NumField label="אימונים/שבוע" {...F("workouts_per_week")}/></div>
      <div style={{ marginBottom:14 }}><div style={{ fontSize:12, fontWeight:700, color:"var(--tx2)", marginBottom:6 }}>רמת פעילות</div>
        <div style={{ display:"flex", gap:6 }}>{[["low","מעט"],["intermediate","בינוני"],["high","גבוה"]].map(([v,l])=><button key={v} onClick={()=>set("activity_level",v)} className={cls("chip",f.activity_level===v&&"on")} style={{flex:1}}>{l}</button>)}</div></div>

      {plan && <Card style={{ background:"var(--card2)", marginBottom:14 }}>
        <div style={{ fontSize:13, fontWeight:800, marginBottom:8 }}>📊 היעדים שלך (מחושב)</div>
        <Row k="BMR" v={fmt(plan.bmr)+" קק״ל"}/><Row k="TDEE (תחזוקה)" v={fmt(plan.tdee)+" קק״ל"}/>
        <Row k="יעד יומי (גירעון)" v={fmt(plan.goalCals)+" קק״ל"} c="var(--acc2)"/><Row k="חלבון" v={fmt(plan.protein)+" ג׳"}/><Row k="מים" v={fmt(plan.water)+" מ״ל"}/><Row k="BMI" v={plan.bmi}/>
      </Card>}

      <Card style={{ background:"var(--card2)", marginBottom:14 }}>
        <div style={{ display:"flex", alignItems:"center", justifyContent:"space-between" }}>
          <div style={{ display:"flex", alignItems:"center", gap:8 }}>
            <span style={{ fontSize:18 }}>🔔</span>
            <div><div style={{ fontSize:13, fontWeight:700 }}>תזכורות חכמות</div><div style={{ fontSize:11, color:pushOn?"var(--acc2)":"var(--tx3)" }}>{pushOn?"פעיל במכשיר זה":"כבוי"}</div></div>
          </div>
          {pushBusy ? <Spinner size={18}/> : <NX.Tog on={pushOn} set={togglePush} label="התראות"/>}
        </div>
        {pushOn && <button onClick={testPush} style={{ width:"100%", marginTop:10, padding:9, borderRadius:12, border:"1px dashed var(--line2)", background:"transparent", color:"var(--tx2)", fontSize:12.5, fontWeight:700 }}>שלח התראת בדיקה</button>}
      </Card>

      <Card style={{ background:"var(--card2)", marginBottom:14 }}>
        <div style={{ fontSize:13, fontWeight:800, marginBottom:10 }}>🔒 שינוי סיסמה</div>
        <input className="inp" type="password" autoComplete="current-password" placeholder="סיסמה נוכחית" value={pw.cur} onChange={e=>setPw(p=>({ ...p, cur:e.target.value }))} style={{ marginBottom:8 }}/>
        <input className="inp" type="password" autoComplete="new-password" placeholder="סיסמה חדשה (לפחות 6 תווים)" value={pw.next} onChange={e=>setPw(p=>({ ...p, next:e.target.value }))} style={{ marginBottom:8 }}/>
        <input className="inp" type="password" autoComplete="new-password" placeholder="אישור סיסמה חדשה" value={pw.confirm} onChange={e=>setPw(p=>({ ...p, confirm:e.target.value }))} style={{ marginBottom:10 }}/>
        <Btn variant="ghost" onClick={changePw} disabled={pwBusy} style={{ width:"100%" }}>{pwBusy?<Spinner size={18}/>:"עדכן סיסמה"}</Btn>
      </Card>

      <Btn onClick={save} disabled={busy} style={{ width:"100%", marginBottom:10 }}>{busy?<Spinner size={18}/>:"שמור שינויים"}</Btn>
      {user?.role==="admin" && <Btn variant="ghost" onClick={onAdmin} style={{ width:"100%", marginBottom:10, color:"var(--warn)", borderColor:"rgba(245,158,11,.3)" }}>🛡️ ניהול משתמשים</Btn>}
      <div style={{ display:"flex", gap:10 }}>
        <Btn variant="ghost" onClick={toggleTheme} style={{ flex:1 }}>{theme==="dark"?"☀️ מצב יום":"🌙 מצב לילה"}</Btn>
        <Btn variant="ghost" onClick={onLogout} style={{ flex:1, color:"var(--danger)" }}>יציאה ⎋</Btn>
      </div>
    </Sheet>
  );
};

/* ════════ ADMIN — user management (in-shell page) ════════ */
NX.Admin = ({ onClose, currentUser, notify }) => {
  const [users, setUsers] = useState(null);
  const [busy, setBusy] = useState(null);
  const [q, setQ] = useState("");
  const load = () => NX.api.adminUsers().then(d=>setUsers(d.users||[])).catch(e=>notify(e.message,"bad"));
  useEffect(()=>{ load(); }, []);

  const hasAI = (u)=> u.role==="admin" || (Array.isArray(u.perms)&&u.perms.includes("ai_agent"));
  const toggleAI = async (u) => {
    if (u.role==="admin") return notify("למנהל יש תמיד גישת AI","info");
    const perms = Array.isArray(u.perms)?[...u.perms]:[];
    const on = perms.includes("ai_agent");
    const next = on ? perms.filter(p=>p!=="ai_agent") : [...perms,"ai_agent"];
    setBusy(u.id);
    try { await NX.api.adminSetPerms(u.id, next); notify((on?"🚫 AI בוטל ל-":"🤖 AI הופעל ל-")+u.name,"ok"); await load(); }
    catch(e){ notify(e.message,"bad"); } setBusy(null);
  };
  const hasScan = (u)=> u.role==="admin" || (Array.isArray(u.perms)&&u.perms.includes("food_scan"));
  const toggleScan = async (u) => {
    if (u.role==="admin") return notify("למנהל יש תמיד סריקת מזון","info");
    const perms = Array.isArray(u.perms)?[...u.perms]:[];
    const on = perms.includes("food_scan");
    const next = on ? perms.filter(p=>p!=="food_scan") : [...perms,"food_scan"];
    setBusy(u.id);
    try { await NX.api.adminSetPerms(u.id, next); notify((on?"🚫 סריקה בוטלה ל-":"📷 סריקה הופעלה ל-")+u.name,"ok"); await load(); }
    catch(e){ notify(e.message,"bad"); } setBusy(null);
  };
  const [scanLimit, setScanLimit] = useState("");
  const [budget, setBudget] = useState("");
  const [usage, setUsage] = useState(null);
  const [confirmReset, setConfirmReset] = useState(false);
  const [overview, setOverview] = useState(null);   // {loading, data} for the coach dashboard
  const loadUsage = ()=> NX.api.aiUsage().then(setUsage).catch(()=>{});
  const R = window.Recharts;
  const viewOverview = async (u)=>{
    setOverview({ loading:true, name:u.name });
    try { const d = await NX.api.adminUserOverview(u.id); setOverview({ data:d }); }
    catch(e){ notify(e.message,"bad"); setOverview(null); }
  };
  useEffect(()=>{ NX.api.raw("GET","/ai/admin/config").then(c=>{ setScanLimit(String(c.food_scan_daily_limit??"10")); setBudget(String(c.ai_monthly_budget??"0")); }).catch(()=>{}); }, []);
  useEffect(()=>{ loadUsage(); }, []);
  const saveScanLimit = async () => {
    try { await NX.api.raw("PUT","/ai/admin/config",{ food_scan_daily_limit:Math.max(0,parseInt(scanLimit)||0) }); notify("מגבלת סריקות יומית עודכנה ✓","ok"); }
    catch(e){ notify(e.message,"bad"); }
  };
  const saveBudget = async () => {
    try { await NX.api.raw("PUT","/ai/admin/config",{ ai_monthly_budget:Math.max(0,parseFloat(budget)||0) }); notify("תקציב חודשי עודכן ✓","ok"); loadUsage(); }
    catch(e){ notify(e.message,"bad"); }
  };
  const doResetUsage = async () => {
    try { await NX.api.resetAiUsage(); notify("מונה ההוצאות אופס ✓","ok"); setConfirmReset(false); loadUsage(); }
    catch(e){ notify(e.message,"bad"); }
  };
  const del = async (u) => {
    if(!window.confirm("למחוק לצמיתות את "+u.name+"?\nכל הנתונים שלו יימחקו.")) return;
    setBusy(u.id);
    try { await NX.api.adminDeleteUser(u.id); notify("🗑 "+u.name+" נמחק","ok"); await load(); }
    catch(e){ notify(e.message,"bad"); } setBusy(null);
  };
  const reset = async (u) => {
    if(!window.confirm("לאפס סיסמה ל-"+u.name+"?")) return;
    try { const r = await NX.api.adminResetUser(u.id); window.alert("סיסמה זמנית ל-"+u.name+":\n\n"+r.temp_password+"\n\nמסור לו והמלץ להחליף."); }
    catch(e){ notify(e.message,"bad"); }
  };

  const list = (users||[]).filter(u => !q || (u.name||"").includes(q) || (u.email||"").includes(q));
  const adminCount = (users||[]).filter(u=>u.role==="admin").length;
  const aiCount = (users||[]).filter(u=>hasAI(u)).length;

  return (
    <div style={{ padding:"4px 0 12px" }}>
      <div className="fu" style={{ display:"flex", alignItems:"center", gap:10, marginBottom:14 }}>
        <button onClick={onClose} aria-label="חזרה" style={{ width:34, height:34, borderRadius:12, background:"var(--card2)", border:"1px solid var(--line2)", fontSize:18 }}>→</button>
        <div style={{ flex:1 }}>
          <h1 style={{ fontSize:22, fontWeight:900 }}>🛡️ ניהול משתמשים</h1>
          <div style={{ fontSize:12, color:"var(--tx2)", marginTop:2 }}>{users?users.length:"…"} משתמשים · {adminCount} מנהלים · {aiCount} עם AI</div>
        </div>
      </div>
      <input className="inp" value={q} onChange={e=>setQ(e.target.value)} placeholder="🔎 חיפוש לפי שם/אימייל" style={{ marginBottom:14 }}/>
      {usage && <Card className="fu" glow style={{ marginBottom:14, padding:16 }}>
        <div style={{ display:"flex", alignItems:"baseline", justifyContent:"space-between", marginBottom:8 }}>
          <div style={{ fontSize:14, fontWeight:800 }}>💸 הוצאות AI (הערכה)</div>
          <div className="num" style={{ fontSize:11, color:"var(--tx3)" }}>היום ${(usage.today_usd||0).toFixed(3)} · החודש ${(usage.month_usd||0).toFixed(2)}</div>
        </div>
        <div style={{ textAlign:"center", marginBottom:12 }}>
          <span className="num grad-tx" style={{ fontSize:30, fontWeight:900 }}>${(usage.total_usd||0).toFixed(2)}</span>
          <div style={{ fontSize:12, color:"var(--tx2)" }}>≈ ₪{((usage.total_usd||0)*(usage.ils_rate||3.7)).toFixed(2)} סה״כ</div>
        </div>
        {usage.monthly_budget>0 && <div style={{ marginBottom:12, padding:"10px 12px", borderRadius:12, background: usage.month_usd>=usage.monthly_budget?"color-mix(in srgb,var(--danger) 12%,transparent)":"var(--card2)", border:"1px solid "+(usage.month_usd>=usage.monthly_budget?"var(--danger)":"var(--line)") }}>
          <div style={{ display:"flex", justifyContent:"space-between", alignItems:"baseline", fontSize:12, marginBottom:6 }}>
            <span style={{ fontWeight:800, color:usage.month_usd>=usage.monthly_budget?"var(--danger)":"var(--tx)" }}>{usage.month_usd>=usage.monthly_budget?"⚠️ חרגת מהתקציב החודשי":"תקציב חודשי"}</span>
            <span className="num" style={{ color:"var(--tx2)" }}>${(usage.month_usd||0).toFixed(3)} / ${usage.monthly_budget.toFixed(2)}</span>
          </div>
          <Bar value={usage.month_usd} max={usage.monthly_budget} color={usage.month_usd>=usage.monthly_budget?"var(--danger)":"var(--acc)"} height={8}/>
        </div>}
        <div style={{ display:"flex", flexDirection:"column" }}>
          {[["💬 צ׳אט מאמן","chat"],["📷 סריקות תווית","scan"],["🍽️ צילום מנות","meal"],["🔍 Embeddings","embedding"]].map(([label,k])=>(
            <div key={k} style={{ display:"flex", justifyContent:"space-between", alignItems:"center", fontSize:12.5, padding:"7px 0", borderTop:"1px solid var(--line)" }}>
              <span>{label} <span className="num" style={{ color:"var(--tx3)" }}>({usage.counts?.[k]||0})</span></span>
              <b className="num">${(usage.breakdown?.[k]||0).toFixed(3)}</b>
            </div>
          ))}
        </div>
        {R && usage.daily && usage.daily.length>0 && (
          <div style={{ marginTop:12 }}>
            <div style={{ fontSize:11, color:"var(--tx3)", marginBottom:4 }}>הוצאה יומית (נמדד)</div>
            <R.ResponsiveContainer width="100%" height={90}>
              <R.BarChart data={usage.daily} margin={{ top:4, right:4, left:-18, bottom:0 }}>
                <R.XAxis dataKey="d" tickFormatter={d=>{ const a=String(d).split("-"); return a.length===3?(+a[2]+"."+(+a[1])):d; }} tick={{ fontSize:9, fill:"var(--tx3)" }} tickLine={false} axisLine={false}/>
                <R.YAxis tick={{ fontSize:9, fill:"var(--tx3)" }} tickLine={false} axisLine={false} width={30} tickFormatter={v=>"$"+v}/>
                <R.Tooltip contentStyle={{ background:"var(--card)", border:"1px solid var(--line2)", borderRadius:8, fontSize:11 }} formatter={v=>["$"+(+v).toFixed(4),"הוצאה"]} labelFormatter={d=>d}/>
                <R.Bar dataKey="c" fill="var(--acc)" radius={[3,3,0,0]}/>
              </R.BarChart>
            </R.ResponsiveContainer>
          </div>
        )}
        <div style={{ fontSize:10.5, color:"var(--tx3)", marginTop:8, lineHeight:1.5 }}>
          נמדד מדויק מ-{(usage.since||"").slice(0,10)} ({usage.measured_calls||0} קריאות) · לפני כן הערכה לפי ממוצעים
        </div>
        <div style={{ marginTop:12, display:"flex", gap:8, alignItems:"center" }}>
          <span style={{ fontSize:12, color:"var(--tx2)", whiteSpace:"nowrap" }}>תקציב חודשי $</span>
          <input className="inp num" type="number" value={budget} onChange={e=>setBudget(e.target.value)} placeholder="0" style={{ flex:1, textAlign:"center", padding:"7px" }}/>
          <Btn onClick={saveBudget} style={{ padding:"0 14px", fontSize:12 }}>שמור</Btn>
        </div>
        {!confirmReset
          ? <button onClick={()=>setConfirmReset(true)} style={{ width:"100%", marginTop:8, color:"var(--tx3)", fontSize:12, fontWeight:600, padding:7, background:"transparent" }}>↺ אפס מונה</button>
          : <div style={{ marginTop:8, padding:12, borderRadius:12, border:"1px solid var(--danger)", background:"color-mix(in srgb,var(--danger) 8%,transparent)" }}>
              <div style={{ fontSize:12.5, fontWeight:700, marginBottom:8 }}>לאפס את מונה ההוצאות? (כולל ההערכה ההיסטורית)</div>
              <div style={{ display:"flex", gap:8 }}>
                <Btn variant="ghost" onClick={()=>setConfirmReset(false)} style={{ flex:1, fontSize:12 }}>ביטול</Btn>
                <Btn onClick={doResetUsage} style={{ flex:1, fontSize:12, background:"var(--danger)", color:"#fff", boxShadow:"none" }}>כן, אפס</Btn>
              </div>
            </div>}
      </Card>}
      <Card className="fu" style={{ marginBottom:14, padding:14 }}>
        <div style={{ fontSize:13, fontWeight:800, marginBottom:8 }}>📷 מגבלת סריקות תווית ליום (למשתמש)</div>
        <div style={{ display:"flex", gap:8 }}>
          <input className="inp num" type="number" value={scanLimit} onChange={e=>setScanLimit(e.target.value)} placeholder="10" style={{ flex:1, textAlign:"center" }}/>
          <Btn onClick={saveScanLimit} style={{ padding:"0 18px", fontSize:13 }}>שמור</Btn>
        </div>
        <div style={{ fontSize:11, color:"var(--tx3)", marginTop:6 }}>0 = ללא הגבלה · מנהלים תמיד ללא הגבלה</div>
      </Card>
      <div>
        {!users ? <div style={{ textAlign:"center", padding:40 }}><Spinner/></div> :
          list.map(u=>{
            const me = u.id===currentUser?.id;
            const ai = hasAI(u);
            return (
              <Card key={u.id} style={{ marginBottom:10, padding:14 }}>
                <div style={{ display:"flex", alignItems:"center", gap:12, marginBottom:12 }}>
                  <div style={{ width:42, height:42, borderRadius:"50%", flexShrink:0, background:"var(--grad)", color:"#04140d", fontWeight:900, fontSize:17, display:"flex", alignItems:"center", justifyContent:"center" }}>{(u.name||"?").charAt(0).toUpperCase()}</div>
                  <div style={{ flex:1, minWidth:0 }}>
                    <div style={{ fontWeight:800, fontSize:15, display:"flex", alignItems:"center", gap:6 }}>
                      {u.name} {me&&<span style={{ fontSize:10, color:"var(--tx3)" }}>(אתה)</span>}
                      {u.role==="admin"&&<span style={{ fontSize:9, fontWeight:800, color:"var(--warn)", background:"rgba(245,158,11,.12)", borderRadius:99, padding:"2px 7px" }}>🛡 מנהל</span>}
                    </div>
                    <div style={{ fontSize:12, color:"var(--tx2)", overflow:"hidden", textOverflow:"ellipsis", whiteSpace:"nowrap" }}>{u.email}</div>
                  </div>
                </div>
                {/* AI agent toggle */}
                <div style={{ display:"flex", alignItems:"center", justifyContent:"space-between", padding:"10px 12px", borderRadius:12, background:ai?"var(--accDim)":"var(--card2)", marginBottom:10 }}>
                  <div style={{ display:"flex", alignItems:"center", gap:8 }}>
                    <span style={{ fontSize:17 }}>🤖</span>
                    <div>
                      <div style={{ fontSize:13, fontWeight:700 }}>גישת מאמן AI</div>
                      <div style={{ fontSize:11, color:ai?"var(--acc2)":"var(--tx3)" }}>{u.role==="admin"?"תמיד פעיל (מנהל)":ai?"פעיל":"כבוי"}</div>
                    </div>
                  </div>
                  <NX.Tog on={ai} set={()=>!busy&&toggleAI(u)} label="גישת AI"/>
                </div>
                {/* food scan toggle */}
                <div style={{ display:"flex", alignItems:"center", justifyContent:"space-between", padding:"10px 12px", borderRadius:12, background:hasScan(u)?"var(--accDim)":"var(--card2)", marginBottom:10 }}>
                  <div style={{ display:"flex", alignItems:"center", gap:8 }}>
                    <span style={{ fontSize:17 }}>📷</span>
                    <div>
                      <div style={{ fontSize:13, fontWeight:700 }}>סריקת תווית תזונתית</div>
                      <div style={{ fontSize:11, color:hasScan(u)?"var(--acc2)":"var(--tx3)" }}>{u.role==="admin"?"תמיד פעיל (מנהל)":hasScan(u)?"פעיל":"כבוי"}</div>
                    </div>
                  </div>
                  <NX.Tog on={hasScan(u)} set={()=>!busy&&toggleScan(u)} label="סריקת מזון"/>
                </div>
                <div style={{ display:"flex", gap:8 }}>
                  <Btn variant="ghost" onClick={()=>viewOverview(u)} style={{ flex:1, padding:"9px", fontSize:12 }}>📊 התקדמות</Btn>
                  <Btn variant="ghost" onClick={()=>reset(u)} style={{ flex:1, padding:"9px", fontSize:12 }}>🔑 סיסמה</Btn>
                  {!me && <Btn variant="ghost" onClick={()=>del(u)} style={{ flex:1, padding:"9px", fontSize:12, color:"var(--danger)", borderColor:"rgba(239,68,68,.3)" }}>{busy===u.id?<Spinner size={14}/>:"🗑 מחק"}</Btn>}
                </div>
              </Card>
            );
          })}
      </div>

      <Sheet open={!!overview} onClose={()=>setOverview(null)} title={"📊 התקדמות — "+(overview?.name||overview?.data?.name||"")}>
        {overview?.loading ? <div style={{ textAlign:"center", padding:30 }}><Spinner/></div> : overview?.data && (()=>{
          const d = overview.data;
          const wp = (d.weight?.trend||[]).map(p=>({ d:String(p.date).slice(5), w:p.weight_kg })).filter(p=>p.w);
          return <>
            <div style={{ fontSize:12, fontWeight:800, marginBottom:8 }}>היום</div>
            <div style={{ display:"grid", gridTemplateColumns:"1fr 1fr 1fr", gap:8, marginBottom:14 }}>
              <Metric emo="🔥" val={d.today.calories} label={"מתוך "+d.goalCals}/>
              <Metric emo="💪" val={d.today.protein} label={"חלבון /"+d.proteinTarget}/>
              <Metric emo="💧" val={d.today.water} label="מ״ל מים"/>
            </div>
            <div style={{ fontSize:12, fontWeight:800, marginBottom:8 }}>משקל</div>
            <div style={{ display:"grid", gridTemplateColumns:"1fr 1fr 1fr", gap:8, marginBottom:10 }}>
              <Metric emo="⚖️" val={d.weight.current??"—"} label="נוכחי"/>
              <Metric emo="🎯" val={d.weight.target??"—"} label="יעד"/>
              <Metric emo="📉" val={d.weight.lost!=null?d.weight.lost:"—"} label="ירד (ק״ג)"/>
            </div>
            {R && wp.length>1 && <R.ResponsiveContainer width="100%" height={120}><R.AreaChart data={wp} margin={{top:6,right:6,left:-18,bottom:0}}>
              <defs><linearGradient id="cwg" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stopColor="var(--acc)" stopOpacity=".4"/><stop offset="100%" stopColor="var(--acc)" stopOpacity="0"/></linearGradient></defs>
              <R.XAxis dataKey="d" tick={{fontSize:9,fill:"var(--tx3)"}} tickLine={false} axisLine={false}/>
              <R.YAxis domain={["dataMin-1","dataMax+1"]} tick={{fontSize:9,fill:"var(--tx3)"}} width={28} tickLine={false} axisLine={false}/>
              <R.Area type="monotone" dataKey="w" stroke="var(--acc)" strokeWidth={2} fill="url(#cwg)"/>
            </R.AreaChart></R.ResponsiveContainer>}
            <div style={{ display:"flex", justifyContent:"space-between", marginTop:14, padding:"10px 12px", background:"var(--card2)", borderRadius:12, fontSize:13 }}>
              <span>🏋️ אימונים ב-7 ימים: <b className="num">{d.workouts7}</b></span>
              <span style={{ color:"var(--tx3)" }}>ארוחה אחרונה: {d.lastMeal||"—"}</span>
            </div>
          </>;
        })()}
      </Sheet>
    </div>
  );
};

/* ════════ AI COACH ════════ */
NX.AICoach = ({ open, onClose, user }) => {
  const [msgs, setMsgs] = useState([{ role:"assistant", content:"היי "+(user?.name||"")+"! 💪 אני המאמן האישי שלך — ואני מכיר את הנתונים שלך (קלוריות, משקל, אימונים). שאל אותי כל דבר על תזונה, אימונים או ירידה במשקל." }]);
  const [txt, setTxt] = useState("");
  const [busy, setBusy] = useState(false);
  const endRef = useRef();
  useEffect(()=>{ endRef.current?.scrollIntoView({ behavior:"smooth" }); }, [msgs, busy]);
  const send = async (forced) => {
    const m = (typeof forced === "string" ? forced : txt).trim();
    if(!m || busy) return;
    if (typeof forced !== "string") setTxt("");
    setMsgs(p=>[...p,{ role:"user", content:m }]); setBusy(true);
    try { const r = await NX.api.aiChat(m, msgs.slice(-6)); setMsgs(p=>[...p,{ role:"assistant", content:r.reply||r.message||r.content||"..." }]); }
    catch(e){ setMsgs(p=>[...p,{ role:"assistant", content:"⚠️ "+e.message }]); } setBusy(false);
  };
  const SUGGEST = ["איך אני היום? 📊","מה כדאי לאכול עכשיו? 🍽️","תכין לי אימון להיום 🏋️","איך מגמת המשקל שלי? ⚖️"];
  if(!open) return null;
  return (
    <div style={{ position:"fixed", inset:0, zIndex:400, background:"var(--bg)", display:"flex", flexDirection:"column", animation:"fadeIn .2s ease" }}>
      <div style={{ padding:"max(14px,env(safe-area-inset-top)) 18px 12px", display:"flex", alignItems:"center", gap:12, borderBottom:"1px solid var(--line)" }}>
        <div style={{ fontSize:26 }}>🤖</div>
        <div style={{ flex:1 }}><div style={{ fontWeight:800 }}>מאמן AI</div><div style={{ fontSize:11, color:"var(--acc2)" }}>● מחובר</div></div>
        <button onClick={onClose} style={{ width:34, height:34, borderRadius:"50%", background:"var(--card2)", fontSize:15 }}>✕</button>
      </div>
      <div style={{ flex:1, overflowY:"auto", padding:16, display:"flex", flexDirection:"column", gap:12 }}>
        {msgs.map((m,i)=>(
          <div key={i} style={{ alignSelf:m.role==="user"?"flex-start":"flex-end", maxWidth:"85%", padding:"11px 15px", borderRadius:16, fontSize:14, lineHeight:1.6,
            background:m.role==="user"?"var(--grad)":"var(--card)", color:m.role==="user"?"#04140d":"var(--tx)", border:m.role==="user"?"none":"1px solid var(--line)", whiteSpace:"pre-wrap" }}>{m.content}</div>
        ))}
        {busy && <div style={{ alignSelf:"flex-end", padding:"11px 15px" }}><Spinner size={18}/></div>}
        <div ref={endRef}/>
      </div>
      {msgs.length<3 && !busy && <div style={{ display:"flex", gap:7, overflowX:"auto", padding:"0 14px 4px", WebkitOverflowScrolling:"touch" }}>
        {SUGGEST.map(s=><button key={s} onClick={()=>send(s)} className="chip" style={{ whiteSpace:"nowrap", flexShrink:0 }}>{s}</button>)}
      </div>}
      <div style={{ padding:"10px 14px max(12px,env(safe-area-inset-bottom))", borderTop:"1px solid var(--line)", display:"flex", gap:8 }}>
        <input className="inp" value={txt} onChange={e=>setTxt(e.target.value)} onKeyDown={e=>e.key==="Enter"&&send()} placeholder="שאל את המאמן..."/>
        <Btn onClick={send} disabled={busy} style={{ padding:"0 18px" }}>שלח</Btn>
      </div>
    </div>
  );
};

})(window.NX);
/* NutriOS 6 — app shell */
(function(NX){
const { useState, useEffect, useCallback } = React;

const TABS = [
  { k:"home",      label:"בית",      icon:"M3 11l9-8 9 8M5 9v11h14V9" },
  { k:"train",     label:"אימון",    icon:"M4 7v10M8 5v14M16 5v14M20 7v10M8 12h8" },
  { k:"nutrition", label:"תזונה",    icon:"M7 3v18M7 9c3 0 4-2 4-6M17 3c0 5-2 7-2 10s2 4 2 8" },
  { k:"progress",  label:"התקדמות",  icon:"M4 19V5M4 19h16M8 16l3-4 3 2 4-6" },
];

function App() {
  const { theme, toggle, dark } = NX.useTheme();
  const { toast, notify } = NX.useToast();
  const [booting, setBooting] = useState(true);
  const [user, setUser] = useState(null);
  const [plan, setPlan] = useState(null);
  const [tab, setTab] = useState("home");
  const [exercises, setExercises] = useState([]);
  const [programs, setPrograms] = useState([]);
  const [myPrograms, setMyPrograms] = useState([]);
  const [challenges, setChallenges] = useState([]);
  const [exMap, setExMap] = useState({});
  const [player, setPlayer] = useState(null);   // { program, dayIndex }
  const [doneCard, setDoneCard] = useState(null);
  const [aiOpen, setAiOpen] = useState(false);
  const [profileOpen, setProfileOpen] = useState(false);
  const [adminOpen, setAdminOpen] = useState(false);

  const loadData = useCallback(async () => {
    try {
      const [ex, pr, mp, ch] = await Promise.all([NX.api.exercises(), NX.api.programs(), NX.api.myPrograms(), NX.api.challenges()]);
      setExercises(ex.exercises||[]);
      setPrograms(pr.programs||[]);
      setMyPrograms(mp.programs||[]);
      setChallenges(ch.challenges||[]);
      const map = {}; (ex.exercises||[]).forEach(e=>map[e.anim_key]=e); setExMap(map);
    } catch(e) { notify("שגיאת טעינת נתונים", "bad"); }
  }, [notify]);

  const reloadMy = useCallback(async () => {
    try { const d = await NX.api.myPrograms(); setMyPrograms(d.programs||[]); } catch(e){}
  }, []);
  const reloadChallenges = useCallback(async () => {
    try { const d = await NX.api.challenges(); setChallenges(d.challenges||[]); } catch(e){}
  }, []);

  const onAuth = useCallback(async () => {
    try {
      const me = await NX.api.me();
      const u = me.user || me;
      setUser(u);
      setPlan(NX.calcPlan(u));
      await loadData();
      setTab("home");
    } catch(e) { NX.setToken(null); setUser(null); }
  }, [loadData]);

  useEffect(() => {
    (async () => {
      if (NX.getToken()) { await onAuth(); }
      setBooting(false);
    })();
  }, []);

  const logout = () => { NX.setToken(null); setUser(null); setTab("home"); };
  const openPlayer = (program, dayIndex) => setPlayer({ program, dayIndex });
  const aiAllowed = user && (user.role === "admin" || (Array.isArray(user.perms) && user.perms.includes("ai_agent")));
  const scanAllowed = user && (user.role === "admin" || (Array.isArray(user.perms) && user.perms.includes("food_scan")));

  if (booting) return <div style={{ minHeight:"100svh", display:"flex", alignItems:"center", justifyContent:"center" }}><NX.Spinner size={30}/></div>;
  if (!user) return <><NX.Toast {...(toast||{})}/><NX.AuthScreen onAuth={onAuth} notify={notify}/></>;

  return (
    <div style={{ maxWidth:560, margin:"0 auto", minHeight:"calc(100svh + 80px)", paddingBottom:"calc(76px + env(safe-area-inset-bottom))" }}>
      <NX.Toast {...(toast||{})}/>

      {/* top bar */}
      <div style={{ position:"sticky", top:0, zIndex:50, background:"color-mix(in srgb, var(--bg) 88%, transparent)", backdropFilter:"blur(14px)", display:"flex", alignItems:"center", justifyContent:"space-between", padding:"max(10px,env(safe-area-inset-top)) 16px 10px", borderBottom:"1px solid var(--line)" }}>
        <div style={{ fontSize:20, fontWeight:900 }}><span className="grad-tx">NutriOS</span></div>
        <div style={{ display:"flex", gap:8, alignItems:"center" }}>
          {user.role==="admin" && <button onClick={()=>setAdminOpen(true)} className="chip" style={{ padding:"5px 12px", fontSize:11, cursor:"pointer", borderColor:"rgba(245,158,11,.4)", color:"var(--warn)" }}>🛡 ניהול</button>}
          <button onClick={toggle} style={{ width:36, height:36, borderRadius:"50%", background:"var(--card2)", border:"1px solid var(--line2)", fontSize:15 }}>{dark?"☀️":"🌙"}</button>
          <button onClick={()=>setProfileOpen(true)} title="פרופיל" style={{ width:36, height:36, borderRadius:"50%", background:"var(--grad)", color:"#04140d", fontWeight:900, fontSize:15, display:"flex", alignItems:"center", justifyContent:"center" }}>{(user.name||"?").charAt(0).toUpperCase()}</button>
        </div>
      </div>

      <div style={{ padding:"14px 16px 0" }}>
        {adminOpen && user.role==="admin" ? (
          <NX.Admin onClose={()=>setAdminOpen(false)} currentUser={user} notify={notify}/>
        ) : <>
          {tab==="home"      && <NX.Home user={user} plan={plan} notify={notify} go={setTab} openPlayer={openPlayer} programs={programs}/>}
          {tab==="train"     && <NX.Train exercises={exercises} programs={programs} myPrograms={myPrograms} reloadMy={reloadMy} challenges={challenges} reloadChallenges={reloadChallenges} exMap={exMap} openPlayer={openPlayer} notify={notify}/>}
          {tab==="nutrition" && <NX.Nutrition plan={plan} notify={notify} scanAllowed={scanAllowed} isAdmin={user.role==="admin"}/>}
          {tab==="progress"  && <NX.Progress user={user} plan={plan} notify={notify} onWeight={(u)=>{ setUser(u); setPlan(NX.calcPlan(u)); }}/>}
        </>}
      </div>

      {/* floating AI — only for users with AI agent access */}
      {aiAllowed && <button onClick={()=>setAiOpen(true)} style={{ position:"fixed", bottom:"calc(86px + env(safe-area-inset-bottom))", insetInlineEnd:16, zIndex:60, width:56, height:56, borderRadius:"50%", background:"var(--grad)", boxShadow:"0 8px 26px var(--accGlow)", fontSize:26, display:"flex", alignItems:"center", justifyContent:"center" }} className="glow-ring">🤖</button>}

      {/* bottom nav */}
      <div style={{ position:"fixed", bottom:0, left:0, right:0, zIndex:55, background:"var(--bg2)", borderTop:"1px solid var(--line)", display:"flex", maxWidth:560, margin:"0 auto", paddingBottom:"env(safe-area-inset-bottom)" }}>
        {TABS.map(t=>{
          const on = tab===t.k;
          return <button key={t.k} onClick={()=>{ setAdminOpen(false); setTab(t.k); }} style={{ flex:1, display:"flex", flexDirection:"column", alignItems:"center", gap:3, padding:"11px 0 9px", position:"relative" }}>
            {on && <div style={{ position:"absolute", top:0, width:30, height:3, borderRadius:3, background:"var(--grad)" }}/>}
            <svg width="23" height="23" viewBox="0 0 24 24" fill="none" stroke={on?"var(--acc)":"var(--tx3)"} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d={t.icon}/></svg>
            <span style={{ fontSize:10.5, fontWeight:on?800:600, color:on?"var(--acc2)":"var(--tx3)" }}>{t.label}</span>
          </button>;
        })}
      </div>

      {/* workout player overlay */}
      {player && <NX.WorkoutPlayer program={player.program} dayIndex={player.dayIndex} exMap={exMap} user={user}
        onClose={()=>setPlayer(null)}
        onDone={(r)=>{ setPlayer(null); setDoneCard(r); reloadChallenges(); }} notify={notify}/>}

      {/* done card */}
      {doneCard && <div onClick={()=>setDoneCard(null)} style={{ position:"fixed", inset:0, zIndex:520, background:"rgba(0,0,0,.7)", backdropFilter:"blur(8px)", display:"flex", alignItems:"center", justifyContent:"center", padding:24, animation:"fadeIn .25s" }}>
        <NX.Card glow style={{ textAlign:"center", maxWidth:340, padding:30, animation:"pop .4s both" }}>
          <div style={{ fontSize:56 }}>🏆</div>
          <div style={{ fontSize:24, fontWeight:900, marginTop:8 }}>אימון הושלם!</div>
          <div style={{ display:"flex", gap:24, justifyContent:"center", margin:"18px 0" }}>
            <div><div className="num grad-tx" style={{ fontSize:28, fontWeight:900 }}>{NX.fmt(doneCard.cal)}</div><div style={{ fontSize:12, color:"var(--tx2)" }}>קק״ל</div></div>
            <div><div className="num" style={{ fontSize:28, fontWeight:900 }}>{Math.round(doneCard.dur/60)}</div><div style={{ fontSize:12, color:"var(--tx2)" }}>דקות</div></div>
          </div>
          <NX.Btn onClick={()=>{ setDoneCard(null); setTab("progress"); }} style={{ width:"100%" }}>ראה התקדמות 📈</NX.Btn>
        </NX.Card>
      </div>}

      {/* AI coach */}
      <NX.AICoach open={aiOpen} onClose={()=>setAiOpen(false)} user={user}/>

      {/* profile / settings */}
      <NX.Profile open={profileOpen} user={user} plan={plan} theme={theme} toggleTheme={toggle}
        onClose={()=>setProfileOpen(false)} onLogout={logout}
        onAdmin={()=>{ setProfileOpen(false); setAdminOpen(true); }}
        onSaved={(u)=>{ setUser(u); setPlan(NX.calcPlan(u)); }} notify={notify}/>

    </div>
  );
}

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