// fc-chart.jsx, weight-trajectory instrument, driven by the active pace state.
// Daily dots (raw) · EWMA trend (primary) · dashed projection to weigh-in ·
// dashed goal line. Y-domain is DYNAMIC: it always frames every drawn value plus
// the goal, so a fast cut or a stall can never push the projection off the card.
// All numerics kg, tnum.

function catmullRom(pts, tension = 0.5) {
  if (pts.length < 2) return '';
  const k = tension;
  let d = `M ${pts[0][0].toFixed(2)} ${pts[0][1].toFixed(2)}`;
  for (let i = 0; i < pts.length - 1; i++) {
    const p0 = pts[Math.max(0, i - 1)], p1 = pts[i], p2 = pts[i + 1], p3 = pts[Math.min(pts.length - 1, i + 2)];
    const c1x = p1[0] + (p2[0] - p0[0]) * k / 3, c1y = p1[1] + (p2[1] - p0[1]) * k / 3;
    const c2x = p2[0] - (p3[0] - p1[0]) * k / 3, c2y = p2[1] - (p3[1] - p1[1]) * k / 3;
    d += ` C ${c1x.toFixed(2)} ${c1y.toFixed(2)}, ${c2x.toFixed(2)} ${c2y.toFixed(2)}, ${p2[0].toFixed(2)} ${p2[1].toFixed(2)}`;
  }
  return d;
}

const _MO = ['JAN','FEB','MAR','APR','MAY','JUN','JUL','AUG','SEP','OCT','NOV','DEC'];
// Day 0 = camp start, anchored so day campDays lands on weigh-in (day before the
// Jan 1 fight = Dec 31), keeping Start / Today / Weigh-in dates coherent.
const _CAMP_START = new Date(2025, 10, 11);  // 11 Nov 2025 → +50d = 31 Dec (weigh-in)
const _dayDate = (d) => { const dt = new Date(_CAMP_START); dt.setDate(dt.getDate() + d); return dt; };
const _fmtDay = (dt) => `${_MO[dt.getMonth()]} ${dt.getDate()}`;

// Round, evenly-spaced kg gridlines covering [min,max], aim for ~4 lines.
function _niceTicks(min, max, target = 5) {
  const span = Math.max(0.001, max - min);
  const steps = [0.5, 1, 2, 5, 10];
  const step = steps.find(s => s >= span / target) || 10;
  const out = [];
  for (let v = Math.ceil(min / step) * step; v <= max + 1e-9; v += step) out.push(+v.toFixed(1));
  return out;
}

