Skip to content

Commit 76979c1

Browse files
christianboerclaude
andcommitted
Add Bourg d'Oisans routes, nutrition plan and dashboard display
Week 9 Alps day: 3 Villard loops (41km/3138m D+) with Strava links, Maurten 320 nutrition plan per loop + hotel stops, heat adjustments. Dashboard timeline now shows route details and nutrition breakdown. Removed poles from gear checklist and plan. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e543985 commit 76979c1

File tree

5 files changed

+186
-15
lines changed

5 files changed

+186
-15
lines changed

plan/swiss-iron-trail-t78.md

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,14 +166,39 @@ Rotterdam strategy: controlled pace, not all-out. Use for leg turnover practice.
166166
| Tue | Easy run + mobility | 10 ||
167167
| Wed | Easy run or rest |||
168168
| Thu | **Rest** — mobility, prep for tomorrow |||
169-
| Fri (May 22) | **Alps trail day**power-hiking, nutrition, gear, poles practice. Most important training day of the plan. | 30 | 3,000m |
169+
| Fri (May 22) | **Alps trail day**3 loops from hotel in Bourg d'Oisans. Most important training day of the plan. | 41 | 3,138m |
170170
| Sat (May 23) | Cycling day 1 — enjoy, moderate intensity |||
171171
| Sun (May 24) | Cycling day 2 — enjoy, moderate intensity |||
172-
| **Total** | | **~48 km run** | **~3,000m** |
172+
| **Total** | | **~59 km run** | **~3,138m** |
173+
174+
**Alps day routes (Bourg d'Oisans → Villard):**
175+
176+
All loops start and finish at the hotel for refuelling.
177+
178+
| # | Route | km | D+ | Max alt | Strava |
179+
|---|---|---|---|---|---|
180+
| 1 | Villard via weg omhoog | 14.6 | 1,008m | 1,671m | [route](https://www.strava.com/routes/3457828796816278982) |
181+
| 2 | Villard steil linksom | 13.9 | 1,000m | 1,671m | [route](https://www.strava.com/routes/3457840458426864014) |
182+
| 3 | Villard steil rechtsom | 12.2 | 1,130m | 1,833m | [route](https://www.strava.com/routes/3457826669002961350) |
183+
184+
Order: easiest first (via weg) as warm-up, then linksom, then rechtsom (steepest, highest) as finale.
185+
186+
**Alps day nutrition plan:**
187+
188+
Target: 250–300 kcal/hour from the start. Estimated total time ~7:00–7:30 on trail + 2 hotel stops.
189+
190+
| Phase | Duration | Nutrition | Kcal | Fluid |
191+
|---|---|---|---|---|
192+
| Loop 1 — Via weg omhoog | ~2:15 | 2× Maurten 320 (1 per soft flask) | 640 | 1L |
193+
| Hotel stop 1 | ~15 min | Banana + white bread w/ honey | ~200 | 500ml water |
194+
| Loop 2 — Steil linksom | ~2:15 | 2× Maurten 320 | 640 | 1L |
195+
| Hotel stop 2 | ~15 min | Banana + white bread w/ honey | ~200 | 500ml water |
196+
| Loop 3 — Steil rechtsom | ~2:30 | 2× Maurten 320 | 640 | 1L |
197+
| **Total** | **~8:00** | | **~2,320** | **~4L** |
198+
199+
Warm weather adjustment (>20°C valley): add 500ml plain water per loop alongside the Maurten flasks. Maurten 320 is hypertonic — pair with water in heat to avoid GI issues.
173200

174201
**Alps day guidance:**
175-
- Practice exact race nutrition (250–300 kcal/hour from hour 1)
176-
- Use poles — get comfortable with them before T78
177202
- Power-hike all uphills >15% grade, targeting 400–500m/hour vertical rate
178203
- Practice smooth hike-to-run transitions
179204
- Wear planned race kit and pack

scripts/export_dashboard_data.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,20 @@
2828

2929
EVENTS = [
3030
{"date": "2026-04-12", "name": "Rotterdam Half Marathon", "distance_km": 21, "elevation_m": 0, "role": "Speed sharpener"},
31-
{"date": "2026-05-22", "name": "French Alps \u2014 Bourg d'Oisans", "distance_km": 30, "elevation_m": 3000, "role": "Mountain adaptation"},
31+
{"date": "2026-05-22", "name": "French Alps — Bourg d'Oisans", "distance_km": 41, "elevation_m": 3138, "role": "Mountain adaptation",
32+
"routes": [
33+
{"name": "Villard via weg omhoog", "km": 14.6, "elevation_m": 1008, "max_alt_m": 1671, "url": "https://www.strava.com/routes/3457828796816278982"},
34+
{"name": "Villard steil linksom", "km": 13.9, "elevation_m": 1000, "max_alt_m": 1671, "url": "https://www.strava.com/routes/3457840458426864014"},
35+
{"name": "Villard steil rechtsom", "km": 12.2, "elevation_m": 1130, "max_alt_m": 1833, "url": "https://www.strava.com/routes/3457826669002961350"},
36+
],
37+
"nutrition": {
38+
"product": "Maurten Drink Mix 320",
39+
"per_loop": "2× soft flask 500ml = 640 kcal",
40+
"hotel_stop": "Banana + bread w/ honey (~200 kcal) + 500ml water",
41+
"total_kcal": 2320,
42+
"total_fluid_l": 4,
43+
"heat_note": ">20°C: add 500ml plain water per loop alongside Maurten"
44+
}},
3245
{"date": "2026-06-06", "name": "Trail Godefroy", "distance_km": 50, "elevation_m": 1840, "role": "Dress rehearsal"},
3346
{"date": "2026-06-27", "name": "Swiss Iron Trail T78", "distance_km": 78, "elevation_m": 5000, "role": "A-race"},
3447
]

site/css/style.css

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,82 @@ a:hover { color: var(--accent-light); }
716716
margin-top: 4px;
717717
}
718718

719+
.event-routes {
720+
margin-top: 8px;
721+
text-align: left;
722+
display: inline-block;
723+
}
724+
725+
.event-route {
726+
display: flex;
727+
align-items: baseline;
728+
gap: 6px;
729+
font-size: 0.7rem;
730+
line-height: 1.7;
731+
}
732+
733+
.event-route .route-num {
734+
font-family: var(--font-mono);
735+
font-size: 0.65rem;
736+
color: var(--bg-primary);
737+
background: var(--accent);
738+
width: 16px;
739+
height: 16px;
740+
border-radius: 50%;
741+
display: inline-flex;
742+
align-items: center;
743+
justify-content: center;
744+
flex-shrink: 0;
745+
font-weight: 600;
746+
}
747+
748+
.event-route a {
749+
color: var(--text-primary);
750+
text-decoration: none;
751+
font-weight: 500;
752+
}
753+
754+
.event-route a:hover {
755+
color: var(--accent);
756+
}
757+
758+
.event-route .route-stats {
759+
color: var(--text-muted);
760+
font-family: var(--font-mono);
761+
font-size: 0.65rem;
762+
white-space: nowrap;
763+
}
764+
765+
.event-nutrition {
766+
margin-top: 8px;
767+
text-align: left;
768+
display: inline-block;
769+
}
770+
771+
.event-nutrition .nutrition-row {
772+
font-size: 0.7rem;
773+
color: var(--text-secondary);
774+
line-height: 1.8;
775+
display: flex;
776+
align-items: baseline;
777+
gap: 5px;
778+
}
779+
780+
.event-nutrition .nutrition-icon {
781+
font-family: var(--font-mono);
782+
font-size: 0.65rem;
783+
width: 18px;
784+
text-align: center;
785+
flex-shrink: 0;
786+
color: var(--phase-mountain);
787+
}
788+
789+
.event-nutrition .nutrition-row.heat {
790+
color: var(--accent);
791+
font-size: 0.65rem;
792+
margin-top: 2px;
793+
}
794+
719795
/* ---- Heatmap ---- */
720796

721797
.heatmap-wrapper {

site/data/training.json

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"generated_at": "2026-03-23T17:33:50",
2+
"generated_at": "2026-03-23T20:52:59",
33
"race": {
44
"name": "Swiss Iron Trail T78",
55
"date": "2026-06-27",
@@ -21,9 +21,40 @@
2121
{
2222
"date": "2026-05-22",
2323
"name": "French Alps — Bourg d'Oisans",
24-
"distance_km": 30,
25-
"elevation_m": 3000,
26-
"role": "Mountain adaptation"
24+
"distance_km": 41,
25+
"elevation_m": 3138,
26+
"role": "Mountain adaptation",
27+
"routes": [
28+
{
29+
"name": "Villard via weg omhoog",
30+
"km": 14.6,
31+
"elevation_m": 1008,
32+
"max_alt_m": 1671,
33+
"url": "https://www.strava.com/routes/3457828796816278982"
34+
},
35+
{
36+
"name": "Villard steil linksom",
37+
"km": 13.9,
38+
"elevation_m": 1000,
39+
"max_alt_m": 1671,
40+
"url": "https://www.strava.com/routes/3457840458426864014"
41+
},
42+
{
43+
"name": "Villard steil rechtsom",
44+
"km": 12.2,
45+
"elevation_m": 1130,
46+
"max_alt_m": 1833,
47+
"url": "https://www.strava.com/routes/3457826669002961350"
48+
}
49+
],
50+
"nutrition": {
51+
"product": "Maurten Drink Mix 320",
52+
"per_loop": "2× soft flask 500ml = 640 kcal",
53+
"hotel_stop": "Banana + bread w/ honey (~200 kcal) + 500ml water",
54+
"total_kcal": 2320,
55+
"total_fluid_l": 4,
56+
"heat_note": ">20°C: add 500ml plain water per loop alongside Maurten"
57+
}
2758
},
2859
{
2960
"date": "2026-06-06",
@@ -559,8 +590,8 @@
559590
"phase": "Mountain Specificity",
560591
"start_date": "2026-05-18",
561592
"end_date": "2026-05-24",
562-
"target_km": 48.0,
563-
"target_elevation": 3000.0,
593+
"target_km": 59.0,
594+
"target_elevation": 3138.0,
564595
"days": [
565596
{
566597
"day": "Mon",
@@ -592,9 +623,9 @@
592623
},
593624
{
594625
"day": "Fri",
595-
"session": "Alps trail day — power-hiking, nutrition, gear, poles practice. Most important training day of the plan.",
596-
"target_km": 30.0,
597-
"target_elevation": 3000.0,
626+
"session": "Alps trail day — 3 loops from hotel in Bourg d'Oisans. Most important training day of the plan.",
627+
"target_km": 41.0,
628+
"target_elevation": 3138.0,
598629
"date": "2026-05-22"
599630
},
600631
{

site/js/app.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,39 @@ function renderTimeline(events) {
101101

102102
const dateStr = new Date(ev.date + 'T00:00:00').toLocaleDateString('en-GB', { day: 'numeric', month: 'short' });
103103

104+
let routesHtml = '';
105+
if (ev.routes && ev.routes.length) {
106+
routesHtml = `<div class="event-routes">` +
107+
ev.routes.map((r, i) => `
108+
<div class="event-route">
109+
<span class="route-num">${i + 1}</span>
110+
<a href="${r.url}" target="_blank" rel="noopener">${r.name}</a>
111+
<span class="route-stats">${r.km}km · ${r.elevation_m}m D+ · ${r.max_alt_m}m</span>
112+
</div>`
113+
).join('') + `</div>`;
114+
}
115+
116+
let nutritionHtml = '';
117+
if (ev.nutrition && typeof ev.nutrition === 'object') {
118+
nutritionHtml = `
119+
<div class="event-nutrition">
120+
<div class="nutrition-row"><span class="nutrition-icon">⚡</span> ${ev.nutrition.product}: ${ev.nutrition.per_loop}</div>
121+
<div class="nutrition-row"><span class="nutrition-icon">🏨</span> ${ev.nutrition.hotel_stop}</div>
122+
<div class="nutrition-row"><span class="nutrition-icon">Σ</span> ${ev.nutrition.total_kcal} kcal · ${ev.nutrition.total_fluid_l}L fluid</div>
123+
<div class="nutrition-row heat">${ev.nutrition.heat_note}</div>
124+
</div>`;
125+
} else if (ev.nutrition) {
126+
nutritionHtml = `<div class="event-nutrition">${ev.nutrition}</div>`;
127+
}
128+
104129
return `
105130
<div class="timeline-event ${status}">
106131
<div class="timeline-dot"></div>
107132
<div class="event-date">${dateStr}</div>
108133
<div class="event-name">${ev.name}</div>
109134
<div class="event-detail">${ev.distance_km}km · ${ev.elevation_m}m D+</div>
135+
${routesHtml}
136+
${nutritionHtml}
110137
<div class="event-days">${daysText}</div>
111138
</div>
112139
`;
@@ -154,7 +181,6 @@ function renderHeatmap(dailyRuns) {
154181

155182
const GEAR_ITEMS = [
156183
{ name: 'Running shoes (tested, aggressive tread)', mandatory: true },
157-
{ name: 'Poles', mandatory: false },
158184
{ name: 'Headlamp + spare battery', mandatory: true },
159185
{ name: 'Rain jacket (waterproof)', mandatory: true },
160186
{ name: 'Warm layer (fleece/puffy)', mandatory: true },

0 commit comments

Comments
 (0)