// fc-instruments.jsx, production chart instruments rolled into the app.
//   FCBurndownChart  , kg-to-go vs days-to-go, ideal vs actual (Trend tab)
//   FCConeChart      , cycle-aware projection cone (honest "will I make it")
//   FCTimeDriftChart , weigh-in time-of-day vs your set window (adherence)
//   FCCyclePhaseChart, weight trend with phase bands + plan undulating w/ cycle
// Plus recentLog(), enriched weigh-in rows (time, off-window, double readings).
// All theme-aware via CSS vars; tone follows computePace().

function _isp(pts, k = 0.7) {
  if (pts.length < 2) return '';
  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)];
    d += ` C ${(p1[0] + (p2[0] - p0[0]) * k / 3).toFixed(2)} ${(p1[1] + (p2[1] - p0[1]) * k / 3).toFixed(2)}, ${(p2[0] - (p3[0] - p1[0]) * k / 3).toFixed(2)} ${(p2[1] - (p3[1] - p1[1]) * k / 3).toFixed(2)}, ${p2[0].toFixed(2)} ${p2[1].toFixed(2)}`;
  }
  return d;
}
const _ilin = (pts) => 'M ' + pts.map(p => `${p[0].toFixed(2)} ${p[1].toFixed(2)}`).join(' L ');
const _toneCol = (tone) => tone === 'red' ? 'var(--red)' : tone === 'amber' ? 'var(--mustard-deep)' : 'var(--t-green)';

// ═══ shared card chrome ═══════════════════════════════════════════════════
function InstrumentCard({ eyebrow, eyebrowColor = 'var(--accent)', headline, children, foot = null }) {
  return (
    <div style={{ background: 'var(--paper)', borderRadius: 'var(--radius)', boxShadow: 'var(--card-shadow)', border: 'var(--card-border)', padding: 20 }}>
      <div style={{ fontFamily: 'var(--display)', fontSize: 10.5, letterSpacing: '0.18em', textTransform: 'uppercase', color: eyebrowColor, fontWeight: 600 }}>{eyebrow}</div>
      {headline && <div style={{ fontFamily: 'var(--sans)', fontSize: 18, fontWeight: 700, lineHeight: 1.25, letterSpacing: '-0.01em', color: 'var(--ink)', marginTop: 8, textWrap: 'balance' }}>{headline}</div>}
      <div style={{ marginTop: 16 }}>{children}</div>
      {foot}
    </div>
  );
}