function FCChart({ paceKey = 'onTrack', width = 345, height = 250, compact = false, fillMode = 'gradient', showPlan = false, showBand = false, rangeStartDay = 0 }) {
  const p = window.computePace(paceKey);
  const FC = window.FC;
  const planStart = FC.planStart != null ? FC.planStart : 78.4;
  const bandKg = 0.7;
  const xMinDay = Math.max(0, Math.min(rangeStartDay || 0, FC.todayDay - 5));
  const planAt = (day) => planStart + (FC.goalWeight - planStart) * (day / FC.campDays);
  const W = width, H = height;
  const padL = 30, padR = 52, padT = 16, padB = compact ? 26 : 42;
  const innerW = W - padL - padR, innerH = H - padT - padB;

  // ── dynamic y-domain ──────────────────────────────────────────────────────
  // Frame everything that gets drawn, raw dots, trend, today, projection, goal,
  // then add breathing room. Guarantees the projection endpoint stays on-card.
  const visHist = p.hist.filter(h => h[0] >= xMinDay);
  const visTrend = p.trend.filter(t => t[0] >= xMinDay);
  const ys = [...visHist.map(h => h[1]), ...visTrend.map(t => t[1]), p.projected, p.trendingToday, FC.goalWeight];
  if (showPlan || showBand) ys.push(planAt(xMinDay));
  if (showBand) ys.push(planAt(xMinDay) + bandKg, FC.goalWeight - bandKg);
  const dMin = Math.min(...ys), dMax = Math.max(...ys);
  const padW = Math.max(0.5, (dMax - dMin) * 0.16);
  // More breathing room below the lowest values (goal / projected) so they sit a
  // touch higher in the frame; trim the top pad to match.
  const yMin = dMin - padW * 1.55, yMax = dMax + padW * 0.85;
  const yTicks = _niceTicks(yMin, yMax);

  const xS = (d) => padL + ((d - xMinDay) / (FC.campDays - xMinDay)) * innerW;
  const yS = (w) => padT + ((yMax - w) / (yMax - yMin)) * innerH;
  const baselineY = H - padB;   // == yS(yMin)

  const trendPts = visTrend.map(q => [xS(q[0]), yS(q[1])]);
  const trendPath = catmullRom(trendPts, 0.85);

  const projStartX = xS(FC.todayDay), projStartY = yS(p.trendingToday);
  const projEndX = xS(FC.campDays), projEndY = yS(p.projected);
  // projection bends at fight-week start: gentle (real-weight) to the knee, then a
  // steeper fight-week drop sized by the cut strategy (water plans drop hardest).
  const _fwDay = Math.max(FC.todayDay + 0.5, FC.campDays - 7);
  const _pType = window.planTypeFor ? window.planTypeFor((window.FC && window.FC.sport) || 'MMA', true) : 'camp';
  const _kick = _pType === 'walk' ? 0 : _pType === 'minimal' ? 0.18 : 0.42;
  const _kFrac = (_fwDay - FC.todayDay) / Math.max(1, FC.campDays - FC.todayDay);
  const _kneeW = p.trendingToday + (p.projected - p.trendingToday) * _kFrac * (1 - _kick);
  const kneeX = xS(_fwDay), kneeY = yS(_kneeW);
  const goalY = yS(FC.goalWeight);
  const planX0 = xS(xMinDay), planX1 = xS(FC.campDays), planY0 = yS(planAt(xMinDay));
  const offPace = p.tone === 'red';
  const projColor = offPace ? 'var(--red)' : p.tone === 'amber' ? 'var(--mustard-deep)' : 'var(--t-green)';

  // Right-margin readouts (PROJ / GOAL): dodge each other when close, clamp on-card.
  const clampY = (y) => Math.max(padT + 14, Math.min(H - 8, y));
  let projLabelY = projEndY, goalLabelY = goalY;
  if (Math.abs(projEndY - goalY) < 26) {
    const mid = (projEndY + goalY) / 2;
    if (projEndY <= goalY) { projLabelY = mid - 14; goalLabelY = mid + 14; }
    else { projLabelY = mid + 14; goalLabelY = mid - 14; }
  }
  projLabelY = clampY(projLabelY); goalLabelY = clampY(goalLabelY);

  let approxLen = 0;
  for (let i = 1; i < trendPts.length; i++) approxLen += Math.hypot(trendPts[i][0] - trendPts[i-1][0], trendPts[i][1] - trendPts[i-1][1]) * 1.05;
  const uid = React.useId().replace(/:/g, '');
  const labelX = W - padR + 4;

  return (
    <svg width={W} height={H} viewBox={`0 0 ${W} ${H}`} style={{ display: 'block', shapeRendering: 'geometricPrecision', overflow: 'visible' }} role="img"
      aria-label={`Weight trajectory, ${paceKey}. Trending ${p.trendingToday.toFixed(1)} kg, projected ${p.projected.toFixed(1)} kg at weigh-in versus ${FC.goalWeight} kg limit.`}>
      <defs>
        <linearGradient id={`fg-${uid}`} x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stopColor="var(--ink)" stopOpacity="0.14" />
          <stop offset="65%" stopColor="var(--ink)" stopOpacity="0.03" />
          <stop offset="100%" stopColor="var(--ink)" stopOpacity="0" />
        </linearGradient>
        <clipPath id={`pc-${uid}`}><rect x={padL} y={padT - 6} width={innerW} height={innerH + 6} /></clipPath>
        <style>{`
          @keyframes dr-${uid}{from{stroke-dashoffset:${approxLen.toFixed(0)}}to{stroke-dashoffset:0}}
          .tl-${uid}{stroke-dasharray:${approxLen.toFixed(0)};stroke-dashoffset:0;animation:dr-${uid} 1100ms cubic-bezier(.65,.05,.36,1) 120ms}
          @media (prefers-reduced-motion:reduce){.tl-${uid}{animation:none}}
        `}</style>
      </defs>

      {/* gridlines + kg scale */}
      {yTicks.map(w => (
        <g key={w}>
          <line x1={padL} x2={W - padR} y1={yS(w)} y2={yS(w)} stroke="var(--rule)" strokeWidth="0.5" opacity="0.7" />
          <text x={padL - 7} y={yS(w) + 3.2} textAnchor="end" fontFamily="var(--mono)" fontSize="9.5" fill="var(--ink-3)" fontWeight="500" style={{ fontFeatureSettings: '"tnum"' }}>{w}</text>
        </g>
      ))}
      <text x={padL - 7} y={padT - 4} textAnchor="end" fontFamily="var(--mono)" fontSize="8" fill="var(--ink-3)" letterSpacing="0.18em" fontWeight="600">KG</text>

      {/* baseline */}
      <line x1={padL} x2={W - padR} y1={baselineY} y2={baselineY} stroke="var(--ink)" strokeWidth="0.7" opacity="0.45" />

      {/* safe-pace corridor, the lane the plan wants you in */}
      {showBand && (
        <polygon points={`${planX0},${yS(planAt(xMinDay) + bandKg)} ${planX1},${yS(FC.goalWeight + bandKg)} ${planX1},${yS(FC.goalWeight - bandKg)} ${planX0},${yS(planAt(xMinDay) - bandKg)}`} fill="var(--t-green)" opacity="0.10" />
      )}
      {/* planned glide path, recessive reference */}
      {showPlan && (
        <g>
          <line x1={planX0} y1={planY0} x2={planX1} y2={yS(FC.goalWeight)} stroke="var(--ink-3)" strokeWidth="1.3" opacity="0.5" />
          <text x={planX0 + 3} y={planY0 - 5} fontFamily="var(--mono)" fontSize="7.5" fill="var(--ink-3)" letterSpacing="0.16em" fontWeight="600">PLAN</text>
        </g>
      )}

      {/* trend area */}
      {fillMode === 'gradient' && (
      <path d={`${trendPath} L ${trendPts[trendPts.length-1][0].toFixed(2)} ${baselineY.toFixed(2)} L ${trendPts[0][0].toFixed(2)} ${baselineY.toFixed(2)} Z`} fill={`url(#fg-${uid})`} clipPath={`url(#pc-${uid})`} />
      )}

      {/* goal line, black, the anchor */}
      <line x1={padL} x2={W - padR} y1={goalY} y2={goalY} stroke="var(--ink)" strokeWidth="1.1" strokeDasharray="5 3" opacity="0.8" />

      {/* today divider, faint anchor */}
      <line x1={projStartX} x2={projStartX} y1={padT} y2={baselineY} stroke="var(--ink-3)" strokeWidth="0.5" strokeDasharray="2 4" opacity="0.28" />

      {/* raw daily readings, recessive */}
      {visHist.map(([d, w]) => <circle key={d} cx={xS(d)} cy={yS(w)} r="1.9" fill="var(--ink)" opacity="0.32" />)}

      {/* trend (primary), status colour */}
      <path className={`tl-${uid}`} d={trendPath} stroke={projColor} strokeWidth="2.6" fill="none" strokeLinejoin="round" strokeLinecap="round" />

      {/* projection to weigh-in, same weight as trend, dashed, same status colour */}
      <path d={`M ${projStartX} ${projStartY} L ${kneeX} ${kneeY} L ${projEndX} ${projEndY}`} stroke={projColor} strokeWidth="2.6" strokeDasharray="4 4" fill="none" strokeLinecap="round" />

      {/* endpoint + today markers */}
      <circle cx={projEndX} cy={projEndY} r="4.5" fill="var(--paper)" stroke={projColor} strokeWidth="1.8" />
      <circle cx={projEndX} cy={projEndY} r="1.8" fill={projColor} />
      <circle cx={projStartX} cy={projStartY} r="4.5" fill={projColor} />

      {/* right-margin readouts */}
      <g style={{ fontFeatureSettings: '"tnum"' }}>
        <line x1={W - padR} x2={labelX - 3} y1={projEndY} y2={projLabelY} stroke={projColor} strokeWidth="0.6" opacity="0.4" />
        <text x={labelX} y={projLabelY - 4} fontFamily="var(--mono)" fontSize="8" fill={projColor} letterSpacing="0.16em" fontWeight="700">PROJ</text>
        <text x={labelX} y={projLabelY + 11} fontFamily="var(--num)" fontSize="16" fill={projColor} fontWeight="500" letterSpacing="-0.02em">{p.projected.toFixed(1)}</text>

        <line x1={W - padR} x2={labelX - 3} y1={goalY} y2={goalLabelY} stroke="var(--ink)" strokeWidth="0.6" opacity="0.4" />
        <text x={labelX} y={goalLabelY - 4} fontFamily="var(--mono)" fontSize="8" fill="var(--ink)" letterSpacing="0.16em" fontWeight="700">LIMIT</text>
        <text x={labelX} y={goalLabelY + 11} fontFamily="var(--num)" fontSize="16" fill="var(--ink)" fontWeight="500" letterSpacing="-0.02em">{FC.goalWeight.toFixed(1)}</text>
      </g>

      {/* date axis, weigh-in is the boldest black anchor; start & today matched */}
      {!compact && (
        <g style={{ fontFeatureSettings: '"tnum"' }}>
          <text x={xS(xMinDay)} y={baselineY + 15} textAnchor="start" fontFamily="var(--mono)" fontSize="8.5" fill="var(--ink-3)" fontWeight="500">{_fmtDay(_dayDate(xMinDay))}</text>
          <text x={xS(xMinDay)} y={baselineY + 27} textAnchor="start" fontFamily="var(--mono)" fontSize="7.5" fill="var(--ink-3)" letterSpacing="0.18em" fontWeight="500">{xMinDay === 0 ? 'START' : ''}</text>
          <text x={projStartX} y={baselineY + 15} textAnchor="middle" fontFamily="var(--mono)" fontSize="8.5" fill="var(--ink-3)" fontWeight="500">{_fmtDay(_dayDate(FC.todayDay))}</text>
          <text x={projStartX} y={baselineY + 27} textAnchor="middle" fontFamily="var(--mono)" fontSize="7.5" fill="var(--ink-3)" letterSpacing="0.18em" fontWeight="500">TODAY</text>
          <text x={projEndX} y={baselineY + 15} textAnchor="end" fontFamily="var(--mono)" fontSize="9" fill="var(--ink)" fontWeight="700">{_fmtDay(_dayDate(FC.campDays))}</text>
          <text x={projEndX} y={baselineY + 27} textAnchor="end" fontFamily="var(--mono)" fontSize="8" fill="var(--ink)" letterSpacing="0.18em" fontWeight="700">WEIGH-IN</text>
        </g>
      )}
    </svg>
  );
}

