[OCaml] High Intensity Training Online
refactor show the routine as a table, not accordions
Replace the routine's workout accordions with a basic HTML table. Each row is one workout of the cycle: a row-header names the workout, one cell shows its prescribed-stimuli count, and one cell lists the prescription. The disclosure widgets and their open animation are dropped with the markup they served. Presentation only; no doctrine bearing. The prescription stays a plain read view: the logbook still records and never interprets. Update the web flow test to assert the table replaces the details accordions.
Changed files
lib/web/assets/hito.css
@@ -583,66 +583,38 @@
583
583
}
584
584
li li { color: var(--muted-ink); font-size: 0.92rem; }
585
585
586
Removed:
/* The routine's workout accordions. Each details is one workout of the cycle.
587
Removed:
The stack spaces the cards; each card reads as a bordered panel with a
588
Removed:
pressable summary row and an inset description. */
589
Removed:
.routine-accordions {
590
Removed:
display: flex;
591
Removed:
flex-direction: column;
592
Removed:
gap: 0.6rem;
586
Added:
/* The routine's workouts as a basic table: one row per workout of the cycle,
587
Added:
with its name, prescribed-stimuli count, and the prescription. The row header
588
Added:
names the workout; the prescription cell lists each stimulus. */
589
Added:
.routine-table {
590
Added:
width: 100%;
593
591
margin: 1.25rem 0 0;
592
Added:
border-collapse: collapse;
594
593
}
595
594
596
Removed:
.routine-accordion {
597
Removed:
border: 1px solid var(--rule-strong);
598
Removed:
border-left: 4px solid var(--oxblood);
599
Removed:
background: var(--paper);
600
Removed:
border-radius: var(--radius);
595
Added:
.routine-table th,
596
Added:
.routine-table td {
597
Added:
padding: 0.6rem 0.7rem;
598
Added:
border-bottom: 1px solid var(--rule);
599
Added:
text-align: left;
600
Added:
vertical-align: top;
601
601
}
602
602
603
Removed:
.routine-accordion > summary {
604
Removed:
display: flex;
605
Removed:
align-items: center;
606
Removed:
min-height: 2.75rem;
607
Removed:
padding: 0.7rem 0.9rem;
603
Added:
.routine-table thead th {
608
604
color: var(--ink);
609
Removed:
cursor: pointer;
610
Removed:
font-weight: 600;
611
Removed:
list-style: none;
605
Added:
font: 700 var(--font-size-small)/1.2 var(--sans);
606
Added:
letter-spacing: 0.02em;
607
Added:
border-bottom: 2px solid var(--rule-strong);
612
608
}
613
609
614
Removed:
.routine-accordion > summary::-webkit-details-marker { display: none; }
615
Removed:
.routine-accordion > summary::marker { content: ""; }
616
Removed:
617
Removed:
/* A rotating chevron marks the open state. */
618
Removed:
.routine-accordion > summary::after {
619
Removed:
margin-left: auto;
620
Removed:
color: var(--rule-strong);
621
Removed:
content: "\25be";
622
Removed:
transition: transform 180ms ease;
610
Added:
.routine-table tbody th {
611
Added:
color: var(--ink);
612
Added:
font-weight: 700;
623
613
}
624
Removed:
.routine-accordion[open] > summary::after { transform: rotate(180deg); }
625
614
626
Removed:
.routine-accordion > summary:hover {
627
Removed:
color: var(--oxblood-dark);
628
Removed:
background: #ece4d5;
629
Removed:
}
630
Removed:
631
Removed:
.routine-description {
632
Removed:
border-top: 1px solid var(--rule);
633
Removed:
padding: 0.4rem 0.9rem 0.9rem;
634
Removed:
}
635
Removed:
636
Removed:
/* Routine accordions animate open: the disclosed panel fades and slides down
637
Removed:
when the details opens. Reduced-motion users get the panel instantly, via
638
Removed:
the global prefers-reduced-motion rule that clamps animation duration. */
639
Removed:
@keyframes accordion-open {
640
Removed:
from { opacity: 0; transform: translateY(-0.35rem); }
641
Removed:
to { opacity: 1; transform: translateY(0); }
642
Removed:
}
643
Removed:
644
Removed:
.routine-accordion[open] > .routine-description {
645
Removed:
animation: accordion-open 180ms ease;
615
Added:
.routine-table ul {
616
Added:
margin: 0;
617
Added:
padding-left: 1.1rem;
646
618
}
647
619
648
620
@media (max-width: 42rem) {
lib/web/pages.ml
@@ -425,32 +425,46 @@
425
425
html_page ~trainee ~request ~active:"routine" ~logging "Routine"
426
426
[
427
427
tag "h1" [] [ txt "%s" (Prescription.Routine.name routine) ];
428
Removed:
tag "div"
429
Removed:
[ class_ "routine-accordions" ]
430
Removed:
(List.map
431
Removed:
(fun workout ->
432
Removed:
tag "details"
433
Removed:
[ class_ "routine-accordion" ]
434
Removed:
[
435
Removed:
tag "summary" []
428
Added:
tag "table"
429
Added:
[ class_ "routine-table" ]
430
Added:
[
431
Added:
tag "thead" []
432
Added:
[
433
Added:
tag "tr" []
434
Added:
[
435
Added:
tag "th"
436
Added:
[ Dream_html.string_attr "scope" "col" ]
437
Added:
[ txt "Workout" ];
438
Added:
tag "th"
439
Added:
[ Dream_html.string_attr "scope" "col" ]
440
Added:
[ txt "Stimuli" ];
441
Added:
tag "th"
442
Added:
[ Dream_html.string_attr "scope" "col" ]
443
Added:
[ txt "Prescription" ];
444
Added:
];
445
Added:
];
446
Added:
tag "tbody" []
447
Added:
(List.map
448
Added:
(fun workout ->
449
Added:
let stimuli = Prescription.Workout.stimuli workout in
450
Added:
tag "tr" []
436
451
[
437
Removed:
txt "%s — %d prescribed stimuli"
438
Removed:
(Prescription.Workout.name workout)
439
Removed:
(List.length (Prescription.Workout.stimuli workout));
440
Removed:
];
441
Removed:
tag "div"
442
Removed:
[ class_ "routine-description" ]
443
Removed:
[
444
Removed:
tag "p" [] [ txt "Description" ];
445
Removed:
tag "ul" []
446
Removed:
(List.map
447
Removed:
(fun stimulus ->
448
Removed:
tag "li" []
449
Removed:
[ txt "%s" (describe_prescription stimulus) ])
450
Removed:
(Prescription.Workout.stimuli workout));
451
Removed:
];
452
Removed:
])
453
Removed:
(Prescription.Routine.workouts routine));
452
Added:
tag "th"
453
Added:
[ Dream_html.string_attr "scope" "row" ]
454
Added:
[ txt "%s" (Prescription.Workout.name workout) ];
455
Added:
tag "td" [] [ txt "%d" (List.length stimuli) ];
456
Added:
tag "td" []
457
Added:
[
458
Added:
tag "ul" []
459
Added:
(List.map
460
Added:
(fun stimulus ->
461
Added:
tag "li" []
462
Added:
[ txt "%s" (describe_prescription stimulus) ])
463
Added:
stimuli);
464
Added:
];
465
Added:
])
466
Added:
(Prescription.Routine.workouts routine));
467
Added:
];
454
468
]
455
469
456
470
let error_for ~input_id (field : string) (errors : (string * string) list) =
test/test_web.ml
@@ -482,7 +482,7 @@
482
482
"client has JavaScript content type" true
483
483
(List.mem "application/javascript; charset=utf-8"
484
484
(Dream.headers asset "Content-Type")) );
485
Removed:
( "the routine page uses workout description accordions",
485
Added:
( "the routine page lists workouts in a basic table",
486
486
`Quick,
487
487
fun () ->
488
488
let c = client () in
@@ -495,14 +495,17 @@
495
495
Alcotest.(check int) "routine selected" 303 (status selection);
496
496
let page = body (get c "/routine") in
497
497
Alcotest.(check bool)
498
Removed:
"uses details" true
498
Added:
"uses a table" true
499
Added:
(contains ~substring:"<table" page);
500
Added:
Alcotest.(check bool)
501
Added:
"no longer uses details accordions" false
499
502
(contains ~substring:"<details" page);
500
503
Alcotest.(check bool)
501
Removed:
"summarizes Day 1" true
502
Removed:
(contains ~substring:"<summary>Day 1" page);
504
Added:
"heads the workout column" true
505
Added:
(contains ~substring:"Workout" page);
503
506
Alcotest.(check bool)
504
Removed:
"includes description" true
505
Removed:
(contains ~substring:"Description" page);
507
Added:
"names Day 1 as a row" true
508
Added:
(contains ~substring:"Day 1" page);
506
509
Alcotest.(check bool)
507
510
"includes prescription" true
508
511
(contains ~substring:"Dumbbell Flyes into Incline Presses" page);