import React, { useMemo, useState } from "react";
// Clean, uniform standalone preview with consistent font sizing and image export support.
const DAYS = [
{ key: "mon", label: "MON", focus: "PUSH" },
{ key: "tue", label: "TUE", focus: "PULL" },
{ key: "wed", label: "WED", focus: "COND" },
{ key: "thu", label: "THU", focus: "PUSH" },
{ key: "fri", label: "FRI", focus: "PULL" },
{ key: "sat", label: "SAT", focus: "STREET" },
] as const;
type DayKey = typeof DAYS[number]["key"];
type BMode = "BASE App Skill" | "Strength Program";
const BASE_SKILLS = [
"Back Lever",
"Elbow Lever",
"Front Lever",
"Handstand",
"Muscle Up",
"Planche",
"Ring Muscle Up",
"Shoulder Stand",
"Handstand Push Up",
] as const;
type DayConfig = {
priority: number;
compC: string[];
compA_choice: "Upper" | "Lower";
bMode: BMode;
bSkill?: typeof BASE_SKILLS[number];
order: string[];
};
type WeekPlan = Record;
const COMP_C_DAY_LIFTS: Record = {
mon: ["Dips", "Squats"],
tue: ["Pull Ups", "Deadlifts"],
wed: [],
thu: ["Bench", "Hip Thrust"],
fri: ["Ring Pull + MU", "Squats"],
sat: ["Dips", "Squats", "Pull Ups"],
};
const COMP_A_DAY_CHOICES: Record = {
mon: ["Chest + Triceps", "Glutes + Abductors"],
tue: ["Lats + Upper Back + Biceps", "Quads + Hamstrings + Adductors"],
wed: ["Conditioning", "Conditioning"],
thu: ["Shoulders + Triceps", "Hamstrings + Quads"],
fri: ["Upper Back + Lats + Biceps", "Glutes + Hamstrings + Abductors"],
sat: ["Upper focus", "Lower focus"],
};
const defaultDay = (k: DayKey): DayConfig => ({
priority: 3,
compC: k === "sat" ? [] : (COMP_C_DAY_LIFTS[k].length ? [COMP_C_DAY_LIFTS[k][0]] : []),
compA_choice: "Upper",
bMode: "Strength Program",
// order excludes fixed cards (WARM UP at top, CORE FINISHER at bottom)
order:
k === "wed"
? ["Conditioning"]
: k === "sat"
? ["STREET LIFT", "STREET WORK", "Conditioning Circuit"]
: ["Comp C", "Comp A", "Comp B"],
});
const defaultWeek = (): WeekPlan => ({
mon: defaultDay("mon"),
tue: defaultDay("tue"),
wed: defaultDay("wed"),
thu: defaultDay("thu"),
fri: defaultDay("fri"),
sat: defaultDay("sat"),
});
const heat = (p: number) => `hsla(${220 - (p - 1) * 55}, 80%, 50%, 0.06)`;
function RowHeader({ title, onUp, onDown }: { title: string; onUp: () => void; onDown: () => void }) {
return (
);
}
export default function TrainingSplitPlannerStandalone() {
const [member, setMember] = useState("Member 001");
const [week, setWeek] = useState(defaultWeek);
const maxPriority = useMemo(() => Math.max(...Object.values(week).map(d => d.priority)), [week]);
const setDay = (k: DayKey, patch: Partial) => setWeek(prev => ({ ...prev, [k]: { ...prev[k], ...patch } }));
const move = (k: DayKey, id: string, dir: -1 | 1) => setWeek(prev => {
const d = prev[k];
const i = d.order.indexOf(id);
if (i < 0) return prev;
const j = Math.max(0, Math.min(d.order.length - 1, i + dir));
if (i === j) return prev;
const order = [...d.order];
const [it] = order.splice(i, 1);
order.splice(j, 0, it);
return { ...prev, [k]: { ...d, order } };
});
const exportImage = async () => {
const node = document.querySelector(".export-root") as HTMLElement | null;
if (!node) return;
const { width, height } = node.getBoundingClientRect();
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
svg.setAttribute("width", String(Math.ceil(width)));
svg.setAttribute("height", String(Math.ceil(height)));
const fo = document.createElementNS("http://www.w3.org/2000/svg", "foreignObject");
fo.setAttribute("x", "0");
fo.setAttribute("y", "0");
fo.setAttribute("width", "100%");
fo.setAttribute("height", "100%");
const clone = node.cloneNode(true) as HTMLElement;
// Ensure fonts and background render
clone.style.background = getComputedStyle(document.body).background || "#f2f2f2";
fo.appendChild(clone);
svg.appendChild(fo);
const svgData = new XMLSerializer().serializeToString(svg);
const img = new Image();
const blob = new Blob([svgData], { type: "image/svg+xml;charset=utf-8" });
const url = URL.createObjectURL(blob);
img.onload = () => {
const canvas = document.createElement("canvas");
canvas.width = Math.ceil(width);
canvas.height = Math.ceil(height);
const ctx = canvas.getContext("2d");
if (!ctx) return;
ctx.fillStyle = "#f2f2f2";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0);
URL.revokeObjectURL(url);
const a = document.createElement("a");
a.download = `${member.replace(/\s+/g, "_")}_training_split.png`;
a.href = canvas.toDataURL("image/png");
a.click();
};
img.src = url;
};
return (
{title}
Training Split Planner
Uniform text. Square cards. Export as image.
Member
setMember(e.target.value)} />
{DAYS.map(({key,label,focus})=>{
const d = week[key];
const cOptions = COMP_C_DAY_LIFTS[key] || [];
const [upLbl, loLbl] = COMP_A_DAY_CHOICES[key];
return (
{label}
{focus}
Importance
setDay(key,{priority: Number(e.target.value)})} />
{/* Fixed top: WARM UP */}
{(d.order || []).map((cellId, idx)=> (
WARM UP
Activation and Mobility
{/* movable card header */}
{cellId}
{key==='wed' && cellId==='Conditioning' && (
["Level 3","Level 2","Level 1"].map(opt=> (
))
)}
{key==='sat' && cellId==='STREET LIFT' && (
<>
);
}
Choose up to 2 lifts
{(COMP_C_DAY_LIFTS[key] || []).map(opt=>{
const selected = d.compC.includes(opt);
const cap = !selected && d.compC.length>=2;
return (
);
})}
>
)}
{key==='sat' && cellId==='STREET WORK' && Street-specific drills or flow. Configure later.
}
{key==='sat' && cellId==='Conditioning Circuit' && Add your circuit template here.
}
{key!=='wed' && key!=='sat' && cellId==='Comp C' && (
(COMP_C_DAY_LIFTS[key] || []).map(opt=> (