// ── FCHealthChart, Apple Health-style axis: y-labels on the RIGHT, faint
// horizontal gridlines + dotted vertical week separators, no heavy baseline,
// a "target zone" band (the plan corridor) with dashed bounds, open-circle
// projection endpoint, solid today dot + scrubber line.
function FCHealthChart({ paceKey = 'onTrack', width = 345, height = 236, rangeStartDay = 0, showZone = true, showDots = true, callout = 'pill', preview = null }) {
  const p = window.computePace(paceKey);
  const FC = window.FC;
  const planStart = FC.planStart != null ? FC.planStart : 78.4;
  const bandKg = 0.7;
  const xMinDay = Math.max(0, Math.min(rangeStartDay || 0, FC.todayDay - 5));
  const planAt = (d) => planStart + (FC.goalWeight - planStart) * (d / FC.campDays);
  const W = width, H = height, padL = 14, padR = 38, padT = 14, padB = 36;
  const innerW = W - padL - padR, innerH = H - padT - padB;

  const visHist = p.hist.filter(h => h[0] >= xMinDay);
  const visTrend = p.trend.filter(t => t[0] >= xMinDay);
  const ys = [...visHist.map(h => h[1]), ...visTrend.map(t => t[1]), p.projected, p.trendingToday, FC.goalWeight];
  if (showZone) ys.push(planAt(xMinDay) + bandKg, planAt(xMinDay) - 0.95, FC.goalWeight - 0.6);
  const dMin = Math.min(...ys), dMax = Math.max(...ys);
  const padW = Math.max(0.5, (dMax - dMin) * 0.16);
  const yMin = dMin - padW, yMax = dMax + padW;
  const yTicks = _niceTicks(yMin, yMax);

  const xS = (d) => padL + ((d - xMinDay) / (FC.campDays - xMinDay)) * innerW;
  const yS = (w) => padT + ((yMax - w) / (yMax - yMin)) * innerH;

  const trendPts = visTrend.map(q => [xS(q[0]), yS(q[1])]);
  const trendPath = catmullRom(trendPts, 0.85);
  const projStartX = xS(FC.todayDay), projStartY = yS(p.trendingToday);
  const projEndX = xS(FC.campDays), projEndY = yS(p.projected);
  // projection bends at fight-week start: gentle (real-weight) to the knee, then a
  // steeper fight-week drop sized by the cut strategy (water plans drop hardest).
  const _fwDay = Math.max(FC.todayDay + 0.5, FC.campDays - 7);
  const _pType = window.planTypeFor ? window.planTypeFor((window.FC && window.FC.sport) || 'MMA', true) : 'camp';
  const _kick = _pType === 'walk' ? 0 : _pType === 'minimal' ? 0.18 : 0.42;
  const _kFrac = (_fwDay - FC.todayDay) / Math.max(1, FC.campDays - FC.todayDay);
  const _kneeW = p.trendingToday + (p.projected - p.trendingToday) * _kFrac * (1 - _kick);
  const kneeX = xS(_fwDay), kneeY = yS(_kneeW);
  const goalY = yS(FC.goalWeight);
  const projColor = p.tone === 'red' ? 'var(--red)' : p.tone === 'amber' ? 'var(--mustard-deep)' : 'var(--t-green)';
  const [sel, setSel] = React.useState(null);  // tapped point: {x,y,val,label}
  // Safe lane is asymmetric: the UPPER edge closes to the goal at weigh-in (you
  // can't be over the limit), the LOWER edge stays a little open (being somewhat
  // under still makes weight).
  const t01 = (d) => d / FC.campDays;
  const upHalf = (d) => bandKg * (1 - t01(d));
  const loHalf = (d) => 0.9 * (1 - 0.4 * t01(d));
  const zTop = (d) => yS(planAt(d) + upHalf(d)), zBot = (d) => yS(planAt(d) - loHalf(d));
  const ptFor = (k) => k === 'today' ? { x: projStartX, y: projStartY, val: p.trendingToday, label: 'Today' }
    : k === 'proj' ? { x: projEndX, y: projEndY, val: p.projected, label: 'Weigh-in' }
    : (() => { const d = visHist[Math.floor(visHist.length / 2)]; return d ? { x: xS(d[0]), y: yS(d[1]), val: d[1], label: _fmtDay(_dayDate(d[0])) } : null; })();
  const eff = sel || (preview ? ptFor(preview) : null);

  const weeks = [];
  for (let d = Math.ceil(xMinDay / 7) * 7; d <= FC.campDays - 1; d += 7) weeks.push(d);
  const uid = React.useId().replace(/:/g, '');
  let approxLen = 0;
  for (let i = 1; i < trendPts.length; i++) approxLen += Math.hypot(trendPts[i][0] - trendPts[i-1][0], trendPts[i][1] - trendPts[i-1][1]) * 1.05;

  return (
    <svg width={W} height={H} viewBox={`0 0 ${W} ${H}`} style={{ display: 'block', shapeRendering: 'geometricPrecision', overflow: 'visible' }} role="img"
      aria-label={`Weight trend, Apple Health style. Projected ${p.projected.toFixed(1)} kg versus ${FC.goalWeight} kg limit.`}>
      <defs>
        <clipPath id={`hc-${uid}`}><rect x={padL} y={padT - 6} width={innerW} height={innerH + 12} /></clipPath>
        <style>{`@keyframes hdr-${uid}{from{stroke-dashoffset:${approxLen.toFixed(0)}}to{stroke-dashoffset:0}}.hl-${uid}{stroke-dasharray:${approxLen.toFixed(0)};animation:hdr-${uid} 1000ms cubic-bezier(.65,.05,.36,1) 100ms}@media (prefers-reduced-motion:reduce){.hl-${uid}{animation:none}}`}</style>
      </defs>

      {/* horizontal gridlines + right-side kg labels */}
      {yTicks.map(w => (
        <g key={w}>
          <line x1={padL} x2={W - padR} y1={yS(w)} y2={yS(w)} stroke="var(--rule)" strokeWidth="0.6" opacity="0.5" />
          <text x={W - padR + 7} y={yS(w) + 3.6} textAnchor="start" fontFamily="var(--sans)" fontSize="11" fill="var(--ink-3)" fontWeight="400" style={{ fontFeatureSettings: '"tnum"' }}>{w}</text>
        </g>
      ))}
      <text x={W - padR + 7} y={padT - 3} textAnchor="start" fontFamily="var(--sans)" fontSize="9" fill="transparent" fontWeight="500">kg</text>
      {/* dotted vertical week separators */}
      {weeks.map(d => <line key={d} x1={xS(d)} x2={xS(d)} y1={padT} y2={H - padB} stroke="var(--rule-2)" strokeWidth="0.6" strokeDasharray="1 4" opacity="0.6" />)}

      {/* target zone (plan corridor), soft band, no outline */}
      {showZone && (
        <polygon clipPath={`url(#hc-${uid})`} points={`${xS(xMinDay)},${zTop(xMinDay)} ${projEndX},${zTop(FC.campDays)} ${projEndX},${zBot(FC.campDays)} ${xS(xMinDay)},${zBot(xMinDay)}`} fill={projColor} opacity="0.10" />
      )}

      {/* goal line */}
      <line x1={padL} x2={W - padR} y1={goalY} y2={goalY} stroke="var(--ink)" strokeWidth="1" strokeDasharray="5 3" opacity="0.6" />

      {/* daily dots */}
      {showDots && visHist.map(([d, w]) => <circle key={d} cx={xS(d)} cy={yS(w)} r="1.7" fill="var(--ink)" opacity="0.26" />)}

      {/* trend + projection */}
      <path className={`hl-${uid}`} d={trendPath} stroke={projColor} strokeWidth="2.6" fill="none" strokeLinejoin="round" strokeLinecap="round" />
      <path d={`M ${projStartX} ${projStartY} L ${kneeX} ${kneeY} L ${projEndX} ${projEndY}`} stroke={projColor} strokeWidth="2.6" strokeDasharray="4 4" fill="none" strokeLinecap="round" />

      {/* markers, today solid, projection open circle */}
      <circle cx={projStartX} cy={projStartY} r="4.5" fill={projColor} />
      <circle cx={projEndX} cy={projEndY} r="4.5" fill="var(--paper)" stroke={projColor} strokeWidth="2" />

      {/* tap targets, reveal scrubber + readout for that point */}
      {visHist.map(([d, w]) => <circle key={'h' + d} cx={xS(d)} cy={yS(w)} r="9" fill="transparent" style={{ cursor: 'pointer' }} onClick={() => setSel({ x: xS(d), y: yS(w), val: w, label: _fmtDay(_dayDate(d)) })} />)}
      <circle cx={projStartX} cy={projStartY} r="11" fill="transparent" style={{ cursor: 'pointer' }} onClick={() => setSel({ x: projStartX, y: projStartY, val: p.trendingToday, label: 'Today' })} />
      <circle cx={projEndX} cy={projEndY} r="11" fill="transparent" style={{ cursor: 'pointer' }} onClick={() => setSel({ x: projEndX, y: projEndY, val: p.projected, label: 'Weigh-in' })} />

      {/* selection scrubber + info callout */}
      {eff && (() => {
        const s = eff;
        const dotHi = <React.Fragment><line x1={s.x} x2={s.x} y1={padT} y2={H - padB} stroke="var(--ink-3)" strokeWidth="1" opacity="0.4" /><circle cx={s.x} cy={s.y} r="4.5" fill="var(--paper)" stroke={projColor} strokeWidth="2" /></React.Fragment>;
        if (callout === 'top') {
          return (<g>{dotHi}<g style={{ pointerEvents: 'none' }}>
            <rect x={padL} y={padT} width="98" height="36" rx="8" fill="var(--paper)" stroke="var(--rule)" />
            <text x={padL + 11} y={padT + 15} fontFamily="var(--sans)" fontSize="9" fontWeight="600" letterSpacing="0.04em" fill="var(--ink-3)">{s.label.toUpperCase()}</text>
            <text x={padL + 11} y={padT + 29} fontFamily="var(--num)" fontSize="15" fontWeight="600" fill="var(--ink)" style={{ fontFeatureSettings: '"tnum"' }}>{s.val.toFixed(1)}<tspan fontFamily="var(--mono)" fontSize="9" fill="var(--ink-3)" dx="2">kg</tspan></text>
          </g></g>);
        }
        const light = callout === 'card';
        const bw = 84, bh = 40, bx = Math.max(padL, Math.min(W - padR - bw, s.x - bw / 2)), by = Math.max(2, s.y - bh - 13), cx = bx + bw / 2;
        return (<g>{dotHi}<g style={{ pointerEvents: 'none' }}>
          <path d={`M ${s.x - 5} ${by + bh} L ${s.x} ${by + bh + 6} L ${s.x + 5} ${by + bh} Z`} fill={light ? 'var(--paper)' : 'var(--ink)'} stroke={light ? 'var(--rule)' : 'none'} />
          <rect x={bx} y={by} width={bw} height={bh} rx="9" fill={light ? 'var(--paper)' : 'var(--ink)'} stroke={light ? 'var(--rule)' : 'none'} />
          <text x={cx} y={by + 15} textAnchor="middle" fontFamily="var(--sans)" fontSize="9.5" fontWeight="500" fill={light ? 'var(--ink-3)' : 'rgba(255,255,255,0.62)'}>{s.label}</text>
          <text x={cx} y={by + 31} textAnchor="middle" fontFamily="var(--num)" fontSize="16" fontWeight="600" fill={light ? 'var(--ink)' : '#fff'} style={{ fontFeatureSettings: '"tnum"' }}>{s.val.toFixed(1)}<tspan fontFamily="var(--mono)" fontSize="9" fill={light ? 'var(--ink-3)' : 'rgba(255,255,255,0.62)'} dx="2">kg</tspan></text>
        </g></g>);
      })()}

      {/* x labels, start · today · weigh-in (anchor) */}
      <g style={{ fontFeatureSettings: '"tnum"' }}>
        <text x={xS(xMinDay)} y={H - padB + 17} textAnchor="start" fontFamily="var(--sans)" fontSize="11" fill="var(--ink-3)" fontWeight="400">{xMinDay === 0 ? 'Start' : ''}</text>
        <text x={xS(xMinDay)} y={H - padB + 29} textAnchor="start" fontFamily="var(--sans)" fontSize="9" fill="var(--ink-3)" fontWeight="400">{_fmtDay(_dayDate(xMinDay))}</text>
        <text x={projStartX} y={H - padB + 17} textAnchor="middle" fontFamily="var(--sans)" fontSize="11" fill="var(--ink-2)" fontWeight="600">Today</text>
        <text x={projStartX} y={H - padB + 29} textAnchor="middle" fontFamily="var(--sans)" fontSize="9" fill="var(--ink-3)" fontWeight="400">{_fmtDay(_dayDate(FC.todayDay))}</text>
        <text x={projEndX} y={H - padB + 17} textAnchor="end" fontFamily="var(--sans)" fontSize="11" fill="var(--ink)" fontWeight="700">Weigh-in</text>
        <text x={projEndX} y={H - padB + 29} textAnchor="end" fontFamily="var(--sans)" fontSize="9" fill="var(--ink-3)" fontWeight="400">{_fmtDay(_dayDate(FC.campDays))}</text>
      </g>
    </svg>
  );
}

