[OCaml] High Intensity Training Online
feat Add polished toolkit interface
Give the server-rendered logbook a responsive, sober interface with\nOcsigen Toolkit components and a separately authored CSS asset.\n\nEmbed the stylesheet at build time so the local server remains\nself-contained while serving it as a typed CSS route.
Changed files
dune-project
@@ -32,6 +32,12 @@
32
32
(>= 7.0))
33
33
(ocsipersist-sqlite
34
34
(>= 2.1))
35
Added:
(ocsigen-toolkit
36
Added:
(= 4.3.0))
37
Added:
(crunch
38
Added:
(and
39
Added:
(= 4.1.0)
40
Added:
:build))
35
41
(ocamlformat
36
42
(and
37
43
(= 0.29.0)
hito.opam
@@ -16,6 +16,8 @@
16
16
"eliom" {>= "12.0"}
17
17
"ocsigenserver" {>= "7.0"}
18
18
"ocsipersist-sqlite" {>= "2.1"}
19
Added:
"ocsigen-toolkit" {= "4.3.0"}
20
Added:
"crunch" {= "4.1.0" & build}
19
21
"ocamlformat" {= "0.29.0" & with-dev-setup}
20
22
"odoc" {with-doc}
21
23
]
lib/web/assets/hito.css
@@ -0,0 +1,472 @@
1
Added:
:root {
2
Added:
color-scheme: dark;
3
Added:
--ink: #17191d;
4
Added:
--panel: #202328;
5
Added:
--panel-raised: #292d33;
6
Added:
--line: #464b54;
7
Added:
--muted: #afb2b8;
8
Added:
--bone: #f1eee8;
9
Added:
--accent: #d9877b;
10
Added:
--accent-strong: #a84438;
11
Added:
--brass: #c69a56;
12
Added:
--danger: #d77b70;
13
Added:
--shadow: 0 1.5rem 4rem rgba(0, 0, 0, 0.28);
14
Added:
}
15
Added:
16
Added:
* {
17
Added:
box-sizing: border-box;
18
Added:
}
19
Added:
20
Added:
body {
21
Added:
min-width: 20rem;
22
Added:
margin: 0;
23
Added:
color: var(--bone);
24
Added:
background:
25
Added:
radial-gradient(circle at 14% -10%, #343840 0, transparent 27rem),
26
Added:
radial-gradient(circle at 94% 12%, #39343a 0, transparent 24rem),
27
Added:
var(--ink);
28
Added:
font:
29
Added:
16px/1.55 Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
30
Added:
"Segoe UI", sans-serif;
31
Added:
}
32
Added:
33
Added:
a {
34
Added:
color: var(--accent);
35
Added:
text-decoration-thickness: 0.09em;
36
Added:
text-underline-offset: 0.18em;
37
Added:
}
38
Added:
39
Added:
a:hover {
40
Added:
color: #efb0a7;
41
Added:
}
42
Added:
43
Added:
.app-shell {
44
Added:
width: min(100%, 68rem);
45
Added:
margin: 0 auto;
46
Added:
padding: 1.25rem;
47
Added:
}
48
Added:
49
Added:
.masthead {
50
Added:
display: flex;
51
Added:
align-items: center;
52
Added:
justify-content: space-between;
53
Added:
gap: 1rem;
54
Added:
padding: 1rem 0 1.25rem;
55
Added:
border-bottom: 1px solid rgba(241, 238, 227, 0.18);
56
Added:
}
57
Added:
58
Added:
.brand {
59
Added:
display: flex;
60
Added:
align-items: center;
61
Added:
gap: 0.75rem;
62
Added:
color: var(--bone);
63
Added:
text-decoration: none;
64
Added:
}
65
Added:
66
Added:
.brand-mark {
67
Added:
display: grid;
68
Added:
width: 2.5rem;
69
Added:
height: 2.5rem;
70
Added:
place-items: center;
71
Added:
border: 1px solid var(--accent);
72
Added:
border-radius: 0.7rem 1.15rem 0.7rem 1.15rem;
73
Added:
color: var(--accent);
74
Added:
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
75
Added:
font-size: 0.72rem;
76
Added:
font-weight: 800;
77
Added:
letter-spacing: 0.08em;
78
Added:
}
79
Added:
80
Added:
.brand-copy {
81
Added:
display: grid;
82
Added:
line-height: 1.05;
83
Added:
}
84
Added:
85
Added:
.brand-copy strong {
86
Added:
font-size: 1.2rem;
87
Added:
letter-spacing: -0.03em;
88
Added:
}
89
Added:
90
Added:
.brand-copy small,
91
Added:
.eyebrow {
92
Added:
color: var(--accent);
93
Added:
font-size: 0.68rem;
94
Added:
font-weight: 800;
95
Added:
letter-spacing: 0.14em;
96
Added:
text-transform: uppercase;
97
Added:
}
98
Added:
99
Added:
.primary-nav {
100
Added:
display: flex;
101
Added:
align-items: center;
102
Added:
gap: 0.2rem;
103
Added:
}
104
Added:
105
Added:
.primary-nav > a,
106
Added:
.ot-dropdown-button {
107
Added:
display: inline-flex;
108
Added:
align-items: center;
109
Added:
gap: 0.45rem;
110
Added:
min-height: 2.5rem;
111
Added:
padding: 0.55rem 0.7rem;
112
Added:
border-radius: 0.55rem;
113
Added:
color: var(--bone);
114
Added:
font-size: 0.86rem;
115
Added:
font-weight: 700;
116
Added:
text-decoration: none;
117
Added:
}
118
Added:
119
Added:
.primary-nav > a:hover,
120
Added:
.ot-dropdown:hover {
121
Added:
background: rgba(217, 135, 123, 0.12);
122
Added:
color: var(--accent);
123
Added:
}
124
Added:
125
Added:
.nav-menu {
126
Added:
display: none;
127
Added:
}
128
Added:
129
Added:
/* Ocsigen Toolkit's CSS-only dropdown: usable without a client OCaml tier. */
130
Added:
.ot-dropdown {
131
Added:
position: relative;
132
Added:
transition: background-color 0.2s ease;
133
Added:
}
134
Added:
135
Added:
.ot-dropdown-button {
136
Added:
position: relative;
137
Added:
z-index: 5;
138
Added:
cursor: default;
139
Added:
}
140
Added:
141
Added:
.ot-dropdown-background {
142
Added:
position: fixed;
143
Added:
inset: 0;
144
Added:
z-index: 1;
145
Added:
visibility: hidden;
146
Added:
background: #000;
147
Added:
opacity: 0;
148
Added:
transition: opacity 0.15s, visibility 0.15s;
149
Added:
}
150
Added:
151
Added:
.ot-dropdown-menu {
152
Added:
position: absolute;
153
Added:
top: calc(100% + 0.35rem);
154
Added:
right: 0;
155
Added:
z-index: 10;
156
Added:
display: grid;
157
Added:
min-width: 12rem;
158
Added:
overflow: hidden;
159
Added:
visibility: hidden;
160
Added:
border: 1px solid var(--line);
161
Added:
border-radius: 0.7rem;
162
Added:
background: #262a30;
163
Added:
box-shadow: var(--shadow);
164
Added:
opacity: 0;
165
Added:
transition: opacity 0.2s, visibility 0.2s;
166
Added:
}
167
Added:
168
Added:
.ot-dropdown:hover > .ot-dropdown-background {
169
Added:
visibility: visible;
170
Added:
opacity: 0.42;
171
Added:
}
172
Added:
173
Added:
.ot-dropdown:hover > .ot-dropdown-menu {
174
Added:
visibility: visible;
175
Added:
opacity: 1;
176
Added:
}
177
Added:
178
Added:
.ot-dropdown-menu a {
179
Added:
padding: 0.8rem 1rem;
180
Added:
color: var(--bone);
181
Added:
font-size: 0.9rem;
182
Added:
font-weight: 700;
183
Added:
text-decoration: none;
184
Added:
}
185
Added:
186
Added:
.ot-dropdown-menu a + a {
187
Added:
border-top: 1px solid var(--line);
188
Added:
}
189
Added:
190
Added:
.ot-dropdown-menu a:hover {
191
Added:
background: rgba(217, 135, 123, 0.13);
192
Added:
color: var(--accent);
193
Added:
}
194
Added:
195
Added:
/* Ocsigen Toolkit icon styles for the settings/navigation glyph. */
196
Added:
.ot-icon {
197
Added:
display: inline-block;
198
Added:
width: 1em;
199
Added:
height: 1em;
200
Added:
overflow: hidden;
201
Added:
position: relative;
202
Added:
}
203
Added:
204
Added:
.ot-icon::before,
205
Added:
.ot-icon::after {
206
Added:
box-sizing: border-box;
207
Added:
display: inline-block;
208
Added:
content: "";
209
Added:
}
210
Added:
211
Added:
.ot-icon-gear::before {
212
Added:
width: 1em;
213
Added:
height: 1em;
214
Added:
border: 0.18em dotted currentColor;
215
Added:
border-radius: 50%;
216
Added:
}
217
Added:
218
Added:
.ot-icon-gear::after {
219
Added:
position: absolute;
220
Added:
top: 0.22em;
221
Added:
left: 0.22em;
222
Added:
width: 0.56em;
223
Added:
height: 0.56em;
224
Added:
border: 0.17em solid currentColor;
225
Added:
border-radius: 50%;
226
Added:
}
227
Added:
228
Added:
main {
229
Added:
padding: 1.65rem 0 2rem;
230
Added:
}
231
Added:
232
Added:
/* Ot_popup.hcf supplies the structural header/content/footer card wrapper. */
233
Added:
.ot-hcf {
234
Added:
display: flex;
235
Added:
width: 100%;
236
Added:
flex-direction: column;
237
Added:
overflow: hidden;
238
Added:
}
239
Added:
240
Added:
.ot-hcf-header,
241
Added:
.ot-hcf-footer {
242
Added:
flex-shrink: 0;
243
Added:
}
244
Added:
245
Added:
.ot-hcf-header:empty,
246
Added:
.ot-hcf-footer:empty {
247
Added:
display: none;
248
Added:
}
249
Added:
250
Added:
.ot-hcf-content {
251
Added:
flex-grow: 1;
252
Added:
overflow: auto;
253
Added:
}
254
Added:
255
Added:
.page-surface {
256
Added:
min-height: 30rem;
257
Added:
border: 1px solid var(--line);
258
Added:
border-radius: 1.15rem;
259
Added:
background: linear-gradient(145deg, rgba(42, 45, 51, 0.97), rgba(25, 27, 31, 0.98));
260
Added:
box-shadow: var(--shadow);
261
Added:
}
262
Added:
263
Added:
.page-surface .ot-hcf-content {
264
Added:
padding: clamp(1.2rem, 4vw, 2.4rem);
265
Added:
}
266
Added:
267
Added:
h2 {
268
Added:
max-width: 35rem;
269
Added:
margin: 0 0 0.4rem;
270
Added:
font-size: clamp(1.65rem, 5vw, 2.55rem);
271
Added:
line-height: 1.04;
272
Added:
letter-spacing: -0.05em;
273
Added:
}
274
Added:
275
Added:
h3 {
276
Added:
margin: 0;
277
Added:
color: var(--bone);
278
Added:
font-size: 1rem;
279
Added:
}
280
Added:
281
Added:
p {
282
Added:
max-width: 45rem;
283
Added:
margin: 0.55rem 0;
284
Added:
color: var(--muted);
285
Added:
}
286
Added:
287
Added:
h2 + p {
288
Added:
max-width: 39rem;
289
Added:
font-size: 1.03rem;
290
Added:
}
291
Added:
292
Added:
fieldset {
293
Added:
margin: 1.15rem 0 0;
294
Added:
padding: 1rem;
295
Added:
border: 1px solid var(--line);
296
Added:
border-radius: 0.8rem;
297
Added:
background: rgba(9, 10, 12, 0.3);
298
Added:
}
299
Added:
300
Added:
legend {
301
Added:
padding: 0 0.45rem;
302
Added:
color: var(--accent);
303
Added:
font-size: 0.79rem;
304
Added:
font-weight: 800;
305
Added:
letter-spacing: 0.05em;
306
Added:
}
307
Added:
308
Added:
label {
309
Added:
display: block;
310
Added:
margin: 0.85rem 0 0.3rem;
311
Added:
color: var(--bone);
312
Added:
font-size: 0.78rem;
313
Added:
font-weight: 800;
314
Added:
letter-spacing: 0.04em;
315
Added:
text-transform: uppercase;
316
Added:
}
317
Added:
318
Added:
input,
319
Added:
select {
320
Added:
width: 100%;
321
Added:
min-height: 2.7rem;
322
Added:
border: 1px solid #5c626c;
323
Added:
border-radius: 0.55rem;
324
Added:
background: rgba(8, 9, 11, 0.5);
325
Added:
color: var(--bone);
326
Added:
font: inherit;
327
Added:
outline: none;
328
Added:
padding: 0.55rem 0.7rem;
329
Added:
}
330
Added:
331
Added:
input:focus,
332
Added:
select:focus {
333
Added:
border-color: var(--accent);
334
Added:
box-shadow: 0 0 0 3px rgba(217, 135, 123, 0.16);
335
Added:
}
336
Added:
337
Added:
input[type="checkbox"] {
338
Added:
width: auto;
339
Added:
min-height: auto;
340
Added:
margin-right: 0.45rem;
341
Added:
accent-color: var(--accent-strong);
342
Added:
}
343
Added:
344
Added:
input[type="submit"] {
345
Added:
width: auto;
346
Added:
min-height: 2.8rem;
347
Added:
margin-top: 1rem;
348
Added:
border: 0;
349
Added:
border-radius: 0.6rem;
350
Added:
background: var(--accent-strong);
351
Added:
color: #fffaf7;
352
Added:
cursor: pointer;
353
Added:
font-weight: 900;
354
Added:
letter-spacing: 0.01em;
355
Added:
padding: 0.65rem 1rem;
356
Added:
transition: transform 0.15s ease, background 0.15s ease;
357
Added:
}
358
Added:
359
Added:
input[type="submit"]:hover {
360
Added:
background: #c45b4e;
361
Added:
transform: translateY(-1px);
362
Added:
}
363
Added:
364
Added:
input[type="submit"]:disabled {
365
Added:
background: #5a5d63;
366
Added:
color: #ddd9d2;
367
Added:
cursor: not-allowed;
368
Added:
transform: none;
369
Added:
}
370
Added:
371
Added:
.row {
372
Added:
display: flex;
373
Added:
gap: 0.75rem;
374
Added:
}
375
Added:
376
Added:
.row > div {
377
Added:
flex: 1;
378
Added:
min-width: 0;
379
Added:
}
380
Added:
381
Added:
.warn {
382
Added:
max-width: 47rem;
383
Added:
margin: 1rem 0;
384
Added:
border: 1px solid rgba(198, 154, 86, 0.58);
385
Added:
border-left: 0.28rem solid var(--brass);
386
Added:
border-radius: 0.65rem;
387
Added:
background: rgba(198, 154, 86, 0.1);
388
Added:
color: #eed5aa;
389
Added:
padding: 0.85rem 1rem;
390
Added:
}
391
Added:
392
Added:
.done {
393
Added:
color: #d8d5ce;
394
Added:
font-size: 0.86rem;
395
Added:
font-weight: 700;
396
Added:
}
397
Added:
398
Added:
ul {
399
Added:
margin: 1rem 0;
400
Added:
padding: 0;
401
Added:
list-style: none;
402
Added:
}
403
Added:
404
Added:
li {
405
Added:
position: relative;
406
Added:
margin: 0.45rem 0;
407
Added:
padding: 0.75rem 0.85rem 0.75rem 1.35rem;
408
Added:
border-left: 1px solid var(--line);
409
Added:
background: rgba(255, 255, 255, 0.025);
410
Added:
}
411
Added:
412
Added:
li::before {
413
Added:
position: absolute;
414
Added:
top: 1.28rem;
415
Added:
left: 0.55rem;
416
Added:
width: 0.3rem;
417
Added:
height: 0.3rem;
418
Added:
border-radius: 50%;
419
Added:
background: var(--accent);
420
Added:
content: "";
421
Added:
}
422
Added:
423
Added:
li ul {
424
Added:
margin: 0.55rem 0 0;
425
Added:
}
426
Added:
427
Added:
li li {
428
Added:
border-left-color: rgba(164, 180, 170, 0.28);
429
Added:
color: var(--muted);
430
Added:
font-size: 0.92rem;
431
Added:
}
432
Added:
433
Added:
@media (max-width: 42rem) {
434
Added:
.app-shell {
435
Added:
padding: 0.85rem;
436
Added:
}
437
Added:
438
Added:
.masthead {
439
Added:
align-items: flex-start;
440
Added:
}
441
Added:
442
Added:
.primary-nav > a {
443
Added:
display: none;
444
Added:
}
445
Added:
446
Added:
.nav-menu {
447
Added:
display: block;
448
Added:
}
449
Added:
450
Added:
.page-surface {
451
Added:
min-height: calc(100vh - 8.25rem);
452
Added:
border-radius: 0.9rem;
453
Added:
}
454
Added:
455
Added:
.page-surface .ot-hcf-content {
456
Added:
padding: 1.1rem;
457
Added:
}
458
Added:
459
Added:
.row {
460
Added:
gap: 0.55rem;
461
Added:
}
462
Added:
}
463
Added:
464
Added:
@media (max-width: 26rem) {
465
Added:
.row {
466
Added:
display: block;
467
Added:
}
468
Added:
469
Added:
.row > div + div {
470
Added:
margin-top: 0.75rem;
471
Added:
}
472
Added:
}
lib/web/dune
@@ -1,8 +1,15 @@
1
1
; Server-rendered only: no client-side OCaml, so no eliom ppx and no
2
2
; js_of_ocaml. Pages are plain HTML from Eliom_registration.Html.
3
3
4
Added:
(rule
5
Added:
(targets stylesheet.ml)
6
Added:
(deps
7
Added:
(source_tree assets))
8
Added:
(action
9
Added:
(run %{bin:ocaml-crunch} --silent --mode plain --output %{targets} assets)))
10
Added:
4
11
(library
5
12
(name hito_web)
6
13
(public_name hito.web)
7
Removed:
(modules routes pages services)
8
Removed:
(libraries hito.core hito.app eliom.server))
14
Added:
(modules routes pages services stylesheet)
15
Added:
(libraries hito.core hito.app eliom.server ocsigen-toolkit.server))
lib/web/pages.ml
@@ -7,24 +7,34 @@
7
7
use sites. *)
8
8
let id_string (id : Repository.routine_id) = (id :> string)
9
9
10
Removed:
(* Kept inline so the server needs no static file extension. *)
11
Removed:
let stylesheet =
12
Removed:
"*{box-sizing:border-box} body{font:16px/1.5 system-ui,sans-serif;margin:0 \
13
Removed:
auto;max-width:34rem;padding:1rem;color:#191919;background:#fafafa} \
14
Removed:
h1{font-size:1.4rem;margin:0 0 .25rem} h2{font-size:1.1rem;margin:1.5rem 0 \
15
Removed:
.5rem} p{margin:.4rem 0} a{color:#0b5} nav{margin:.5rem 0 \
16
Removed:
1.5rem;font-size:.9rem} nav a{margin-right:1rem} fieldset{border:1px solid \
17
Removed:
#ddd;border-radius:6px;margin:0 0 1rem;padding:.75rem} \
18
Removed:
legend{font-weight:600;padding:0 .35rem} label{display:block;margin:.4rem 0 \
19
Removed:
.1rem;font-size:.85rem;color:#555} \
20
Removed:
input,select{font:inherit;padding:.4rem;width:100%;border:1px solid \
21
Removed:
#ccc;border-radius:4px;background:#fff} \
22
Removed:
input[type=submit]{background:#191919;color:#fff;border:0;cursor:pointer;margin-top:.6rem;width:auto;padding:.5rem \
23
Removed:
1rem} .row{display:flex;gap:.5rem} .row>div{flex:1} \
24
Removed:
.warn{background:#fff6e5;border-left:3px solid #e59b00;padding:.6rem \
25
Removed:
.8rem;margin:1rem 0} .done{color:#666;font-size:.9rem} \
26
Removed:
ul{padding-left:1.2rem} li{margin:.2rem 0}"
10
Added:
(* The stylesheet is a separately authored CSS asset. Dune embeds it in the
11
Added:
executable and the explicit Eliom CSS service serves it at runtime. *)
27
12
13
Added:
let nav_link service label = a ~service [ txt label ] ()
14
Added:
15
Added:
let navigation () =
16
Added:
let menu =
17
Added:
Ot_buttons.dropdown
18
Added:
~a:[ a_class [ "nav-menu" ] ]
19
Added:
~menu:
20
Added:
[
21
Added:
nav_link Routes.home "Overview";
22
Added:
nav_link Routes.routine "Routine";
23
Added:
nav_link Routes.log "Current workout";
24
Added:
nav_link Routes.history "History";
25
Added:
]
26
Added:
[ span [ txt "Menu" ]; Ot_icons.F.config () ]
27
Added:
in
28
Added:
nav
29
Added:
~a:[ a_class [ "primary-nav" ] ]
30
Added:
[
31
Added:
nav_link Routes.home "Overview";
32
Added:
nav_link Routes.routine "Routine";
33
Added:
nav_link Routes.log "Current workout";
34
Added:
nav_link Routes.history "History";
35
Added:
menu;
36
Added:
]
37
Added:
28
38
let shell ~title:t content =
29
39
html
30
40
(head
@@ -37,23 +47,44 @@
37
47
a_name "viewport"; a_content "width=device-width,initial-scale=1";
38
48
]
39
49
();
40
Removed:
style [ txt stylesheet ];
50
Added:
meta ~a:[ a_name "theme-color"; a_content "#111815" ] ();
51
Added:
css_link ~uri:(make_uri ~service:Routes.stylesheet ()) ();
41
52
])
42
53
(body
43
Removed:
([
44
Removed:
h1 [ txt "hito" ];
45
Removed:
nav
46
Removed:
[
47
Removed:
a ~service:Routes.home [ txt "Home" ] ();
48
Removed:
a ~service:Routes.routine [ txt "Routine" ] ();
49
Removed:
a ~service:Routes.log [ txt "Current workout" ] ();
50
Removed:
a ~service:Routes.history [ txt "History" ] ();
51
Removed:
];
52
Removed:
]
53
Removed:
@ content))
54
Added:
~a:[ a_class [ "hito-app" ] ]
55
Added:
[
56
Added:
div
57
Added:
~a:[ a_class [ "app-shell" ] ]
58
Added:
[
59
Added:
header
60
Added:
~a:[ a_class [ "masthead" ] ]
61
Added:
[
62
Added:
a
63
Added:
~a:[ a_class [ "brand" ] ]
64
Added:
~service:Routes.home
65
Added:
[
66
Added:
span ~a:[ a_class [ "brand-mark" ] ] [ txt "HD" ];
67
Added:
span
68
Added:
~a:[ a_class [ "brand-copy" ] ]
69
Added:
[
70
Added:
strong [ txt "hito" ];
71
Added:
small [ txt "Heavy Duty logbook" ];
72
Added:
];
73
Added:
]
74
Added:
();
75
Added:
navigation ();
76
Added:
];
77
Added:
main [ Ot_popup.hcf ~a:[ a_class [ "page-surface" ] ] content ];
78
Added:
];
79
Added:
])
54
80
55
81
let problem ~title:t ~detail =
56
Removed:
shell ~title:t [ h2 [ txt t ]; p ~a:[ a_class [ "warn" ] ] [ txt detail ] ]
82
Added:
shell ~title:t
83
Added:
[
84
Added:
p ~a:[ a_class [ "eyebrow" ] ] [ txt "Attention required" ];
85
Added:
h2 [ txt t ];
86
Added:
p ~a:[ a_class [ "warn" ] ] [ txt detail ];
87
Added:
]
57
88
58
89
(* A prescribed stimulus described in words: the movements, and the rep window
59
90
that calibrates the load. *)
lib/web/routes.ml
@@ -30,6 +30,12 @@
30
30
Eliom_service.create ~path:(Eliom_service.Path [ "" ])
31
31
~meth:(Eliom_service.Get Eliom_parameter.unit) ()
32
32
33
Added:
(* GET /assets/hito.css — the application stylesheet. *)
34
Added:
let stylesheet =
35
Added:
Eliom_service.create
36
Added:
~path:(Eliom_service.Path [ "assets"; "hito.css" ])
37
Added:
~meth:(Eliom_service.Get Eliom_parameter.unit) ()
38
Added:
33
39
(* GET /log — the workout in progress. *)
34
40
let log =
35
41
Eliom_service.create ~path:(Eliom_service.Path [ "log" ])
lib/web/services.ml
@@ -29,7 +29,15 @@
29
29
Pages.problem ~title:"Routine unavailable"
30
30
~detail:(Format.asprintf "%a" Service.pp_error error)))
31
31
32
Added:
let stylesheet =
33
Added:
match Stylesheet.read "hito.css" with
34
Added:
| Some stylesheet -> stylesheet
35
Added:
| None -> failwith "Embedded stylesheet hito.css is missing"
36
Added:
32
37
let register () =
38
Added:
Eliom_registration.CssText.register ~service:Routes.stylesheet (fun () () ->
39
Added:
Lwt.return stylesheet);
40
Added:
33
41
Eliom_registration.Html.register ~service:Routes.home (fun () () ->
34
42
Lwt.return (home_page ()));
35
43