// ═══ 1 · BURNDOWN ═════════════════════════════════════════════════════════
function FCBurndownChart({ paceKey = 'onTrack', width = 331, height = 184 }) {
  const FC = window.FC, p = window.computePace(paceKey);
  const W = width, H = height, padL = 8, padR = 40, padT = 16, padB = 28;
  const innerW = W - padL - padR, innerH = H - padT - padB;
  const rem = (w) => Math.max(0, w - FC.goalWeight);
  const startRem = rem(FC.planStart);
  const maxY = startRem * 1.08;
  const xS = (d) => padL + (d / FC.campDays) * innerW;
  const yS = (kg) => padT + (1 - kg / maxY) * innerH;
  const baseY = yS(0);
  const ideal = [[xS(0), yS(startRem)], [xS(FC.campDays), yS(0)]];
  const actualPts = p.trend.map(t => [xS(t[0]), yS(rem(t[1]))]);
  const todayPt = actualPts[actualPts.length - 1];
  const projPts = [[xS(FC.todayDay), yS(rem(p.trendingToday))], [xS(FC.campDays), yS(rem(p.projected))]];
  const idealRemNow = startRem * (1 - FC.todayDay / FC.campDays);
  const behind = rem(p.trendingToday) - idealRemNow;     // + = behind the line
  const col = _toneCol(p.tone);
  const uid = React.useId().replace(/:/g, '');

  return (
    <svg width={W} height={H} viewBox={`0 0 ${W} ${H}`} style={{ display: 'block', overflow: 'visible' }} role="img"
      aria-label={`Burndown: ${rem(p.trendingToday).toFixed(1)} kg to go with ${FC.campDays - FC.todayDay} days left.`}>
      {/* y ticks */}
      {[0, Math.round(startRem / 2), Math.round(startRem)].map(kg => (
        <g key={kg}>
          <line x1={padL} x2={W - padR} y1={yS(kg)} y2={yS(kg)} stroke="var(--rule)" strokeWidth="0.6" opacity="0.6" />
          <text x={W - padR + 6} y={yS(kg) + 3.4} fontFamily="var(--mono)" fontSize="9.5" fill="var(--ink-3)" style={{ fontFeatureSettings: '"tnum"' }}>{kg}</text>
        </g>
      ))}
      <text x={W - padR + 6} y={padT - 4} fontFamily="var(--mono)" fontSize="7.5" fill="var(--ink-3)" letterSpacing="0.14em" fontWeight="600">KG TO GO</text>
      {/* ideal burndown line */}
      <path d={_ilin(ideal)} fill="none" stroke="var(--ink-3)" strokeWidth="1.4" strokeDasharray="5 4" opacity="0.7" />
      <text x={xS(FC.campDays * 0.5)} y={yS(startRem * 0.5) - 6} textAnchor="middle" fontFamily="var(--mono)" fontSize="8" fill="var(--ink-3)" letterSpacing="0.1em" fontWeight="600" transform={`rotate(-9 ${xS(FC.campDays * 0.5)} ${yS(startRem * 0.5)})`}>ON-PLAN LINE</text>
      {/* actual + projection */}
      <path d={_isp(actualPts)} fill="none" stroke={col} strokeWidth="2.6" strokeLinecap="round" strokeLinejoin="round" />
      <path d={_ilin(projPts)} fill="none" stroke={col} strokeWidth="2.4" strokeDasharray="4 4" strokeLinecap="round" />
      <circle cx={todayPt[0]} cy={todayPt[1]} r="4.5" fill={col} />
      <circle cx={xS(FC.campDays)} cy={yS(rem(p.projected))} r="4.5" fill="var(--paper)" stroke={col} strokeWidth="2" />
      {/* x labels */}
      <g style={{ fontFeatureSettings: '"tnum"' }}>
        <text x={padL} y={baseY + 16} fontFamily="var(--sans)" fontSize="10.5" fill="var(--ink-3)">Start</text>
        <text x={todayPt[0]} y={baseY + 16} textAnchor="middle" fontFamily="var(--sans)" fontSize="10.5" fill="var(--ink-2)" fontWeight="600">Today</text>
        <text x={W - padR} y={baseY + 16} textAnchor="end" fontFamily="var(--sans)" fontSize="10.5" fill="var(--ink)" fontWeight="700">Weigh-in</text>
      </g>
    </svg>
  );
}

function FCBurndownCard({ paceKey = 'onTrack', width = 331 }) {
  const FC = window.FC, p = window.computePace(paceKey);
  const daysLeft = FC.campDays - FC.todayDay;
  const idealRemNow = (FC.planStart - FC.goalWeight) * (1 - FC.todayDay / FC.campDays);
  const behind = (p.trendingToday - FC.goalWeight) - idealRemNow;
  const ahead = behind <= 0.05;
  const head = ahead
    ? `${Math.abs(p.toGo).toFixed(1)} kg to go, ${daysLeft} days out, on or ahead of the line.`
    : `${Math.abs(p.toGo).toFixed(1)} kg to go in ${daysLeft} days, ${behind.toFixed(1)} kg behind the line.`;
  return (
    <InstrumentCard eyebrow="Cut burndown" eyebrowColor={ahead ? 'var(--t-green)' : 'var(--mustard-deep)'} headline={head}>
      <FCBurndownChart paceKey={paceKey} width={width} />
    </InstrumentCard>
  );
}