// ── FCChartCard, heading + time-range toggle + legend around the plot. ──────
// `variant` picks the shell styling: 'clean' (iOS segmented), 'editorial'
// (Oswald + underline tabs), 'lab' (mono + bracket tabs). Range windows the
// x-domain; the projection always runs to weigh-in.
function FCRangeToggle({ variant, value, onChange, opts }) {
  if (variant === 'editorial') {
    return (
      <div style={{ display: 'flex', gap: 16 }}>
        {opts.map(o => {
          const on = o === value;
          return <button key={o} onClick={() => onChange(o)} style={{ fontFamily: 'var(--display)', fontSize: 12, letterSpacing: '0.1em', textTransform: 'uppercase', fontWeight: 600, color: on ? 'var(--ink)' : 'var(--ink-3)', paddingBottom: 3, borderBottom: on ? '2px solid var(--accent)' : '2px solid transparent' }}>{o}</button>;
        })}
      </div>
    );
  }
  if (variant === 'lab') {
    return (
      <div style={{ display: 'flex', gap: 6 }}>
        {opts.map(o => {
          const on = o === value;
          return <button key={o} onClick={() => onChange(o)} style={{ fontFamily: 'var(--mono)', fontSize: 10.5, letterSpacing: '0.08em', fontWeight: 600, color: on ? 'var(--paper)' : 'var(--ink-3)', background: on ? 'var(--ink)' : 'transparent', border: '1px solid ' + (on ? 'var(--ink)' : 'var(--rule)'), borderRadius: 5, padding: '4px 9px' }}>{o}</button>;
        })}
      </div>
    );
  }
  if (variant === 'health') {
    // Apple-style full-width sliding segmented control
    return (
      <div style={{ display: 'flex', background: 'var(--paper-2)', borderRadius: 9, padding: 2, gap: 2, width: '100%' }}>
        {opts.map(o => {
          const on = o === value;
          return <button key={o} onClick={() => onChange(o)} style={{ flex: 1, textAlign: 'center', fontFamily: 'var(--sans)', fontSize: 13, fontWeight: on ? 600 : 500, color: on ? 'var(--ink)' : 'var(--ink-2)', background: on ? 'var(--paper)' : 'transparent', boxShadow: on ? '0 1px 3px rgba(0,0,0,0.14)' : 'none', borderRadius: 7, padding: '7px 0' }}>{o}</button>;
        })}
      </div>
    );
  }
  // clean, iOS segmented
  return (
    <div style={{ display: 'flex', background: 'var(--paper-2)', borderRadius: 9, padding: 2, gap: 2 }}>
      {opts.map(o => {
        const on = o === value;
        return <button key={o} onClick={() => onChange(o)} style={{ fontFamily: 'var(--sans)', fontSize: 12.5, fontWeight: on ? 600 : 500, color: on ? 'var(--ink)' : 'var(--ink-3)', background: on ? 'var(--paper)' : 'transparent', boxShadow: on ? '0 1px 2px rgba(0,0,0,0.12)' : 'none', borderRadius: 7, padding: '5px 12px' }}>{o}</button>;
      })}
    </div>
  );
}

