483 lines
22 KiB
HTML
483 lines
22 KiB
HTML
<!doctype html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||
<meta name="theme-color" content="#0f766e">
|
||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||
<title>Training</title>
|
||
<link rel="stylesheet" href="/shared/boehmi.css">
|
||
<script src="/shared/boehmi.js"></script>
|
||
<style>
|
||
:root {
|
||
color-scheme: dark light;
|
||
--ink: var(--bt-ink, #eef2f5);
|
||
--ink-soft: var(--bt-ink-soft, #cbd3da);
|
||
--muted: var(--bt-muted, #8f9aa5);
|
||
--line: var(--bt-line, #303842);
|
||
--line-soft: var(--bt-line-soft, #273039);
|
||
--paper: var(--bt-surface, #1d2228);
|
||
--paper-2: var(--bt-surface-2, #20262d);
|
||
--wash: var(--bt-bg, #111418);
|
||
--wash-2: var(--bt-bg-2, #171b20);
|
||
--accent: var(--bt-accent, #ff7a1a);
|
||
--accent-ink: var(--bt-accent-ink, #fff);
|
||
--accent-soft: var(--bt-accent-soft, rgba(255,122,26,.13));
|
||
--teal: var(--bt-teal, #0f766e);
|
||
--ok: var(--bt-ok, #2f9e68);
|
||
--danger: var(--bt-err, #e2554f);
|
||
--shadow: var(--bt-shadow, 0 8px 24px rgba(0,0,0,.22));
|
||
--radius: var(--bt-r-lg, 12px);
|
||
--radius-sm: var(--bt-r, 9px);
|
||
--appbar-h: var(--bt-appbar-h, 48px);
|
||
}
|
||
* { box-sizing: border-box; }
|
||
html, body { max-width: 100%; overflow-x: hidden; }
|
||
body { margin: 0; min-height: 100vh; background: var(--wash); color: var(--ink); }
|
||
button, input, select, textarea { font: inherit; }
|
||
button { cursor: pointer; }
|
||
button:disabled, select:disabled { cursor: default; opacity: .55; }
|
||
.hidden { display: none !important; }
|
||
|
||
.tracker-toolbar {
|
||
position: sticky; top: var(--appbar-h); z-index: 32;
|
||
display: flex; align-items: center; gap: 9px;
|
||
min-height: 52px; padding: 7px 12px;
|
||
background: color-mix(in srgb, var(--paper) 94%, transparent);
|
||
backdrop-filter: blur(14px); border-bottom: 1px solid var(--line);
|
||
}
|
||
.tracker-plan { min-width: 0; flex: 1; }
|
||
.tracker-plan .toolbar-label { display: block; color: var(--muted); font-size: 10px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; }
|
||
.tracker-plan strong { display: block; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-size: 13px; color: var(--ink); }
|
||
.toolbar-actions { display: flex; align-items: center; gap: 7px; }
|
||
.toolbar-btn, .icon-btn {
|
||
min-height: 36px; border: 1px solid var(--line); border-radius: var(--radius-sm);
|
||
background: var(--paper-2); color: var(--ink); padding: 7px 10px; font-weight: 700;
|
||
}
|
||
.toolbar-btn .wide-label { display: none; }
|
||
.save-pill {
|
||
min-height: 25px; display: inline-flex; align-items: center; border-radius: 999px;
|
||
padding: 4px 8px; background: var(--paper-2); border: 1px solid var(--line);
|
||
color: var(--muted); font-size: 10px; font-weight: 750; white-space: nowrap;
|
||
}
|
||
.save-pill.saving { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 48%, var(--line)); }
|
||
.save-pill.saved { color: var(--ok); }
|
||
.save-pill.error { color: var(--danger); }
|
||
|
||
.bottom-nav {
|
||
position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
|
||
display: grid; grid-template-columns: repeat(2,1fr);
|
||
padding: 7px 8px max(7px, env(safe-area-inset-bottom));
|
||
background: color-mix(in srgb, var(--paper) 96%, transparent);
|
||
backdrop-filter: blur(14px); border-top: 1px solid var(--line);
|
||
}
|
||
.nav-btn { border: 0; background: transparent; color: var(--muted); border-radius: var(--radius-sm); padding: 7px 3px; font-size: 10px; font-weight: 750; }
|
||
.nav-btn span { display: block; font-size: 18px; margin-bottom: 1px; }
|
||
.nav-btn.active { color: var(--accent); background: var(--accent-soft); }
|
||
|
||
.app { width: min(960px, 100%); min-width: 0; max-width: 100%; margin: 0 auto; padding: 12px 10px 96px; }
|
||
.card {
|
||
min-width: 0; max-width: 100%;
|
||
background: var(--paper); border: 1px solid var(--line); border-radius: var(--radius);
|
||
box-shadow: var(--shadow); margin-bottom: 10px; overflow: hidden;
|
||
}
|
||
.card-pad { padding: 14px; }
|
||
.hero { padding: 15px; background: var(--paper); border-left: 3px solid var(--accent); }
|
||
.eyebrow { color: var(--accent); font-size: 10px; font-weight: 800; letter-spacing: .1em; text-transform: uppercase; }
|
||
h1, h2, h3, p { margin-top: 0; }
|
||
h1 { font-size: clamp(20px, 6vw, 29px); line-height: 1.1; margin-bottom: 7px; }
|
||
h2 { font-size: 17px; margin-bottom: 0; }
|
||
h3 { font-size: 14px; margin-bottom: 5px; }
|
||
.muted { color: var(--muted); }
|
||
.tiny { font-size: 12px; }
|
||
.row { display: flex; align-items: center; gap: 9px; }
|
||
.between { justify-content: space-between; }
|
||
.grow { flex: 1; min-width: 0; }
|
||
.day-strip { display: flex; gap: 7px; overflow-x: auto; scrollbar-width: none; padding: 2px 1px 8px; scroll-snap-type: x proximity; }
|
||
.day-strip::-webkit-scrollbar { display: none; }
|
||
.chip {
|
||
flex: 0 0 auto; scroll-snap-align: start; border: 1px solid var(--line);
|
||
background: var(--paper-2); color: var(--ink); border-radius: 999px;
|
||
padding: 8px 12px; font-size: 12px; font-weight: 750;
|
||
}
|
||
.chip.active { border-color: var(--accent); background: var(--accent); color: var(--accent-ink); }
|
||
.status-badge { display: inline-flex; align-items: center; min-height: 24px; padding: 3px 8px; border-radius: 999px; font-size: 10px; font-weight: 800; background: var(--paper-2); color: var(--muted); border: 1px solid var(--line); }
|
||
.section-head { display: flex; align-items: center; justify-content: space-between; gap: 9px; padding: 13px 14px 9px; }
|
||
.section-body { padding: 0 14px 14px; }
|
||
.list { display: grid; gap: 7px; }
|
||
.plain-item { padding: 10px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--paper-2); font-size: 13px; line-height: 1.4; }
|
||
.exercise { padding: 13px 14px; border-top: 1px solid var(--line-soft); }
|
||
.exercise:first-child { border-top: 0; }
|
||
.exercise-title { display: grid; grid-template-columns: minmax(0,1fr) minmax(180px,280px); gap: 10px; align-items: end; }
|
||
.exercise-name strong { display: block; font-size: 14px; }
|
||
.exercise-name small { display: block; margin-top: 3px; color: var(--muted); font-size: 11px; }
|
||
label.field { display: grid; gap: 5px; color: var(--muted); font-size: 10px; font-weight: 800; text-transform: uppercase; letter-spacing: .05em; margin: 0; }
|
||
select {
|
||
width: 100%; min-height: 39px; border: 1px solid var(--line); border-radius: var(--radius-sm);
|
||
background: var(--paper-2); color: var(--ink); padding: 8px 10px; outline: none;
|
||
}
|
||
select:focus { border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent); }
|
||
.progression-list { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 9px; }
|
||
.progression-pill { display: inline-flex; border: 1px solid var(--line); border-radius: 999px; padding: 3px 8px; color: var(--muted); background: var(--paper-2); font-size: 10px; font-weight: 750; }
|
||
.progression-pill.current { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
|
||
.plan-day { border-top: 1px solid var(--line); padding: 13px 14px; }
|
||
.plan-day:first-child { border-top: 0; }
|
||
.plan-day h3 { margin-bottom: 7px; }
|
||
.overview-line { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 9px; }
|
||
.empty { padding: 25px 16px; text-align: center; color: var(--muted); }
|
||
.error-card { border: 1px solid color-mix(in srgb, var(--danger) 55%, var(--line)); background: color-mix(in srgb, var(--danger) 13%, var(--paper)); color: var(--danger); padding: 13px; border-radius: var(--radius); }
|
||
|
||
.modal-backdrop { position: fixed; inset: 0; z-index: 80; background: rgba(0,0,0,.68); display: grid; align-items: end; }
|
||
.modal { width: min(760px,100%); max-height: 88vh; overflow: auto; margin: 0 auto; border-radius: 18px 18px 0 0; padding: 16px 12px max(16px, env(safe-area-inset-bottom)); background: var(--paper); border: 1px solid var(--line); box-shadow: var(--shadow); }
|
||
.plan-choice { display: block; width: 100%; text-align: left; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--paper-2); color: var(--ink); padding: 11px; margin-top: 8px; }
|
||
.plan-choice.active { border-color: var(--accent); box-shadow: inset 3px 0 0 var(--accent); }
|
||
.plan-choice strong, .plan-choice span { display: block; }
|
||
.plan-choice span { color: var(--muted); font-size: 11px; margin-top: 4px; }
|
||
|
||
@media (max-width: 520px) {
|
||
.exercise-title { grid-template-columns: minmax(0,1fr); }
|
||
}
|
||
@media (min-width: 560px) {
|
||
.app { padding-left: 14px; padding-right: 14px; }
|
||
.toolbar-btn .wide-label { display: inline; }
|
||
}
|
||
@media (min-width: 780px) {
|
||
.tracker-toolbar { padding-left: 16px; padding-right: 16px; }
|
||
.bottom-nav {
|
||
position: sticky; top: calc(var(--appbar-h) + 52px); z-index: 31;
|
||
display: flex; justify-content: center; gap: 3px; padding: 0 16px;
|
||
background: var(--wash); border-top: 0; border-bottom: 1px solid var(--line);
|
||
}
|
||
.nav-btn { border-radius: var(--radius-sm) var(--radius-sm) 0 0; padding: 9px 14px; font-size: 12px; border-bottom: 2px solid transparent; }
|
||
.nav-btn span { display: inline; font-size: 13px; margin: 0 5px 0 0; }
|
||
.nav-btn.active { background: var(--wash-2); border-bottom-color: var(--accent); }
|
||
.app { padding-top: 16px; padding-bottom: 70px; }
|
||
.modal-backdrop { align-items: center; padding: 24px; }
|
||
.modal { border-radius: var(--radius); }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body data-bt-title="Training" data-bt-icon="📱" data-base="__APP_BASE__">
|
||
<header class="tracker-toolbar">
|
||
<div class="tracker-plan">
|
||
<span class="toolbar-label">Aktiver Trainingsplan</span>
|
||
<strong id="headerPlan">Trainingsplan wird geladen</strong>
|
||
</div>
|
||
<div class="toolbar-actions">
|
||
<span class="save-pill" id="saveState">Bereit</span>
|
||
<button class="toolbar-btn" id="choosePlanBtn" aria-label="Trainingsplan wechseln" title="Trainingsplan wechseln">⇄ <span class="wide-label">Plan wechseln</span></button>
|
||
</div>
|
||
</header>
|
||
|
||
<nav class="bottom-nav" aria-label="Training-Navigation">
|
||
<button class="nav-btn active" data-view="day"><span>☰</span>Tag</button>
|
||
<button class="nav-btn" data-view="plan"><span>◎</span>Plan</button>
|
||
</nav>
|
||
|
||
<main class="app">
|
||
<div id="fatalError" class="error-card hidden"></div>
|
||
<section id="dayView"></section>
|
||
<section id="planView" class="hidden"></section>
|
||
</main>
|
||
|
||
<div id="planModal" class="modal-backdrop hidden" role="dialog" aria-modal="true">
|
||
<div class="modal">
|
||
<div class="row between">
|
||
<div>
|
||
<div class="eyebrow">Trainingsquelle</div>
|
||
<h2>Trainingsplan auswählen</h2>
|
||
</div>
|
||
<button class="icon-btn" id="closePlanModal" aria-label="Schließen">×</button>
|
||
</div>
|
||
<p class="muted tiny">Es werden nur Trainingspläne aus <code>data/trainingsplan/plans</code> angezeigt.</p>
|
||
<div id="planChoices"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
(() => {
|
||
'use strict';
|
||
const BASE = document.body.dataset.base || '';
|
||
const api = path => `${BASE}${path}`;
|
||
const state = {plans: [], selectedId: null, plan: null, dayNum: 1, view: 'day', saving: false};
|
||
const $ = sel => document.querySelector(sel);
|
||
const $$ = sel => [...document.querySelectorAll(sel)];
|
||
const esc = value => String(value ?? '').replace(/[&<>'"]/g, ch => ({'&':'&','<':'<','>':'>',"'":''','"':'"'}[ch]));
|
||
|
||
function plain(value) {
|
||
const div = document.createElement('div');
|
||
div.innerHTML = String(value || '');
|
||
return (div.textContent || '').trim();
|
||
}
|
||
|
||
function storageKey(planId = state.selectedId) {
|
||
return planId ? `trainingstracker.slim.${planId}` : '';
|
||
}
|
||
|
||
function persistUiState() {
|
||
const key = storageKey();
|
||
if (!key) return;
|
||
try { localStorage.setItem(key, JSON.stringify({view: state.view, dayNum: state.dayNum})); } catch (_) {}
|
||
}
|
||
|
||
function restoreUiState() {
|
||
const key = storageKey();
|
||
if (!key) return false;
|
||
try {
|
||
const saved = JSON.parse(localStorage.getItem(key) || 'null');
|
||
if (!saved || typeof saved !== 'object') return false;
|
||
if (['day','plan'].includes(saved.view)) state.view = saved.view;
|
||
const dayNum = Number(saved.dayNum);
|
||
if (state.plan?.config?.days?.some(day => Number(day.num) === dayNum)) state.dayNum = dayNum;
|
||
return true;
|
||
} catch (_) { return false; }
|
||
}
|
||
|
||
async function fetchJSON(url, options = {}) {
|
||
const response = await fetch(url, {headers: {'Content-Type':'application/json', ...(options.headers || {})}, ...options});
|
||
const payload = await response.json().catch(() => ({}));
|
||
if (!response.ok) throw new Error(payload.error || `HTTP ${response.status}`);
|
||
return payload;
|
||
}
|
||
|
||
function setSaveState(text, cls = '') {
|
||
const el = $('#saveState');
|
||
el.textContent = text;
|
||
el.className = `save-pill ${cls}`;
|
||
}
|
||
|
||
async function loadPlans(openChooser = false) {
|
||
const data = await fetchJSON(api('/api/plans'));
|
||
state.plans = data.plans || [];
|
||
if (!state.plans.length) throw new Error('Keine Trainingsplan-JSON gefunden.');
|
||
await selectPlan(data.selected || state.plans[0].id, false);
|
||
if (openChooser) openPlanModal();
|
||
}
|
||
|
||
async function selectPlan(id, persist = true) {
|
||
persistUiState();
|
||
if (persist) await fetchJSON(api(`/api/plans/${encodeURIComponent(id)}/select`), {method:'POST', body:'{}'});
|
||
const data = await fetchJSON(api(`/api/plans/${encodeURIComponent(id)}`));
|
||
state.selectedId = id;
|
||
state.plan = data.plan;
|
||
state.dayNum = state.plan.config.days[0]?.num || 1;
|
||
state.view = 'day';
|
||
restoreUiState();
|
||
$('#headerPlan').textContent = plain(state.plan.name || state.plan.config.meta?.title);
|
||
setSaveState('Bereit');
|
||
renderEverything();
|
||
}
|
||
|
||
function currentDay() {
|
||
return state.plan?.config?.days?.find(day => Number(day.num) === Number(state.dayNum)) || state.plan?.config?.days?.[0] || null;
|
||
}
|
||
|
||
function exerciseById(id) {
|
||
return (state.plan?.config?.exercises || []).find(exercise => exercise.id === id) || null;
|
||
}
|
||
|
||
function progressionOptions(exercise) {
|
||
const current = exercise?.current_progression_id || exercise?.progressions?.[0]?.id || '';
|
||
return (exercise?.progressions || []).map(progression =>
|
||
`<option value="${esc(progression.id)}"${progression.id === current ? ' selected' : ''}>${esc(progression.name || progression.id)}</option>`
|
||
).join('');
|
||
}
|
||
|
||
function currentProgression(exercise) {
|
||
return exercise?.current_progression || (exercise?.progressions || []).find(item => item.id === exercise?.current_progression_id) || exercise?.progressions?.[0] || null;
|
||
}
|
||
|
||
function renderEverything() {
|
||
renderDay();
|
||
renderPlan();
|
||
switchView(state.view);
|
||
}
|
||
|
||
function renderDay() {
|
||
const cfg = state.plan?.config;
|
||
const day = currentDay();
|
||
if (!cfg || !day) {
|
||
$('#dayView').innerHTML = '<section class="card"><div class="empty">Kein Tag im Plan vorhanden.</div></section>';
|
||
return;
|
||
}
|
||
const days = cfg.days.map(item =>
|
||
`<button class="chip ${Number(item.num) === Number(state.dayNum) ? 'active' : ''}" data-day="${esc(item.num)}">Tag ${esc(item.num)}</button>`
|
||
).join('');
|
||
$('#dayView').innerHTML = `
|
||
<section class="card hero">
|
||
<div class="eyebrow">Tag ${esc(day.num)}</div>
|
||
<div class="row between" style="align-items:flex-start;margin-top:6px">
|
||
<div class="grow"><h1>${esc(plain(day.name))}</h1><div class="muted tiny">${esc(plain(cfg.meta?.title || state.plan.name))}</div></div>
|
||
<span class="status-badge">${cfg.exercises.length} Übungen</span>
|
||
</div>
|
||
<div class="overview-line">
|
||
<span class="status-badge">${cfg.schema_version}/${cfg.contract_version}</span>
|
||
<span class="status-badge">${cfg.days.length} Tage</span>
|
||
</div>
|
||
</section>
|
||
<section class="card card-pad">
|
||
<div class="eyebrow">Tage</div>
|
||
<div class="day-strip">${days}</div>
|
||
</section>
|
||
${renderListSection('Warm-up', day.warmup)}
|
||
${renderTraining(day)}
|
||
${renderListSection('Cool-down', day.cooldown)}
|
||
`;
|
||
}
|
||
|
||
function renderListSection(title, section) {
|
||
const items = section?.items || [];
|
||
if (!items.length) return '';
|
||
return `<section class="card">
|
||
<div class="section-head"><div><div class="eyebrow">Bestandteil</div><h2>${esc(title)}</h2></div></div>
|
||
<div class="section-body"><div class="list">${items.map(item => `<div class="plain-item">${esc(item)}</div>`).join('')}</div></div>
|
||
</section>`;
|
||
}
|
||
|
||
function renderTraining(day) {
|
||
const exercises = day.exercises || [];
|
||
if (!exercises.length) return '<section class="card"><div class="empty">Für diesen Tag sind keine Übungen hinterlegt.</div></section>';
|
||
return `<section class="card">
|
||
<div class="section-head"><div><div class="eyebrow">Training</div><h2>Übungen</h2></div><span class="status-badge">${exercises.length}</span></div>
|
||
${exercises.map(item => renderExercise(item.exercise || exerciseById(item.exercise_id))).join('')}
|
||
</section>`;
|
||
}
|
||
|
||
function renderExercise(exercise) {
|
||
if (!exercise) return '';
|
||
const current = currentProgression(exercise);
|
||
const options = progressionOptions(exercise);
|
||
const disabled = options ? '' : 'disabled';
|
||
return `<div class="exercise" data-exercise-id="${esc(exercise.id)}">
|
||
<div class="exercise-title">
|
||
<div class="exercise-name">
|
||
<strong>${esc(plain(exercise.name))}</strong>
|
||
<small>${current ? esc(current.name) : 'Keine Progression hinterlegt'}</small>
|
||
</div>
|
||
<label class="field">Aktuell
|
||
<select data-current-progression ${disabled}>
|
||
${options || '<option value="">Keine Progression</option>'}
|
||
</select>
|
||
</label>
|
||
</div>
|
||
${renderProgressionPills(exercise)}
|
||
</div>`;
|
||
}
|
||
|
||
function renderProgressionPills(exercise) {
|
||
const current = exercise.current_progression_id || exercise.progressions?.[0]?.id || '';
|
||
const items = exercise.progressions || [];
|
||
if (!items.length) return '';
|
||
return `<div class="progression-list">${items.map(item =>
|
||
`<span class="progression-pill ${item.id === current ? 'current' : ''}">${esc(item.name)}</span>`
|
||
).join('')}</div>`;
|
||
}
|
||
|
||
function renderPlan() {
|
||
const cfg = state.plan?.config;
|
||
if (!cfg) return;
|
||
$('#planView').innerHTML = `<section class="card hero">
|
||
<div class="eyebrow">Plan</div>
|
||
<h1>${esc(plain(cfg.meta?.title || state.plan.name))}</h1>
|
||
<div class="overview-line">
|
||
<span class="status-badge">${cfg.days.length} Tage</span>
|
||
<span class="status-badge">${cfg.exercises.length} Übungen</span>
|
||
</div>
|
||
</section>
|
||
<section class="card">
|
||
${cfg.days.map(day => `<div class="plan-day">
|
||
<h3>Tag ${esc(day.num)} · ${esc(plain(day.name))}</h3>
|
||
<div class="tiny muted">${(day.exercises || []).map(item => esc(plain((item.exercise || exerciseById(item.exercise_id))?.name || ''))).filter(Boolean).join(' · ')}</div>
|
||
</div>`).join('')}
|
||
</section>`;
|
||
}
|
||
|
||
function switchView(view) {
|
||
state.view = view;
|
||
persistUiState();
|
||
$$('.nav-btn').forEach(button => button.classList.toggle('active', button.dataset.view === view));
|
||
$('#dayView').classList.toggle('hidden', view !== 'day');
|
||
$('#planView').classList.toggle('hidden', view !== 'plan');
|
||
}
|
||
|
||
async function saveCurrentProgression(exerciseId, progressionId) {
|
||
if (state.saving) return;
|
||
state.saving = true;
|
||
setSaveState('Speichert …', 'saving');
|
||
try {
|
||
const data = await fetchJSON(api(`/api/plans/${encodeURIComponent(state.selectedId)}/exercises/${encodeURIComponent(exerciseId)}/current`), {
|
||
method: 'PATCH',
|
||
body: JSON.stringify({current_progression_id: progressionId}),
|
||
});
|
||
state.plan = data.plan;
|
||
setSaveState('Gespeichert', 'saved');
|
||
renderEverything();
|
||
setTimeout(() => setSaveState('Bereit'), 1200);
|
||
} catch (error) {
|
||
setSaveState('Fehler', 'error');
|
||
window.alert(error.message);
|
||
} finally {
|
||
state.saving = false;
|
||
}
|
||
}
|
||
|
||
function openPlanModal() {
|
||
$('#planChoices').innerHTML = state.plans.map(plan => `<button class="plan-choice ${plan.id === state.selectedId ? 'active' : ''}" data-plan-choice="${esc(plan.id)}">
|
||
<strong>${esc(plain(plan.name))}</strong>
|
||
<span>${esc(plan.days)} Tage · ${esc(plan.exercises)} Übungen</span>
|
||
</button>`).join('');
|
||
$('#planModal').classList.remove('hidden');
|
||
}
|
||
|
||
function closePlanModal() {
|
||
$('#planModal').classList.add('hidden');
|
||
}
|
||
|
||
document.addEventListener('click', async event => {
|
||
const dayButton = event.target.closest('[data-day]');
|
||
if (dayButton) {
|
||
state.dayNum = Number(dayButton.dataset.day);
|
||
persistUiState();
|
||
renderEverything();
|
||
return;
|
||
}
|
||
const nav = event.target.closest('[data-view]');
|
||
if (nav) {
|
||
switchView(nav.dataset.view);
|
||
return;
|
||
}
|
||
if (event.target.closest('#choosePlanBtn')) {
|
||
openPlanModal();
|
||
return;
|
||
}
|
||
if (event.target.closest('#closePlanModal') || event.target === $('#planModal')) {
|
||
closePlanModal();
|
||
return;
|
||
}
|
||
const planChoice = event.target.closest('[data-plan-choice]');
|
||
if (planChoice) {
|
||
closePlanModal();
|
||
await selectPlan(planChoice.dataset.planChoice);
|
||
}
|
||
});
|
||
|
||
document.addEventListener('change', event => {
|
||
const select = event.target.closest('[data-current-progression]');
|
||
if (!select) return;
|
||
const row = select.closest('[data-exercise-id]');
|
||
if (!row || !select.value) return;
|
||
saveCurrentProgression(row.dataset.exerciseId, select.value);
|
||
});
|
||
|
||
const ready = window.BT?.ready || Promise.resolve();
|
||
ready.then(() => loadPlans()).catch(error => {
|
||
$('#fatalError').textContent = error.message;
|
||
$('#fatalError').classList.remove('hidden');
|
||
$('#headerPlan').textContent = 'Kein Plan gefunden';
|
||
});
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|