// ═══ 2 · PROJECTION CONE (cycle-aware) ════════════════════════════════════
function FCConeChart({ paceKey = 'onTrack', width = 331, height = 210 }) {
  const FC = window.FC, p = window.computePace(paceKey);
  const W = width, H = height, padL = 8, padR = 44, padT = 16, padB = 30;
  const innerW = W - padL - padR, innerH = H - padT - padB;
  const daysLeft = FC.campDays - FC.todayDay;
  // cone half-width grows with horizon; upper edge also carries the weigh-in water offset
  const halfEnd = 0.45 + daysLeft * 0.035;
  const upEnd = p.projected + halfEnd + Math.max(0, p.weighInOffset);
  const loEnd = p.projected - halfEnd;
  const visTrend = p.trend.filter(t => t[0] >= 0);
  const ys = [...visTrend.map(t => t[1]), upEnd, loEnd, FC.goalWeight];
  const dMin = Math.min(...ys), dMax = Math.max(...ys), padW = (dMax - dMin) * 0.18 || 0.5;
  const yMin = dMin - padW, yMax = dMax + padW;
  const xS = (d) => padL + (d / FC.campDays) * innerW;
  const yS = (w) => padT + ((yMax - w) / (yMax - yMin)) * innerH;
  const trendPts = visTrend.map(t => [xS(t[0]), yS(t[1])]);
  const tx = xS(FC.todayDay), ty = yS(p.trendingToday), ex = xS(FC.campDays);
  const goalY = yS(FC.goalWeight);
  const col = _toneCol(p.tone);
  const conePoly = `${tx},${ty} ${ex},${yS(upEnd)} ${ex},${yS(loEnd)}`;
  const clears = upEnd <= FC.goalWeight + 0.02;
  const misses = loEnd >= FC.goalWeight - 0.02;
  const uid = React.useId().replace(/:/g, '');

  return (
    <svg width={W} height={H} viewBox={`0 0 ${W} ${H}`} style={{ display: 'block', overflow: 'visible' }} role="img"
      aria-label={`Projection cone: likely weigh-in between ${loEnd.toFixed(1)} and ${upEnd.toFixed(1)} kg versus ${FC.goalWeight} kg limit.`}>
      {/* goal line */}
      <line x1={padL} x2={W - padR} y1={goalY} y2={goalY} stroke="var(--ink)" strokeWidth="1.1" strokeDasharray="5 3" opacity="0.75" />
      <text x={W - padR + 5} y={goalY - 4} fontFamily="var(--mono)" fontSize="8" fill="var(--ink)" letterSpacing="0.12em" fontWeight="700">LIMIT</text>
      <text x={W - padR + 5} y={goalY + 11} fontFamily="var(--num)" fontSize="15" fill="var(--ink)" fontWeight="500">{FC.goalWeight.toFixed(1)}</text>
      {/* cone */}
      <polygon points={conePoly} fill={col} opacity="0.14" />
      <path d={_ilin([[tx, ty], [ex, yS(upEnd)]])} fill="none" stroke={col} strokeWidth="1.2" strokeDasharray="3 3" opacity="0.7" />
      <path d={_ilin([[tx, ty], [ex, yS(loEnd)]])} fill="none" stroke={col} strokeWidth="1.2" strokeDasharray="3 3" opacity="0.7" />
      {/* central projection */}
      <path d={_ilin([[tx, ty], [ex, yS(p.projected)]])} fill="none" stroke={col} strokeWidth="2" strokeDasharray="5 4" opacity="0.9" />
      {/* trend */}
      <path d={_isp(trendPts, 0.85)} fill="none" stroke={col} strokeWidth="2.6" strokeLinecap="round" strokeLinejoin="round" />
      <circle cx={tx} cy={ty} r="4.5" fill={col} />
      {/* cone end caps */}
      <circle cx={ex} cy={yS(p.projected)} r="3.6" fill="var(--paper)" stroke={col} strokeWidth="1.8" />
      {/* right-edge range readout */}
      <g style={{ fontFeatureSettings: '"tnum"' }}>
        <text x={W - padR + 5} y={yS(upEnd) + 3} fontFamily="var(--mono)" fontSize="10" fill={col} fontWeight="600">{upEnd.toFixed(1)}</text>
        <text x={W - padR + 5} y={yS(loEnd) + 3} fontFamily="var(--mono)" fontSize="10" fill={col} fontWeight="600">{loEnd.toFixed(1)}</text>
      </g>
      {/* x labels */}
      <g style={{ fontFeatureSettings: '"tnum"' }}>
        <text x={padL} y={H - padB + 16} fontFamily="var(--sans)" fontSize="10.5" fill="var(--ink-3)">Start</text>
        <text x={tx} y={H - padB + 16} textAnchor="middle" fontFamily="var(--sans)" fontSize="10.5" fill="var(--ink-2)" fontWeight="600">Today</text>
        <text x={ex} y={H - padB + 16} textAnchor="end" fontFamily="var(--sans)" fontSize="10.5" fill="var(--ink)" fontWeight="700">Weigh-in</text>
      </g>
    </svg>
  );
}