function FCLegend({ variant, projColor, showZone = false }) {
  const items = [
    ['3-day average', <svg width="22" height="8"><line x1="0" y1="4" x2="22" y2="4" stroke={projColor} strokeWidth="2.8" strokeLinecap="round"/></svg>],
    ['Projection', <svg width="22" height="8"><line x1="0" y1="4" x2="22" y2="4" stroke={projColor} strokeWidth="2.8" strokeDasharray="4 4" strokeLinecap="round"/></svg>],
  ];
  if (showZone) items.push(['Target zone', <svg width="22" height="12"><rect x="0" y="1.5" width="22" height="9" rx="2" fill={projColor} opacity="0.13"/></svg>]);
  items.push(['Limit', <svg width="22" height="8"><line x1="0" y1="4" x2="22" y2="4" stroke="var(--ink)" strokeWidth="1.2" strokeDasharray="5 3"/></svg>]);
  return (
    <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '11px 16px', paddingTop: 15, marginTop: 4, borderTop: '1px solid var(--rule)' }}>
      {items.map(([lbl, sw]) => (
        <span key={lbl} style={{ display: 'inline-flex', alignItems: 'center', gap: 9 }}>
          <span style={{ width: 22, display: 'inline-flex', justifyContent: 'center', flexShrink: 0 }}>{sw}</span>
          <span style={{ fontFamily: 'var(--sans)', fontSize: 12.5, color: 'var(--ink-2)', fontWeight: 500 }}>{lbl}</span>
        </span>
      ))}
    </div>
  );
}

