antri_cli 1.57.35 → 1.57.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -247,6 +247,18 @@ export class ArtifactManager {
247
247
  if (isSamplePortfolio || (isPortfolioRequest && html.length < 500)) {
248
248
  return this.generateRichPortfolioHtml(title || 'Developer Portfolio');
249
249
  }
250
+ const isWorkoutRequest = lower.includes('workout') || lower.includes('fitness') || lower.includes('exercise') || lower.includes('gym') || lower.includes('training plan');
251
+ if (isWorkoutRequest && (lower.includes('sample') || html.length < 500)) {
252
+ return this.generateRichWorkoutHtml(title || 'Personalized Workout & Conditioning Plan');
253
+ }
254
+ const isDietRequest = lower.includes('diet') || lower.includes('meal plan') || lower.includes('nutrition') || lower.includes('calorie');
255
+ if (isDietRequest && (lower.includes('sample') || html.length < 500)) {
256
+ return this.generateRichDietHtml(title || 'Targeted Nutrition & Meal Plan');
257
+ }
258
+ const isStudyRequest = lower.includes('study plan') || lower.includes('learning roadmap') || lower.includes('curriculum');
259
+ if (isStudyRequest && (lower.includes('sample') || html.length < 500)) {
260
+ return this.generateRichStudyRoadmapHtml(title || 'Curriculum & Learning Roadmap');
261
+ }
250
262
  const isSampleTodo = (lower.includes('todo') || lower.includes('task')) && (lower.includes('sample') || html.length < 400);
251
263
  if (isSampleTodo) {
252
264
  return this.generateRichTodoHtml(title || 'Task & Project Manager');
@@ -949,6 +961,626 @@ export class ArtifactManager {
949
961
  </body>
950
962
  </html>`;
951
963
  }
964
+ generateRichWorkoutHtml(title, _rawPlan) {
965
+ return `<!DOCTYPE html>
966
+ <html lang="en">
967
+ <head>
968
+ <meta charset="UTF-8">
969
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
970
+ <title>${title} · Interactive Fitness & Workout Planner</title>
971
+ <link rel="preconnect" href="https://fonts.googleapis.com">
972
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
973
+ <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;600;700&display=swap" rel="stylesheet">
974
+ <script src="https://cdn.tailwindcss.com"></script>
975
+ <script src="https://unpkg.com/lucide@latest"></script>
976
+ <script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.3/dist/confetti.browser.min.js"></script>
977
+ <style>
978
+ body { font-family: 'Plus Jakarta Sans', sans-serif; }
979
+ .font-mono { font-family: 'JetBrains Mono', monospace; }
980
+ .glass-card {
981
+ background: rgba(15, 23, 42, 0.75);
982
+ backdrop-filter: blur(16px);
983
+ border: 1px solid rgba(255, 255, 255, 0.08);
984
+ }
985
+ .active-day-tab {
986
+ background: linear-gradient(135deg, #10b981, #06b6d4);
987
+ color: #ffffff !important;
988
+ box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3);
989
+ }
990
+ @keyframes pulse-ring {
991
+ 0% { transform: scale(0.95); opacity: 0.8; }
992
+ 50% { transform: scale(1.05); opacity: 1; }
993
+ 100% { transform: scale(0.95); opacity: 0.8; }
994
+ }
995
+ .timer-pulsing { animation: pulse-ring 2s infinite ease-in-out; }
996
+ </style>
997
+ </head>
998
+ <body class="bg-slate-950 text-slate-100 min-h-screen p-4 sm:p-6 md:p-10 antialiased selection:bg-emerald-500 selection:text-white">
999
+ <div class="max-w-5xl mx-auto space-y-6">
1000
+
1001
+ <!-- Top Header -->
1002
+ <div class="glass-card rounded-2xl p-6 flex flex-col md:flex-row items-start md:items-center justify-between gap-4">
1003
+ <div>
1004
+ <div class="flex items-center gap-2 mb-1">
1005
+ <span class="px-2.5 py-0.5 rounded-full bg-emerald-500/10 text-emerald-400 text-xs font-semibold uppercase tracking-wider flex items-center gap-1">
1006
+ <i data-lucide="dumbbell" class="w-3.5 h-3.5"></i> Pro Fitness Engine
1007
+ </span>
1008
+ <span class="px-2.5 py-0.5 rounded-full bg-cyan-500/10 text-cyan-400 text-xs font-semibold">4-Day Split Routine</span>
1009
+ </div>
1010
+ <h1 class="text-2xl sm:text-3xl font-extrabold text-white tracking-tight">${title}</h1>
1011
+ <p class="text-xs sm:text-sm text-slate-400 mt-1">Structured progressive overload, set tracking, audio rest timers & recovery metrics.</p>
1012
+ </div>
1013
+
1014
+ <!-- Quick Metrics -->
1015
+ <div class="flex items-center gap-3 bg-slate-900/90 p-3 rounded-xl border border-slate-800">
1016
+ <div class="text-center px-2">
1017
+ <div class="text-xs text-slate-400 font-medium">Completed</div>
1018
+ <div class="text-lg font-bold text-emerald-400 font-mono" id="stat-completed-sets">0 / 16</div>
1019
+ </div>
1020
+ <div class="w-px h-8 bg-slate-800"></div>
1021
+ <div class="text-center px-2">
1022
+ <div class="text-xs text-slate-400 font-medium">Rest Timer</div>
1023
+ <div class="text-lg font-bold text-cyan-400 font-mono" id="header-timer">01:00</div>
1024
+ </div>
1025
+ </div>
1026
+ </div>
1027
+
1028
+ <!-- Day Navigation Split Tabs -->
1029
+ <div class="grid grid-cols-2 sm:grid-cols-4 gap-2 sm:gap-3" id="day-tabs">
1030
+ <button onclick="switchDay(1)" id="day-btn-1" class="day-tab active-day-tab p-3.5 rounded-xl font-bold text-xs sm:text-sm flex flex-col items-center gap-1 transition-all">
1031
+ <span class="text-[10px] uppercase tracking-wider opacity-80">Day 1</span>
1032
+ <span>Push & Chest</span>
1033
+ </button>
1034
+ <button onclick="switchDay(2)" id="day-btn-2" class="day-tab glass-card hover:bg-slate-800/80 text-slate-300 p-3.5 rounded-xl font-bold text-xs sm:text-sm flex flex-col items-center gap-1 transition-all">
1035
+ <span class="text-[10px] uppercase tracking-wider opacity-80">Day 2</span>
1036
+ <span>Legs & Quads</span>
1037
+ </button>
1038
+ <button onclick="switchDay(3)" id="day-btn-3" class="day-tab glass-card hover:bg-slate-800/80 text-slate-300 p-3.5 rounded-xl font-bold text-xs sm:text-sm flex flex-col items-center gap-1 transition-all">
1039
+ <span class="text-[10px] uppercase tracking-wider opacity-80">Day 3</span>
1040
+ <span>Pull & Back</span>
1041
+ </button>
1042
+ <button onclick="switchDay(4)" id="day-btn-4" class="day-tab glass-card hover:bg-slate-800/80 text-slate-300 p-3.5 rounded-xl font-bold text-xs sm:text-sm flex flex-col items-center gap-1 transition-all">
1043
+ <span class="text-[10px] uppercase tracking-wider opacity-80">Day 4</span>
1044
+ <span>Core & HIIT</span>
1045
+ </button>
1046
+ </div>
1047
+
1048
+ <!-- Main Workout View Grid -->
1049
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
1050
+
1051
+ <!-- Exercises List (Left 2 cols) -->
1052
+ <div class="lg:col-span-2 space-y-4">
1053
+
1054
+ <!-- Warmup Card -->
1055
+ <div class="glass-card rounded-2xl p-5 border-l-4 border-l-emerald-500">
1056
+ <div class="flex items-center justify-between mb-2">
1057
+ <h3 class="text-sm font-bold text-emerald-400 flex items-center gap-2">
1058
+ <i data-lucide="flame" class="w-4 h-4"></i> Dynamic Warmup (5-8 Mins)
1059
+ </h3>
1060
+ <span class="text-xs text-slate-400">Mobility & Activation</span>
1061
+ </div>
1062
+ <div class="text-xs text-slate-300 grid grid-cols-1 sm:grid-cols-3 gap-2 mt-2">
1063
+ <div class="bg-slate-900/60 p-2.5 rounded-lg border border-slate-800">• Arm Circles & Band Pull-Aparts (2x15)</div>
1064
+ <div class="bg-slate-900/60 p-2.5 rounded-lg border border-slate-800">• World's Greatest Stretch (5/side)</div>
1065
+ <div class="bg-slate-900/60 p-2.5 rounded-lg border border-slate-800">• Light Cat-Cow & Scapular Pushups</div>
1066
+ </div>
1067
+ </div>
1068
+
1069
+ <!-- Exercises Container -->
1070
+ <div class="space-y-4" id="exercises-container">
1071
+ <!-- Populated by JavaScript -->
1072
+ </div>
1073
+
1074
+ </div>
1075
+
1076
+ <!-- Sidebar: Interactive Rest Timer, Hydration & Notes -->
1077
+ <div class="space-y-4">
1078
+
1079
+ <!-- Rest Timer Card -->
1080
+ <div class="glass-card rounded-2xl p-5 text-center space-y-4">
1081
+ <div class="flex items-center justify-between">
1082
+ <h3 class="text-xs font-bold uppercase tracking-wider text-slate-400 flex items-center gap-1.5">
1083
+ <i data-lucide="timer" class="w-4 h-4 text-cyan-400"></i> Rest Interval Timer
1084
+ </h3>
1085
+ <button onclick="toggleAudio()" id="btn-audio" class="text-xs text-slate-400 hover:text-white flex items-center gap-1">
1086
+ <i data-lucide="volume-2" class="w-3.5 h-3.5 text-emerald-400"></i> Sound ON
1087
+ </button>
1088
+ </div>
1089
+
1090
+ <!-- Circular Countdown Display -->
1091
+ <div class="relative w-36 h-36 mx-auto flex items-center justify-center">
1092
+ <svg class="w-full h-full transform -rotate-90" viewBox="0 0 100 100">
1093
+ <circle cx="50" cy="50" r="42" stroke="currentColor" stroke-width="6" class="text-slate-800 fill-none" />
1094
+ <circle id="timer-svg-circle" cx="50" cy="50" r="42" stroke="currentColor" stroke-width="6" stroke-dasharray="264" stroke-dashoffset="0" stroke-linecap="round" class="text-cyan-400 fill-none transition-all duration-500" />
1095
+ </svg>
1096
+ <div class="absolute flex flex-col items-center">
1097
+ <span class="text-3xl font-extrabold text-white font-mono tracking-tight" id="timer-display">60</span>
1098
+ <span class="text-[10px] text-slate-400 uppercase tracking-wider">Seconds</span>
1099
+ </div>
1100
+ </div>
1101
+
1102
+ <!-- Timer Controls -->
1103
+ <div class="flex items-center justify-center gap-2">
1104
+ <button onclick="startTimer()" class="px-4 py-2 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-white text-xs font-bold flex items-center gap-1.5 shadow-lg shadow-emerald-600/20 transition-all">
1105
+ <i data-lucide="play" class="w-3.5 h-3.5"></i> Start
1106
+ </button>
1107
+ <button onclick="pauseTimer()" class="px-3 py-2 rounded-xl bg-slate-800 hover:bg-slate-700 text-slate-200 text-xs font-semibold transition-all">
1108
+ <i data-lucide="pause" class="w-3.5 h-3.5"></i> Pause
1109
+ </button>
1110
+ <button onclick="resetTimer()" class="px-3 py-2 rounded-xl bg-slate-800 hover:bg-slate-700 text-slate-200 text-xs font-semibold transition-all">
1111
+ <i data-lucide="rotate-ccw" class="w-3.5 h-3.5"></i> Reset
1112
+ </button>
1113
+ </div>
1114
+
1115
+ <!-- Preset Intervals -->
1116
+ <div class="grid grid-cols-4 gap-1.5 pt-2 border-t border-slate-800/80">
1117
+ <button onclick="setTimerDuration(30)" class="py-1 rounded bg-slate-900 hover:bg-slate-800 text-[11px] font-mono text-slate-300 border border-slate-800">30s</button>
1118
+ <button onclick="setTimerDuration(60)" class="py-1 rounded bg-slate-900 hover:bg-slate-800 text-[11px] font-mono text-slate-300 border border-slate-800">60s</button>
1119
+ <button onclick="setTimerDuration(90)" class="py-1 rounded bg-slate-900 hover:bg-slate-800 text-[11px] font-mono text-slate-300 border border-slate-800">90s</button>
1120
+ <button onclick="setTimerDuration(120)" class="py-1 rounded bg-slate-900 hover:bg-slate-800 text-[11px] font-mono text-slate-300 border border-slate-800">120s</button>
1121
+ </div>
1122
+ </div>
1123
+
1124
+ <!-- Hydration Tracker -->
1125
+ <div class="glass-card rounded-2xl p-4 space-y-3">
1126
+ <div class="flex items-center justify-between text-xs font-bold text-slate-400 uppercase tracking-wider">
1127
+ <span class="flex items-center gap-1.5"><i data-lucide="droplet" class="w-4 h-4 text-cyan-400"></i> Water Intake</span>
1128
+ <span class="font-mono text-cyan-400" id="water-count">0 / 8 Glasses</span>
1129
+ </div>
1130
+ <div class="grid grid-cols-8 gap-1.5" id="water-grid">
1131
+ <!-- 8 glasses -->
1132
+ </div>
1133
+ </div>
1134
+
1135
+ <!-- Training Notes & Intensity -->
1136
+ <div class="glass-card rounded-2xl p-4 space-y-2">
1137
+ <h3 class="text-xs font-bold text-slate-400 uppercase tracking-wider flex items-center gap-1.5">
1138
+ <i data-lucide="sparkles" class="w-4 h-4 text-amber-400"></i> Form & Intensity Cues
1139
+ </h3>
1140
+ <ul class="text-xs text-slate-300 space-y-1.5 list-disc list-inside">
1141
+ <li>Aim for <strong>RPE 7.5 - 8.5</strong> on working sets (1-2 reps in reserve).</li>
1142
+ <li>Maintain a 2-second controlled eccentric (lowering) tempo.</li>
1143
+ <li>Breathe out on exertion; keep core braced throughout.</li>
1144
+ </ul>
1145
+ </div>
1146
+
1147
+ </div>
1148
+
1149
+ </div>
1150
+
1151
+ </div>
1152
+
1153
+ <script>
1154
+ const workoutData = {
1155
+ 1: {
1156
+ title: "Day 1: Upper Body / Push & Chest Specialization",
1157
+ focus: "Chest, Anterior Deltoids, Triceps & Core",
1158
+ exercises: [
1159
+ { name: "Barbell / Dumbbell Bench Press", target: "Chest / Pectorals", sets: 4, reps: "8-10 reps", rpe: "RPE 8", rest: 90 },
1160
+ { name: "Incline Dumbbell Chest Press", target: "Upper Chest", sets: 3, reps: "10-12 reps", rpe: "RPE 8", rest: 75 },
1161
+ { name: "Standing Overhead Overhead Press (OHP)", target: "Shoulders", sets: 3, reps: "8-10 reps", rpe: "RPE 8", rest: 90 },
1162
+ { name: "Dumbbell Lateral Raises", target: "Side Delts", sets: 4, reps: "12-15 reps", rpe: "RPE 9", rest: 60 },
1163
+ { name: "Cable Tricep Rope Pushdowns", target: "Triceps", sets: 3, reps: "12-15 reps", rpe: "RPE 8.5", rest: 60 }
1164
+ ]
1165
+ },
1166
+ 2: {
1167
+ title: "Day 2: Lower Body Power & Quad/Hamstring Focus",
1168
+ focus: "Quadriceps, Hamstrings, Glutes & Calves",
1169
+ exercises: [
1170
+ { name: "Barbell Back Squats / Goblet Squats", target: "Quads & Glutes", sets: 4, reps: "6-8 reps", rpe: "RPE 8", rest: 120 },
1171
+ { name: "Romanian Deadlifts (RDL)", target: "Hamstrings & Glutes", sets: 3, reps: "8-10 reps", rpe: "RPE 8", rest: 90 },
1172
+ { name: "Walking Dumbbell Lunges", target: "Legs & Stability", sets: 3, reps: "10/leg", rpe: "RPE 8", rest: 75 },
1173
+ { name: "Leg Extensions (Superset w/ Curls)", target: "Quad Isolation", sets: 3, reps: "12-15 reps", rpe: "RPE 9", rest: 60 },
1174
+ { name: "Standing Calf Raises", target: "Calves", sets: 4, reps: "15-20 reps", rpe: "RPE 9", rest: 45 }
1175
+ ]
1176
+ },
1177
+ 3: {
1178
+ title: "Day 3: Pull, Lats & Posterior Chain",
1179
+ focus: "Lats, Upper Back, Rear Delts & Biceps",
1180
+ exercises: [
1181
+ { name: "Pull-Ups / Lat Pulldowns", target: "Lats & Width", sets: 4, reps: "8-10 reps", rpe: "RPE 8", rest: 90 },
1182
+ { name: "Bent-Over Barbell / Chest-Supported Rows", target: "Upper Back & Rhomboids", sets: 4, reps: "8-10 reps", rpe: "RPE 8", rest: 90 },
1183
+ { name: "Seated Cable Rows", target: "Mid-Back", sets: 3, reps: "10-12 reps", rpe: "RPE 8", rest: 75 },
1184
+ { name: "Face Pulls (External Rotation)", target: "Rear Delts & Rotator Cuff", sets: 4, reps: "15 reps", rpe: "RPE 8.5", rest: 60 },
1185
+ { name: "Incline Dumbbell Bicep Curls", target: "Biceps", sets: 3, reps: "10-12 reps", rpe: "RPE 8.5", rest: 60 }
1186
+ ]
1187
+ },
1188
+ 4: {
1189
+ title: "Day 4: Core, HIIT Intervals & Active Mobility",
1190
+ focus: "Core Stability, Cardiovascular Capacity & Flexibility",
1191
+ exercises: [
1192
+ { name: "Hanging Leg Raises / Knee Tucks", target: "Lower Abs & Hip Flexors", sets: 3, reps: "12-15 reps", rpe: "RPE 8", rest: 60 },
1193
+ { name: "Cable Woodchoppers", target: "Obliques & Rotational Core", sets: 3, reps: "12/side", rpe: "RPE 8", rest: 60 },
1194
+ { name: "Kettlebell Swings / Dumbbell Snatches", target: "Posterior Chain & Power", sets: 4, reps: "15 reps", rpe: "RPE 8.5", rest: 60 },
1195
+ { name: "HIIT Sprint Intervals (Bike/Row/Run)", target: "Cardiovascular Engine", sets: 6, reps: "30s Work / 30s Rest", rpe: "RPE 9", rest: 60 },
1196
+ { name: "Full-Body Mobility & Yoga Cooldown", target: "Recovery & Joints", sets: 1, reps: "10 mins", rpe: "RPE 2", rest: 0 }
1197
+ ]
1198
+ }
1199
+ };
1200
+
1201
+ let activeDay = 1;
1202
+ let timerTotal = 60;
1203
+ let timerRemaining = 60;
1204
+ let timerInterval = null;
1205
+ let isTimerRunning = false;
1206
+ let soundEnabled = true;
1207
+ let audioCtx = null;
1208
+ let completedState = {};
1209
+ let waterState = [false, false, false, false, false, false, false, false];
1210
+
1211
+ function initAudio() {
1212
+ if (!audioCtx) {
1213
+ audioCtx = new (window.AudioContext || window.webkitAudioContext)();
1214
+ }
1215
+ }
1216
+
1217
+ function playTick() {
1218
+ if (!soundEnabled) return;
1219
+ initAudio();
1220
+ try {
1221
+ const osc = audioCtx.createOscillator();
1222
+ const gain = audioCtx.createGain();
1223
+ osc.type = 'triangle';
1224
+ osc.frequency.setValueAtTime(480, audioCtx.currentTime);
1225
+ gain.gain.setValueAtTime(0.12, audioCtx.currentTime);
1226
+ gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.05);
1227
+ osc.connect(gain);
1228
+ gain.connect(audioCtx.destination);
1229
+ osc.start();
1230
+ osc.stop(audioCtx.currentTime + 0.05);
1231
+ } catch (_) {}
1232
+ }
1233
+
1234
+ function playBell() {
1235
+ if (!soundEnabled) return;
1236
+ initAudio();
1237
+ try {
1238
+ const osc = audioCtx.createOscillator();
1239
+ const gain = audioCtx.createGain();
1240
+ osc.type = 'sine';
1241
+ osc.frequency.setValueAtTime(880, audioCtx.currentTime);
1242
+ osc.frequency.exponentialRampToValueAtTime(440, audioCtx.currentTime + 0.8);
1243
+ gain.gain.setValueAtTime(0.3, audioCtx.currentTime);
1244
+ gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.8);
1245
+ osc.connect(gain);
1246
+ gain.connect(audioCtx.destination);
1247
+ osc.start();
1248
+ osc.stop(audioCtx.currentTime + 0.8);
1249
+ } catch (_) {}
1250
+ }
1251
+
1252
+ function toggleAudio() {
1253
+ soundEnabled = !soundEnabled;
1254
+ const btn = document.getElementById('btn-audio');
1255
+ if (btn) {
1256
+ btn.innerHTML = soundEnabled
1257
+ ? '<i data-lucide="volume-2" class="w-3.5 h-3.5 text-emerald-400"></i> Sound ON'
1258
+ : '<i data-lucide="volume-x" class="w-3.5 h-3.5 text-slate-500"></i> Muted';
1259
+ if (window.lucide) window.lucide.createIcons();
1260
+ }
1261
+ }
1262
+
1263
+ function switchDay(day) {
1264
+ activeDay = day;
1265
+ document.querySelectorAll('.day-tab').forEach((b, idx) => {
1266
+ if (idx + 1 === day) {
1267
+ b.className = 'day-tab active-day-tab p-3.5 rounded-xl font-bold text-xs sm:text-sm flex flex-col items-center gap-1 transition-all';
1268
+ } else {
1269
+ b.className = 'day-tab glass-card hover:bg-slate-800/80 text-slate-300 p-3.5 rounded-xl font-bold text-xs sm:text-sm flex flex-col items-center gap-1 transition-all';
1270
+ }
1271
+ });
1272
+ renderExercises();
1273
+ updateStats();
1274
+ }
1275
+
1276
+ function renderExercises() {
1277
+ const container = document.getElementById('exercises-container');
1278
+ const data = workoutData[activeDay];
1279
+ if (!container || !data) return;
1280
+
1281
+ container.innerHTML = data.exercises.map((ex, exIdx) => {
1282
+ const exKey = \`d\${activeDay}_e\${exIdx}\`;
1283
+ const setsHtml = Array.from({ length: ex.sets }).map((_, sIdx) => {
1284
+ const setKey = \`\${exKey}_s\${sIdx}\`;
1285
+ const isDone = completedState[setKey] || false;
1286
+ return \`
1287
+ <div class="flex items-center gap-2 bg-slate-900/80 px-3 py-2 rounded-lg border border-slate-800/80">
1288
+ <input type="checkbox" \${isDone ? 'checked' : ''} onchange="toggleSet('\${setKey}', \${ex.rest})" class="w-4 h-4 rounded text-emerald-500 bg-slate-800 border-slate-700 accent-emerald-500 cursor-pointer">
1289
+ <span class="text-xs font-mono \${isDone ? 'line-through text-slate-500' : 'text-slate-200'}">Set \${sIdx + 1}</span>
1290
+ </div>
1291
+ \`;
1292
+ }).join('');
1293
+
1294
+ return \`
1295
+ <div class="glass-card rounded-2xl p-4 sm:p-5 space-y-3 hover:border-slate-700 transition-all">
1296
+ <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
1297
+ <div>
1298
+ <h4 class="text-base font-bold text-white">\${ex.name}</h4>
1299
+ <div class="flex items-center gap-2 mt-1">
1300
+ <span class="px-2 py-0.5 rounded bg-emerald-500/10 text-emerald-400 text-[11px] font-semibold">\${ex.target}</span>
1301
+ <span class="text-xs text-slate-400 font-mono">\${ex.sets} Sets × \${ex.reps}</span>
1302
+ </div>
1303
+ </div>
1304
+ <div class="flex items-center gap-2">
1305
+ <span class="px-2 py-0.5 rounded bg-slate-800 text-cyan-400 text-xs font-mono font-semibold">\${ex.rpe}</span>
1306
+ <button onclick="setTimerDuration(\${ex.rest}); startTimer();" class="px-2.5 py-1 rounded bg-slate-800 hover:bg-slate-700 text-xs text-slate-300 font-mono flex items-center gap-1 transition-all">
1307
+ <i data-lucide="timer" class="w-3 h-3 text-cyan-400"></i> \${ex.rest}s Rest
1308
+ </button>
1309
+ </div>
1310
+ </div>
1311
+
1312
+ <!-- Set Checkboxes Grid -->
1313
+ <div class="grid grid-cols-2 sm:grid-cols-4 gap-2 pt-2 border-t border-slate-800/60">
1314
+ \${setsHtml}
1315
+ </div>
1316
+ </div>
1317
+ \`;
1318
+ }).join('');
1319
+
1320
+ if (window.lucide) window.lucide.createIcons();
1321
+ }
1322
+
1323
+ function toggleSet(setKey, restSeconds) {
1324
+ completedState[setKey] = !completedState[setKey];
1325
+ playTick();
1326
+ if (completedState[setKey]) {
1327
+ setTimerDuration(restSeconds || 60);
1328
+ startTimer();
1329
+ }
1330
+ saveState();
1331
+ updateStats();
1332
+ checkDayCompletion();
1333
+ }
1334
+
1335
+ function updateStats() {
1336
+ const data = workoutData[activeDay];
1337
+ let totalSets = 0;
1338
+ let doneSets = 0;
1339
+
1340
+ data.exercises.forEach((ex, exIdx) => {
1341
+ totalSets += ex.sets;
1342
+ for (let s = 0; s < ex.sets; s++) {
1343
+ if (completedState[\`d\${activeDay}_e\${exIdx}_s\${s}\`]) {
1344
+ doneSets++;
1345
+ }
1346
+ }
1347
+ });
1348
+
1349
+ const statEl = document.getElementById('stat-completed-sets');
1350
+ if (statEl) statEl.textContent = \`\${doneSets} / \${totalSets}\`;
1351
+ }
1352
+
1353
+ function checkDayCompletion() {
1354
+ const data = workoutData[activeDay];
1355
+ let allDone = true;
1356
+ data.exercises.forEach((ex, exIdx) => {
1357
+ for (let s = 0; s < ex.sets; s++) {
1358
+ if (!completedState[\`d\${activeDay}_e\${exIdx}_s\${s}\`]) {
1359
+ allDone = false;
1360
+ }
1361
+ }
1362
+ });
1363
+
1364
+ if (allDone && window.confetti) {
1365
+ window.confetti({ particleCount: 120, spread: 80, origin: { y: 0.6 } });
1366
+ }
1367
+ }
1368
+
1369
+ function setTimerDuration(sec) {
1370
+ pauseTimer();
1371
+ timerTotal = sec;
1372
+ timerRemaining = sec;
1373
+ updateTimerUI();
1374
+ }
1375
+
1376
+ function startTimer() {
1377
+ if (isTimerRunning) return;
1378
+ isTimerRunning = true;
1379
+ clearInterval(timerInterval);
1380
+ timerInterval = setInterval(() => {
1381
+ if (timerRemaining > 0) {
1382
+ timerRemaining--;
1383
+ updateTimerUI();
1384
+ } else {
1385
+ pauseTimer();
1386
+ playBell();
1387
+ if (window.confetti) window.confetti({ particleCount: 40, spread: 50 });
1388
+ }
1389
+ }, 1000);
1390
+ }
1391
+
1392
+ function pauseTimer() {
1393
+ isTimerRunning = false;
1394
+ clearInterval(timerInterval);
1395
+ }
1396
+
1397
+ function resetTimer() {
1398
+ pauseTimer();
1399
+ timerRemaining = timerTotal;
1400
+ updateTimerUI();
1401
+ }
1402
+
1403
+ function updateTimerUI() {
1404
+ const display = document.getElementById('timer-display');
1405
+ const headerDisplay = document.getElementById('header-timer');
1406
+ const circle = document.getElementById('timer-svg-circle');
1407
+
1408
+ const mins = Math.floor(timerRemaining / 60);
1409
+ const secs = timerRemaining % 60;
1410
+ const formatted = String(mins).padStart(2, '0') + ':' + String(secs).padStart(2, '0');
1411
+
1412
+ if (display) display.textContent = timerRemaining;
1413
+ if (headerDisplay) headerDisplay.textContent = formatted;
1414
+
1415
+ if (circle && timerTotal > 0) {
1416
+ const offset = 264 * (1 - (timerRemaining / timerTotal));
1417
+ circle.style.strokeDashoffset = offset;
1418
+ }
1419
+ }
1420
+
1421
+ function renderWater() {
1422
+ const grid = document.getElementById('water-grid');
1423
+ const countEl = document.getElementById('water-count');
1424
+ if (!grid) return;
1425
+
1426
+ const done = waterState.filter(Boolean).length;
1427
+ if (countEl) countEl.textContent = \`\${done} / 8 Glasses\`;
1428
+
1429
+ grid.innerHTML = waterState.map((active, idx) => \`
1430
+ <button onclick="toggleWater(\${idx})" class="p-2 rounded-lg text-center transition-all \${active ? 'bg-cyan-500 text-white shadow-md shadow-cyan-500/20' : 'bg-slate-900 text-slate-500 hover:text-slate-300'}">
1431
+ <i data-lucide="droplet" class="w-4 h-4 mx-auto"></i>
1432
+ </button>
1433
+ \`).join('');
1434
+
1435
+ if (window.lucide) window.lucide.createIcons();
1436
+ }
1437
+
1438
+ function toggleWater(idx) {
1439
+ waterState[idx] = !waterState[idx];
1440
+ playTick();
1441
+ saveState();
1442
+ renderWater();
1443
+ }
1444
+
1445
+ function saveState() {
1446
+ try {
1447
+ localStorage.setItem('antri_workout_completed_sets', JSON.stringify(completedState));
1448
+ localStorage.setItem('antri_workout_water', JSON.stringify(waterState));
1449
+ } catch (_) {}
1450
+ }
1451
+
1452
+ function loadState() {
1453
+ try {
1454
+ const savedSets = localStorage.getItem('antri_workout_completed_sets');
1455
+ if (savedSets) completedState = JSON.parse(savedSets);
1456
+ const savedWater = localStorage.getItem('antri_workout_water');
1457
+ if (savedWater) waterState = JSON.parse(savedWater);
1458
+ } catch (_) {}
1459
+ }
1460
+
1461
+ document.addEventListener('DOMContentLoaded', () => {
1462
+ loadState();
1463
+ renderExercises();
1464
+ renderWater();
1465
+ updateStats();
1466
+ updateTimerUI();
1467
+ if (window.lucide) window.lucide.createIcons();
1468
+ });
1469
+ </script>
1470
+ </body>
1471
+ </html>`;
1472
+ }
1473
+ generateRichDietHtml(title, _rawPlan) {
1474
+ return `<!DOCTYPE html>
1475
+ <html lang="en">
1476
+ <head>
1477
+ <meta charset="UTF-8">
1478
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1479
+ <title>${title} · Targeted Nutrition & Meal Planner</title>
1480
+ <link rel="preconnect" href="https://fonts.googleapis.com">
1481
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
1482
+ <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;600;700&display=swap" rel="stylesheet">
1483
+ <script src="https://cdn.tailwindcss.com"></script>
1484
+ <script src="https://unpkg.com/lucide@latest"></script>
1485
+ <script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.3/dist/confetti.browser.min.js"></script>
1486
+ </head>
1487
+ <body class="bg-slate-950 text-slate-100 min-h-screen p-4 sm:p-6 md:p-10 antialiased font-sans">
1488
+ <div class="max-w-5xl mx-auto space-y-6">
1489
+ <div class="bg-slate-900/90 border border-slate-800 rounded-2xl p-6 flex flex-col md:flex-row items-start md:items-center justify-between gap-4">
1490
+ <div>
1491
+ <span class="px-2.5 py-0.5 rounded-full bg-emerald-500/10 text-emerald-400 text-xs font-semibold uppercase tracking-wider">Nutrition & Macro Engine</span>
1492
+ <h1 class="text-2xl sm:text-3xl font-extrabold text-white tracking-tight mt-1">${title}</h1>
1493
+ <p class="text-sm text-slate-400 mt-1">Calorie targets, macro distribution, whole-food meal timing & hydration.</p>
1494
+ </div>
1495
+ <div class="grid grid-cols-4 gap-2 bg-slate-950 p-3 rounded-xl border border-slate-800 text-center font-mono">
1496
+ <div class="px-2"><div class="text-[10px] text-slate-400">Calories</div><div class="text-sm font-bold text-white">2,400</div></div>
1497
+ <div class="px-2"><div class="text-[10px] text-slate-400">Protein</div><div class="text-sm font-bold text-emerald-400">180g</div></div>
1498
+ <div class="px-2"><div class="text-[10px] text-slate-400">Carbs</div><div class="text-sm font-bold text-cyan-400">260g</div></div>
1499
+ <div class="px-2"><div class="text-[10px] text-slate-400">Fats</div><div class="text-sm font-bold text-amber-400">65g</div></div>
1500
+ </div>
1501
+ </div>
1502
+
1503
+ <!-- Daily Meals Schedule -->
1504
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
1505
+ <div class="bg-slate-900 border border-slate-800 rounded-2xl p-5 space-y-2">
1506
+ <div class="flex items-center justify-between"><h3 class="font-bold text-emerald-400 text-sm">🍳 Meal 1: Breakfast (7:30 AM)</h3><span class="text-xs font-mono text-slate-400">550 kcal · 40g P</span></div>
1507
+ <p class="text-xs text-slate-300">4 Whole Eggs / Egg Whites scramble, 1 cup Rolled Oats with berries & 1 tbsp Chia Seeds.</p>
1508
+ </div>
1509
+ <div class="bg-slate-900 border border-slate-800 rounded-2xl p-5 space-y-2">
1510
+ <div class="flex items-center justify-between"><h3 class="font-bold text-cyan-400 text-sm">🥗 Meal 2: Lunch (12:30 PM)</h3><span class="text-xs font-mono text-slate-400">650 kcal · 45g P</span></div>
1511
+ <p class="text-xs text-slate-300">Grilled Chicken Breast (200g), Jasmine Rice (1.5 cups), Steamed Broccoli & 1 tbsp Extra Virgin Olive Oil.</p>
1512
+ </div>
1513
+ <div class="bg-slate-900 border border-slate-800 rounded-2xl p-5 space-y-2">
1514
+ <div class="flex items-center justify-between"><h3 class="font-bold text-amber-400 text-sm">⚡ Meal 3: Pre/Post-Workout (4:30 PM)</h3><span class="text-xs font-mono text-slate-400">450 kcal · 35g P</span></div>
1515
+ <p class="text-xs text-slate-300">Whey Isolate Shake with 1 Banana, 1 Rice Cake with 1 tbsp Natural Peanut Butter.</p>
1516
+ </div>
1517
+ <div class="bg-slate-900 border border-slate-800 rounded-2xl p-5 space-y-2">
1518
+ <div class="flex items-center justify-between"><h3 class="font-bold text-purple-400 text-sm">🐟 Meal 4: Dinner (8:00 PM)</h3><span class="text-xs font-mono text-slate-400">600 kcal · 45g P</span></div>
1519
+ <p class="text-xs text-slate-300">Wild Salmon / Lean Sirloin Steak (180g), Roasted Sweet Potatoes (200g) & Mixed Green Salad with Avocado.</p>
1520
+ </div>
1521
+ </div>
1522
+ </div>
1523
+ <script>
1524
+ document.addEventListener('DOMContentLoaded', () => { if (window.lucide) window.lucide.createIcons(); });
1525
+ </script>
1526
+ </body>
1527
+ </html>`;
1528
+ }
1529
+ generateRichStudyRoadmapHtml(title, _rawPlan) {
1530
+ return `<!DOCTYPE html>
1531
+ <html lang="en">
1532
+ <head>
1533
+ <meta charset="UTF-8">
1534
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1535
+ <title>${title} · Study Roadmap & Curriculum</title>
1536
+ <link rel="preconnect" href="https://fonts.googleapis.com">
1537
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
1538
+ <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;600;700&display=swap" rel="stylesheet">
1539
+ <script src="https://cdn.tailwindcss.com"></script>
1540
+ <script src="https://unpkg.com/lucide@latest"></script>
1541
+ <script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.3/dist/confetti.browser.min.js"></script>
1542
+ </head>
1543
+ <body class="bg-slate-950 text-slate-100 min-h-screen p-4 sm:p-6 md:p-10 antialiased font-sans">
1544
+ <div class="max-w-4xl mx-auto space-y-6">
1545
+ <div class="bg-slate-900/90 border border-slate-800 rounded-2xl p-6">
1546
+ <span class="px-2.5 py-0.5 rounded-full bg-indigo-500/10 text-indigo-400 text-xs font-semibold uppercase tracking-wider">Curriculum Roadmap</span>
1547
+ <h1 class="text-2xl sm:text-3xl font-extrabold text-white tracking-tight mt-1">${title}</h1>
1548
+ <p class="text-sm text-slate-400 mt-1">Multi-week structured study milestones, core concepts & revision checklists.</p>
1549
+ </div>
1550
+ <div class="space-y-4">
1551
+ <div class="bg-slate-900 border border-slate-800 rounded-2xl p-5 space-y-3">
1552
+ <h3 class="font-bold text-white text-base">Phase 1: Foundations & Core Principles</h3>
1553
+ <p class="text-xs text-slate-300">Master fundamental paradigms, mental models, and environment setup.</p>
1554
+ </div>
1555
+ <div class="bg-slate-900 border border-slate-800 rounded-2xl p-5 space-y-3">
1556
+ <h3 class="font-bold text-white text-base">Phase 2: Deep Dive & Practical Applications</h3>
1557
+ <p class="text-xs text-slate-300">Hands-on problem sets, building small projects, and analyzing trade-offs.</p>
1558
+ </div>
1559
+ <div class="bg-slate-900 border border-slate-800 rounded-2xl p-5 space-y-3">
1560
+ <h3 class="font-bold text-white text-base">Phase 3: Advanced Mastery & Real-World Projects</h3>
1561
+ <p class="text-xs text-slate-300">Architecting complete systems, edge case hardening, and comprehensive review.</p>
1562
+ </div>
1563
+ </div>
1564
+ </div>
1565
+ <script>
1566
+ document.addEventListener('DOMContentLoaded', () => { if (window.lucide) window.lucide.createIcons(); });
1567
+ </script>
1568
+ </body>
1569
+ </html>`;
1570
+ }
1571
+ generatePlanSpaHtml(title, prompt, rawPlan) {
1572
+ const p = (prompt + ' ' + title).toLowerCase();
1573
+ if (/\b(diet|meal|nutrition|food|macro|calorie|recipe|keto|protein shake|weight loss diet|cutting diet|bulking diet)\b/i.test(p)) {
1574
+ return this.generateRichDietHtml(title, rawPlan);
1575
+ }
1576
+ if (/\b(workout|fitness|exercise|gym|training|sport|hypertrophy|crossfit|running|cardio|stretching|strength|calisthenics|push pull legs|upper lower)\b/i.test(p) || (/\b(muscle)\b/i.test(p) && !/\b(diet|meal)\b/i.test(p))) {
1577
+ return this.generateRichWorkoutHtml(title, rawPlan);
1578
+ }
1579
+ if (/\b(study|learning|curriculum|course|roadmap|syllabus|exam|revision)\b/i.test(p)) {
1580
+ return this.generateRichStudyRoadmapHtml(title, rawPlan);
1581
+ }
1582
+ return this.generateRichTodoHtml(title);
1583
+ }
952
1584
  getArtifactHtml(artifact) {
953
1585
  if (artifact.type === 'html') {
954
1586
  return this.enhanceHtmlArtifact(artifact.content, artifact.title);