function FCConeCard({ paceKey = 'onTrack', width = 331 }) {
  const FC = window.FC, p = window.computePace(paceKey);
  const daysLeft = FC.campDays - FC.todayDay;
  const halfEnd = 0.45 + daysLeft * 0.035;
  const upEnd = p.projected + halfEnd + Math.max(0, p.weighInOffset);
  const loEnd = p.projected - halfEnd;
  const clears = upEnd <= FC.goalWeight + 0.02;
  const misses = loEnd >= FC.goalWeight - 0.02;
  const tone = clears ? 'var(--t-green)' : misses ? 'var(--red)' : 'var(--mustard-deep)';
  const head = clears ? `Likely ${loEnd.toFixed(1)}–${upEnd.toFixed(1)} kg, clears the limit even with cycle water.`
    : misses ? `Likely ${loEnd.toFixed(1)}–${upEnd.toFixed(1)} kg, the whole range misses the limit.`
    : `Likely ${loEnd.toFixed(1)}–${upEnd.toFixed(1)} kg, the range still straddles the limit.`;
  return (
    <InstrumentCard eyebrow="How sure are we?" eyebrowColor={tone} headline={head}
      foot={p.weighInOffset > 0.05 ? <div style={{ fontFamily: 'var(--sans)', fontSize: 12.5, color: 'var(--ink-3)', lineHeight: 1.5, marginTop: 14, paddingTop: 13, borderTop: '1px solid var(--rule)', textWrap: 'pretty' }}>Upper edge includes <strong style={{ color: 'var(--ink-2)', fontWeight: 600 }}>+{p.weighInOffset.toFixed(1)} kg</strong> of luteal water expected at weigh-in. Your true-weight target is {p.trueTarget.toFixed(1)} kg.</div> : null}>
      <FCConeChart paceKey={paceKey} width={width} />
    </InstrumentCard>
  );
}