function FCChartCard({ paceKey = 'onTrack', variant = 'clean', heading = 'Weight trend', showZone = true, showDots = true, width = 331 }) {
  const [range, setRange] = React.useState('Camp');
  const opts = ['2W', '6W', 'Camp'];
  const rangeDays = { '2W': 14, '6W': 42, 'Camp': null }[range];
  const rangeStartDay = rangeDays == null ? 0 : (window.FC.todayDay - rangeDays);
  const p = window.computePace(paceKey);
  const projColor = p.tone === 'red' ? 'var(--red)' : p.tone === 'amber' ? 'var(--mustard-deep)' : 'var(--t-green)';
  const isHealth = variant === 'health';

  const Head = () => {
    if (variant === 'editorial') return (
      <div style={{ marginBottom: 14 }}>
        <div style={{ fontFamily: 'var(--display)', fontSize: 10.5, letterSpacing: '0.18em', textTransform: 'uppercase', color: 'var(--accent)', fontWeight: 600 }}>Trajectory</div>
        <div style={{ display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', gap: 12, marginTop: 6 }}>
          <span style={{ fontFamily: 'var(--sans)', fontSize: 19, fontWeight: 700, letterSpacing: '-0.01em', color: 'var(--ink)' }}>{heading}</span>
          <FCRangeToggle variant={variant} value={range} onChange={setRange} opts={opts} />
        </div>
      </div>
    );
    if (variant === 'lab') return (
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, marginBottom: 14 }}>
        <span style={{ fontFamily: 'var(--mono)', fontSize: 11, letterSpacing: '0.12em', textTransform: 'uppercase', color: 'var(--ink-2)', fontWeight: 600 }}>{heading} <span style={{ color: 'var(--ink-3)' }}>// 5-day avg</span></span>
        <FCRangeToggle variant={variant} value={range} onChange={setRange} opts={opts} />
      </div>
    );
    if (isHealth) return (
      <div style={{ marginBottom: 14 }}>
        <FCRangeToggle variant="health" value={range} onChange={setRange} opts={opts} />
        <div style={{ fontFamily: 'var(--sans)', fontSize: 16, fontWeight: 600, color: 'var(--ink)', marginTop: 14 }}>{heading}</div>
      </div>
    );
    return (
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, marginBottom: 14 }}>
        <span style={{ fontFamily: 'var(--sans)', fontSize: 16, fontWeight: 600, color: 'var(--ink)' }}>{heading}</span>
        <FCRangeToggle variant={variant} value={range} onChange={setRange} opts={opts} />
      </div>
    );
  };

  return (
    <div style={{ width: '100%' }}>
      <Head />
      <div style={{ display: 'flex', justifyContent: 'center' }}>
        {isHealth
          ? <FCHealthChart paceKey={paceKey} width={width} height={236} showZone={showZone} showDots={showDots} rangeStartDay={rangeStartDay} />
          : <FCChart paceKey={paceKey} width={width} height={236} showBand={showZone} showPlan={false} fillMode="none" rangeStartDay={rangeStartDay} />}
      </div>
      <FCLegend variant={variant} projColor={projColor} showZone={showZone} />
    </div>
  );
}