// ═══ 3 · WEIGH-IN TIME DRIFT ══════════════════════════════════════════════
function FCTimeDriftChart({ width = 331, height = 168, days = 20 }) {
  const FC = window.FC, target = FC.weighInTargetHour;
  const W = width, H = height, padL = 34, padR = 12, padT = 14, padB = 26;
  const innerW = W - padL - padR, innerH = H - padT - padB;
  const yLo = target - 1.6, yHi = target + 3.2;          // morning window; outliers clamp to top
  const xS = (i) => padL + (i / (days - 1)) * innerW;
  const yS = (h) => padT + ((Math.min(yHi, Math.max(yLo, h)) - yLo) / (yHi - yLo)) * innerH;  // note: larger hour = lower on screen? invert:
  const yInv = (h) => padT + (1 - (Math.min(yHi, Math.max(yLo, h)) - yLo) / (yHi - yLo)) * innerH;
  const startDay = FC.todayDay - days + 1;
  const ticks = [target - 1, target, target + 1, target + 2];
  const doubleDay = FC.todayDay - window.FC_DOUBLE_DAY_OFFSET;

  const dots = [];
  for (let i = 0; i < days; i++) {
    const day = startDay + i, h = window.weighInClock(day), off = window.weighInOffWindow(day);
    const clamped = h > yHi;
    dots.push(
      <g key={i}>
        {clamped
          ? <path d={`M ${xS(i).toFixed(1)} ${(padT + 2).toFixed(1)} l -3.4 5 l 6.8 0 z`} fill="var(--red)" />
          : <circle cx={xS(i)} cy={yInv(h)} r={off ? 3.4 : 3} fill={off ? 'var(--red)' : 'var(--ink)'} opacity={off ? 1 : 0.5} />}
        {clamped && <text x={xS(i)} y={padT + 16} textAnchor="middle" fontFamily="var(--mono)" fontSize="7" fill="var(--red)" fontWeight="600">{window.fmtClock(h).replace(' ', '')}</text>}
        {day === doubleDay && !clamped && <circle cx={xS(i) + 4} cy={yInv(h) + 4} r="2.4" fill="none" stroke="var(--ink-3)" strokeWidth="1" opacity="0.7" />}
      </g>
    );
  }
  return (
    <svg width={W} height={H} viewBox={`0 0 ${W} ${H}`} style={{ display: 'block', overflow: 'visible' }} role="img" aria-label="Weigh-in time of day versus your set window.">
      {/* comparable-window band ±1h */}
      <rect x={padL} y={yInv(target + 1)} width={innerW} height={yInv(target - 1) - yInv(target + 1)} fill="var(--t-green)" opacity="0.10" />
      {/* hour gridlines */}
      {ticks.map(h => (
        <g key={h}>
          <line x1={padL} x2={W - padR} y1={yInv(h)} y2={yInv(h)} stroke="var(--rule)" strokeWidth="0.6" opacity={h === target ? 0 : 0.6} />
          <text x={padL - 6} y={yInv(h) + 3.2} textAnchor="end" fontFamily="var(--mono)" fontSize="9" fill="var(--ink-3)" style={{ fontFeatureSettings: '"tnum"' }}>{window.fmtClock(h).replace(':00', '').replace(' ', '')}</text>
        </g>
      ))}
      {/* target line */}
      <line x1={padL} x2={W - padR} y1={yInv(target)} y2={yInv(target)} stroke="var(--t-green)" strokeWidth="1.4" strokeDasharray="5 3" />
      <text x={padL} y={yInv(target) - 5} fontFamily="var(--mono)" fontSize="8" fill="var(--t-green)" letterSpacing="0.04em" fontWeight="700">SET · {FC.weighInTime}</text>
      {dots}
      <text x={padL} y={H - 6} fontFamily="var(--sans)" fontSize="10" fill="var(--ink-3)">{days} days</text>
      <text x={W - padR} y={H - 6} textAnchor="end" fontFamily="var(--sans)" fontSize="10" fill="var(--ink-2)" fontWeight="600">Today</text>
    </svg>
  );
}

function FCTimeDriftCard({ width = 331, days = 20, onChangeTime = null }) {
  const FC = window.FC;
  const startDay = FC.todayDay - days + 1;
  let off = 0; for (let d = startDay; d <= FC.todayDay; d++) if (window.weighInOffWindow(d)) off++;
  const inWin = days - off;
  return (
    <InstrumentCard eyebrow="Weigh-in time drift" eyebrowColor={off <= 2 ? 'var(--t-green)' : 'var(--mustard-deep)'}
      headline={`${inWin} of ${days} readings landed in your ${FC.weighInTime} window.`}
      foot={
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, marginTop: 14, paddingTop: 13, borderTop: '1px solid var(--rule)' }}>
          <div style={{ fontFamily: 'var(--sans)', fontSize: 12.5, color: 'var(--ink-3)', lineHeight: 1.5, textWrap: 'pretty', flex: 1 }}>Off-window readings are noisier. Same time daily beats the perfect time.</div>
          <button onClick={onChangeTime || undefined} style={{ flexShrink: 0, fontFamily: 'var(--sans)', fontSize: 13.5, fontWeight: 600, color: 'var(--accent)', whiteSpace: 'nowrap' }}>Change time</button>
        </div>
      }>
      <FCTimeDriftChart width={width} days={days} />
    </InstrumentCard>
  );
}

// ═══ 4 · CYCLE-PHASE TREND (plan undulates with cycle) ════════════════════
function FCCyclePhaseChart({ paceKey = 'onTrack', width = 331, height = 210 }) {
  const FC = window.FC, p = window.computePace(paceKey), CP = window.CYCLE_PHASE;
  const W = width, H = height, padL = 8, padR = 40, padT = 26, padB = 30;
  const innerW = W - padL - padR, innerH = H - padT - padB;
  // true plan heads to trueTarget; expected SCALE plan = true plan + cycle water → undulates, lands on goal
  const truePlan = (d) => FC.planStart + (p.trueTarget - FC.planStart) * (d / FC.campDays);
  const scalePlan = (d) => truePlan(d) + window.cycleOffsetKg(d);
  const visTrend = p.trend.filter(t => t[0] >= 0);
  const planSamples = []; for (let d = 0; d <= FC.campDays; d += 2) planSamples.push(d);
  const ys = [...visTrend.map(t => t[1]), ...planSamples.map(scalePlan), FC.goalWeight, p.trueTarget];
  const dMin = Math.min(...ys), dMax = Math.max(...ys), padW = (dMax - dMin) * 0.16 || 0.5;
  const yMin = dMin - padW, yMax = dMax + padW;
  const xS = (d) => padL + (d / FC.campDays) * innerW;
  const yS = (w) => padT + ((yMax - w) / (yMax - yMin)) * innerH;
  const trendPts = visTrend.map(t => [xS(t[0]), yS(t[1])]);
  const scalePts = planSamples.map(d => [xS(d), yS(scalePlan(d))]);
  const goalY = yS(FC.goalWeight);
  const col = _toneCol(p.tone);

  // phase bands across camp
  const bands = [];
  let segStart = 0, segPhase = window.cyclePhaseAt(0);
  for (let d = 1; d <= FC.campDays; d++) {
    const ph = window.cyclePhaseAt(d);
    if (ph !== segPhase || d === FC.campDays) {
      bands.push([segStart, d, segPhase]); segStart = d; segPhase = ph;
    }
  }
  const wiPhase = window.cyclePhaseAt(FC.campDays);

  return (
    <svg width={W} height={H} viewBox={`0 0 ${W} ${H}`} style={{ display: 'block', overflow: 'visible' }} role="img"
      aria-label={`Weight trend with cycle phases. Weigh-in lands in your ${CP[wiPhase].label} phase.`}>
      {/* phase bands */}
      {bands.map(([a, b, ph], i) => (
        <rect key={i} x={xS(a)} y={padT} width={xS(b) - xS(a)} height={innerH} fill={CP[ph].color} opacity="0.12" />
      ))}
      {/* phase legend strip */}
      {bands.filter(([a, b]) => xS(b) - xS(a) > 24).map(([a, b, ph], i) => (
        <text key={'l' + i} x={(xS(a) + xS(b)) / 2} y={padT - 9} textAnchor="middle" fontFamily="var(--mono)" fontSize="7.5" letterSpacing="0.04em" fill={CP[ph].color} fontWeight="700">{CP[ph].short.toUpperCase()}</text>
      ))}
      {/* goal/limit */}
      <line x1={padL} x2={W - padR} y1={goalY} y2={goalY} stroke="var(--ink)" strokeWidth="1.1" strokeDasharray="5 3" opacity="0.7" />
      <text x={W - padR + 5} y={goalY + 3} fontFamily="var(--num)" fontSize="13" fill="var(--ink)" fontWeight="500">{FC.goalWeight.toFixed(1)}</text>
      {/* expected scale plan, undulating */}
      <path d={_isp(scalePts, 0.6)} fill="none" stroke="var(--ink-3)" strokeWidth="1.6" strokeDasharray="4 3" opacity="0.85" />
      <text x={xS(2)} y={yS(scalePlan(2)) - 6} fontFamily="var(--mono)" fontSize="7.5" fill="var(--ink-3)" letterSpacing="0.1em" fontWeight="600">PLAN · CYCLE-ADJUSTED</text>
      {/* actual trend */}
      <path d={_isp(trendPts, 0.85)} fill="none" stroke={col} strokeWidth="2.6" strokeLinecap="round" strokeLinejoin="round" />
      <circle cx={xS(FC.todayDay)} cy={yS(p.trendingToday)} r="4.5" fill={col} />
      {/* weigh-in marker */}
      <line x1={xS(FC.campDays)} x2={xS(FC.campDays)} y1={padT} y2={padT + innerH} stroke={CP[wiPhase].color} strokeWidth="1.4" />
      <g style={{ fontFeatureSettings: '"tnum"' }}>
        <text x={padL} y={H - padB + 16} fontFamily="var(--sans)" fontSize="10.5" fill="var(--ink-3)">Start</text>
        <text x={xS(FC.todayDay)} y={H - padB + 16} textAnchor="middle" fontFamily="var(--sans)" fontSize="10.5" fill="var(--ink-2)" fontWeight="600">Today</text>
        <text x={xS(FC.campDays)} y={H - padB + 16} textAnchor="end" fontFamily="var(--sans)" fontSize="10.5" fill="var(--ink)" fontWeight="700">Weigh-in</text>
      </g>
    </svg>
  );
}