// ── FCHealthCard, Apple-Health-axis card with a clean Projected/Goal readout.
// `readout`: 'header' (stats above chart, Apple-callout style) · 'below' (stat
// row under chart) · 'minimal' (chart only). Range toggle + legend included.
function HStat({ label, val, unit = 'kg', color = 'var(--ink)', i = 0 }) {
  return (
    <div style={{ flex: 1, paddingLeft: i ? 18 : 0, borderLeft: i ? '1px solid var(--rule)' : 'none' }}>
      <div style={{ fontFamily: 'var(--sans)', fontSize: 11, fontWeight: 600, color: 'var(--ink-3)' }}>{label}</div>
      <div style={{ display: 'flex', alignItems: 'baseline', gap: 3, marginTop: 8 }}>
        <span style={{ fontFamily: 'var(--num)', fontWeight: 300, fontSize: 27, letterSpacing: '-0.02em', color }}>{val}</span>
        <span style={{ fontFamily: 'var(--mono)', fontSize: 10, color: 'var(--ink-3)', fontWeight: 600 }}>{unit}</span>
      </div>
    </div>
  );
}

function FCHealthCard({ paceKey = 'onTrack', place = 'left', heading = 'Weight trend', showZone = true, showDots = true, callout = 'pill', preview = null, width = 331 }) {
  const FC = window.FC;
  const [range, setRange] = React.useState('Camp');
  const opts = ['2W', '6W', 'Camp'];
  const rangeDays = { '2W': 14, '6W': 42, 'Camp': null }[range];
  const rangeStartDay = rangeDays == null ? 0 : (FC.todayDay - rangeDays);
  const p = window.computePace(paceKey);
  const projColor = p.tone === 'red' ? 'var(--red)' : p.tone === 'amber' ? 'var(--mustard-deep)' : 'var(--t-green)';
  const lblS = { fontFamily: 'var(--sans)', fontSize: 10.5, fontWeight: 600, letterSpacing: '0.04em', textTransform: 'uppercase', color: 'var(--ink-3)' };
  const numS = { fontFamily: 'var(--num)', fontWeight: 400, fontSize: 21, letterSpacing: '-0.02em', marginTop: 4 };

  const pairRow = (al, mt = 13) => (
    <div style={{ display: 'flex', gap: 26, marginTop: mt, justifyContent: al || 'flex-start' }}>
      {[['Projected', p.projected.toFixed(1), projColor], ['Limit', FC.goalWeight.toFixed(1), 'var(--ink-2)']].map(([lb, v, c]) => (
        <div key={lb} style={{ textAlign: al === 'flex-end' ? 'right' : 'left' }}><div style={lblS}>{lb}</div><div style={{ ...numS, color: c }}>{v}</div></div>
      ))}
    </div>
  );
  const headingEl = (
    <div style={{ display: 'flex', alignItems: 'baseline', gap: 6 }}>
      <span style={{ fontFamily: 'var(--sans)', fontSize: 18, fontWeight: 700, letterSpacing: '-0.01em', color: 'var(--ink)' }}>{heading}</span>
      <span style={{ fontFamily: 'var(--sans)', fontSize: 13, fontWeight: 500, color: 'var(--ink-3)' }}>(kg)</span>
    </div>
  );

  return (
    <div style={{ width: '100%' }}>
      <FCRangeToggle variant="health" value={range} onChange={setRange} opts={opts} />
      <div style={{ marginTop: 16 }}>
        {place === 'right' ? (
          <React.Fragment>{heading && headingEl}{pairRow('flex-end')}</React.Fragment>
        ) : (
          <React.Fragment>{heading && headingEl}{place === 'left' && pairRow()}</React.Fragment>
        )}
      </div>
      <div style={{ display: 'flex', justifyContent: 'center', marginTop: 14 }}>
        <FCHealthChart paceKey={paceKey} width={width} height={236} showZone={showZone} showDots={showDots} callout={callout} preview={preview} rangeStartDay={rangeStartDay} />
      </div>
      {place === 'below' && <div style={{ marginTop: 14, paddingTop: 14, borderTop: '1px solid var(--rule)' }}>{pairRow('flex-start', 0)}</div>}
      <FCLegend variant="clean" projColor={projColor} showZone={showZone} />
    </div>
  );
}

Object.assign(window, { FCChart, FCHealthChart, FCHealthCard, HStat, FCChartCard, FCRangeToggle, FCLegend, _dayDate, _fmtDay });