function FCCyclePhaseCard({ paceKey = 'onTrack', width = 331 }) {
  const FC = window.FC, p = window.computePace(paceKey), CP = window.CYCLE_PHASE;
  const wiPhase = CP[window.cyclePhaseAt(FC.campDays)];
  const buf = p.cycleBuffer;
  const head = buf > 0.05
    ? `Weigh-in lands in your ${wiPhase.label.toLowerCase()} phase, plan holds ${buf.toFixed(1)} kg of buffer.`
    : `Weigh-in lands in your ${wiPhase.label.toLowerCase()} phase, low water, no extra buffer needed.`;
  return (
    <InstrumentCard eyebrow="Cycle-adjusted plan" eyebrowColor="var(--accent)" headline={head}
      foot={<div style={{ fontFamily: 'var(--sans)', fontSize: 12.5, color: 'var(--ink-3)', lineHeight: 1.5, marginTop: 14, paddingTop: 13, borderTop: '1px solid var(--rule)', textWrap: 'pretty' }}>The plan line rises and dips with your cycle so a normal hormonal swing never reads as falling behind. True-weight target: <strong style={{ color: 'var(--ink-2)', fontWeight: 600 }}>{p.trueTarget.toFixed(1)} kg</strong>.</div>}>
      <FCCyclePhaseChart paceKey={paceKey} width={width} />
    </InstrumentCard>
  );
}

// ═══ enriched recent log (time + off-window + double readings) ════════════
const _WDl = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
const _MOl = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
function recentLog(paceKey, n = 8) {
  const p = window.computePace(paceKey);
  const doubleDay = window.FC.todayDay - window.FC_DOUBLE_DAY_OFFSET;
  const rows = [...p.hist].reverse().slice(0, n).map(([day, w], i, a) => {
    const dt = window._dayDate(day);
    const prev = a[i + 1] ? a[i + 1][1] : w;
    const h = window.weighInClock(day);
    const row = {
      day, dayLabel: `${_WDl[dt.getDay()]} ${dt.getDate()} ${_MOl[dt.getMonth()]}`,
      w, d: w - prev, time: window.fmtClock(h), off: window.weighInOffWindow(day),
    };
    if (day === doubleDay) {
      // a second, later reading that day, context only, not used for the trend
      row.second = { time: window.fmtClock(h + 11.5), w: +(w + 0.6).toFixed(1) };
    }
    return row;
  });
  return rows;
}

Object.assign(window, {
  FCBurndownChart, FCBurndownCard, FCConeChart, FCConeCard,
  FCTimeDriftChart, FCTimeDriftCard, FCCyclePhaseChart, FCCyclePhaseCard,
  InstrumentCard, recentLog,
});
