forked from
anil.recoil.org/monopam-myspace
My aggregated monorepo of OCaml code, automaintained
1<!DOCTYPE html>
2<html lang="en">
3<head>
4<meta charset="utf-8">
5<meta name="viewport" content="width=device-width, initial-scale=1">
6<title>jon.recoil.org — mockup</title>
7<link rel="preconnect" href="https://fonts.googleapis.com">
8<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9<link href="https://fonts.googleapis.com/css2?family=Newsreader:ital,opsz,wght@0,6..72,400;0,6..72,500;0,6..72,600;1,6..72,400;1,6..72,500&family=DM+Sans:ital,wght@0,400;0,500;0,600;1,400&display=swap" rel="stylesheet">
10<style>
11*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
12
13:root {
14 --text: #1a1a2e;
15 --text-secondary: #555e6e;
16 --text-tertiary: #8891a0;
17 --bg: #faf9f7;
18 --surface: #fff;
19 --border: #e5e2dc;
20 --accent: #b44e2d;
21 --accent-hover: #943e22;
22 --link: #2a6496;
23 --link-hover: #1d4a6e;
24 --tag-bg: #f0ede8;
25 --tag-text: #6b6358;
26 --max-w: 680px;
27 --wide-w: 960px;
28}
29
30@media (prefers-color-scheme: dark) {
31 :root {
32 --text: #e2dfd8;
33 --text-secondary: #a8a299;
34 --text-tertiary: #76716a;
35 --bg: #1a1917;
36 --surface: #242320;
37 --border: #3a3834;
38 --accent: #d4734f;
39 --accent-hover: #e0896a;
40 --link: #6aaddb;
41 --link-hover: #8dc2e6;
42 --tag-bg: #2e2c28;
43 --tag-text: #a8a299;
44 }
45}
46
47html {
48 font-size: 17px;
49 -webkit-font-smoothing: antialiased;
50}
51
52body {
53 font-family: 'DM Sans', -apple-system, sans-serif;
54 background: var(--bg);
55 color: var(--text);
56 line-height: 1.6;
57}
58
59a { color: var(--link); text-decoration: none; }
60a:hover { color: var(--link-hover); }
61
62/* ── Nav (kept close to existing) ── */
63.nav {
64 border-bottom: 1px solid var(--border);
65}
66.nav-inner {
67 max-width: var(--wide-w);
68 margin: 0 auto;
69 padding: 16px 24px;
70 display: flex;
71 align-items: center;
72 justify-content: space-between;
73 font-size: 14px;
74}
75.nav-brand {
76 font-weight: 600;
77 color: var(--text);
78 text-decoration: none;
79}
80.nav-brand:hover { color: var(--accent); }
81.nav-links { display: flex; gap: 20px; }
82.nav-links a { color: var(--text-secondary); }
83.nav-links a:hover { color: var(--link); }
84
85/* ── Hero ── */
86.hero {
87 max-width: var(--wide-w);
88 margin: 0 auto;
89 padding: 56px 24px 48px;
90 display: grid;
91 grid-template-columns: 1fr auto;
92 gap: 40px;
93 align-items: start;
94}
95
96.hero-text h1 {
97 font-family: 'Newsreader', Georgia, serif;
98 font-size: 2.4rem;
99 font-weight: 500;
100 line-height: 1.2;
101 letter-spacing: -0.02em;
102 margin-bottom: 16px;
103 color: var(--text);
104}
105
106.hero-text h1 em {
107 font-style: italic;
108 color: var(--accent);
109}
110
111.hero-bio {
112 font-size: 1.05rem;
113 color: var(--text-secondary);
114 line-height: 1.65;
115 max-width: 540px;
116}
117
118.hero-bio a {
119 color: var(--text);
120 text-decoration: underline;
121 text-decoration-color: var(--border);
122 text-underline-offset: 3px;
123 text-decoration-thickness: 1.5px;
124}
125.hero-bio a:hover {
126 text-decoration-color: var(--accent);
127 color: var(--accent);
128}
129
130.hero-photo {
131 width: 120px;
132 height: 120px;
133 border-radius: 50%;
134 object-fit: cover;
135 border: 3px solid var(--bg);
136 box-shadow: 0 0 0 1px var(--border);
137 margin-top: 8px;
138}
139
140/* ── Section divider ── */
141.divider {
142 max-width: var(--wide-w);
143 margin: 0 auto;
144 padding: 0 24px;
145}
146.divider hr {
147 border: none;
148 border-top: 1px solid var(--border);
149}
150
151/* ── Posts section ── */
152.posts-section {
153 max-width: var(--wide-w);
154 margin: 0 auto;
155 padding: 40px 24px 48px;
156}
157
158.section-header {
159 display: flex;
160 align-items: baseline;
161 justify-content: space-between;
162 margin-bottom: 28px;
163}
164
165.section-title {
166 font-family: 'Newsreader', Georgia, serif;
167 font-size: 1.35rem;
168 font-weight: 500;
169 color: var(--text);
170 letter-spacing: -0.01em;
171}
172
173.section-link {
174 font-size: 0.85rem;
175 color: var(--text-tertiary);
176}
177.section-link:hover { color: var(--accent); }
178
179/* ── Featured post ── */
180.featured {
181 background: var(--surface);
182 border: 1px solid var(--border);
183 border-radius: 10px;
184 padding: 28px 32px;
185 margin-bottom: 24px;
186 transition: box-shadow 0.2s ease;
187}
188.featured:hover {
189 box-shadow: 0 2px 12px rgba(0,0,0,0.06);
190}
191
192.featured-label {
193 font-size: 0.72rem;
194 font-weight: 600;
195 letter-spacing: 0.08em;
196 text-transform: uppercase;
197 color: var(--accent);
198 margin-bottom: 10px;
199}
200
201.featured h3 {
202 font-family: 'Newsreader', Georgia, serif;
203 font-size: 1.5rem;
204 font-weight: 500;
205 line-height: 1.3;
206 margin-bottom: 8px;
207}
208.featured h3 a {
209 color: var(--text);
210}
211.featured h3 a:hover {
212 color: var(--accent);
213}
214
215.featured-excerpt {
216 color: var(--text-secondary);
217 font-size: 0.95rem;
218 line-height: 1.6;
219 margin-bottom: 12px;
220}
221
222.featured-meta {
223 font-size: 0.8rem;
224 color: var(--text-tertiary);
225 display: flex;
226 align-items: center;
227 gap: 12px;
228}
229
230.tag {
231 display: inline-block;
232 background: var(--tag-bg);
233 color: var(--tag-text);
234 font-size: 0.72rem;
235 font-weight: 500;
236 padding: 3px 9px;
237 border-radius: 4px;
238 letter-spacing: 0.02em;
239}
240
241/* ── Post list ── */
242.post-list {
243 display: flex;
244 flex-direction: column;
245 gap: 1px;
246 background: var(--border);
247 border-radius: 10px;
248 overflow: hidden;
249 border: 1px solid var(--border);
250}
251
252.post-item {
253 display: grid;
254 grid-template-columns: 1fr auto;
255 gap: 16px;
256 align-items: baseline;
257 padding: 16px 24px;
258 background: var(--surface);
259 transition: background 0.15s ease;
260}
261.post-item:hover {
262 background: var(--tag-bg);
263}
264
265.post-title {
266 font-family: 'Newsreader', Georgia, serif;
267 font-size: 1.05rem;
268 font-weight: 400;
269 line-height: 1.4;
270}
271.post-title a {
272 color: var(--text);
273}
274.post-title a:hover {
275 color: var(--accent);
276}
277
278.post-date {
279 font-size: 0.78rem;
280 color: var(--text-tertiary);
281 white-space: nowrap;
282 font-variant-numeric: tabular-nums;
283}
284
285/* ── Highlights / cards ── */
286.highlights {
287 max-width: var(--wide-w);
288 margin: 0 auto;
289 padding: 0 24px 48px;
290 display: grid;
291 grid-template-columns: repeat(3, 1fr);
292 gap: 16px;
293}
294
295.highlight-card {
296 background: var(--surface);
297 border: 1px solid var(--border);
298 border-radius: 10px;
299 padding: 24px;
300 transition: box-shadow 0.2s ease, transform 0.2s ease;
301}
302.highlight-card:hover {
303 box-shadow: 0 2px 12px rgba(0,0,0,0.06);
304 transform: translateY(-1px);
305}
306
307.highlight-icon {
308 font-size: 1.6rem;
309 margin-bottom: 12px;
310 display: block;
311 line-height: 1;
312}
313
314.highlight-card h3 {
315 font-family: 'Newsreader', Georgia, serif;
316 font-size: 1.05rem;
317 font-weight: 500;
318 margin-bottom: 6px;
319}
320.highlight-card h3 a { color: var(--text); }
321.highlight-card h3 a:hover { color: var(--accent); }
322
323.highlight-card p {
324 font-size: 0.85rem;
325 color: var(--text-secondary);
326 line-height: 1.55;
327}
328
329/* ── Footer ── */
330.footer {
331 max-width: var(--wide-w);
332 margin: 0 auto;
333 padding: 24px 24px 40px;
334 border-top: 1px solid var(--border);
335 font-size: 0.8rem;
336 color: var(--text-tertiary);
337 display: flex;
338 justify-content: space-between;
339 align-items: center;
340}
341
342.footer a { color: var(--text-tertiary); }
343.footer a:hover { color: var(--accent); }
344
345/* ── Animations ── */
346@keyframes fadeUp {
347 from { opacity: 0; transform: translateY(12px); }
348 to { opacity: 1; transform: translateY(0); }
349}
350
351.hero, .featured, .post-list, .highlights, .section-header {
352 animation: fadeUp 0.5s ease both;
353}
354.featured { animation-delay: 0.05s; }
355.post-list { animation-delay: 0.1s; }
356.highlights .highlight-card:nth-child(1) { animation: fadeUp 0.5s ease 0.15s both; }
357.highlights .highlight-card:nth-child(2) { animation: fadeUp 0.5s ease 0.2s both; }
358.highlights .highlight-card:nth-child(3) { animation: fadeUp 0.5s ease 0.25s both; }
359
360/* ── Responsive ── */
361@media (max-width: 700px) {
362 .hero {
363 grid-template-columns: 1fr;
364 padding: 36px 20px 32px;
365 gap: 20px;
366 }
367 .hero-photo {
368 width: 80px;
369 height: 80px;
370 order: -1;
371 }
372 .hero-text h1 { font-size: 1.8rem; }
373 .highlights {
374 grid-template-columns: 1fr;
375 }
376 .post-item {
377 grid-template-columns: 1fr;
378 gap: 4px;
379 }
380 .featured { padding: 20px; }
381}
382</style>
383</head>
384<body>
385
386<!-- Nav (matching existing site structure) -->
387<nav class="nav">
388 <div class="nav-inner">
389 <a href="/" class="nav-brand">jon.recoil.org</a>
390 <div class="nav-links">
391 <a href="/blog/">blog</a>
392 <a href="/notebooks/">notebooks</a>
393 <a href="/projects/">projects</a>
394 <a href="/reference/">reference</a>
395 </div>
396 </div>
397</nav>
398
399<!-- Hero -->
400<section class="hero">
401 <div class="hero-text">
402 <h1>Jon Ludlam</h1>
403 <p class="hero-bio">
404 Fellow of <a href="https://www.chu.cam.ac.uk/">Churchill College</a>,
405 Senior Research Associate at Cambridge's
406 <a href="https://www.cl.cam.ac.uk/">Department of Computer Science</a>,
407 and Professional Old Dude at <a href="https://tarides.com/">Tarides</a>.
408 I work on <a href="https://github.com/ocaml/odoc">odoc</a>, OCaml documentation tooling,
409 and interactive computing in the browser.
410 </p>
411 </div>
412 <img src="/static/assets/jon.jpg" alt="Jon Ludlam" class="hero-photo">
413</section>
414
415<div class="divider"><hr></div>
416
417<!-- Recent writing -->
418<section class="posts-section">
419 <div class="section-header">
420 <h2 class="section-title">Recent writing</h2>
421 <a href="/blog/" class="section-link">All posts →</a>
422 </div>
423
424 <a href="/blog/2025/12/an-svg-is-all-you-need.html" style="text-decoration:none; display:block;">
425 <div class="featured">
426 <div class="featured-label">Featured</div>
427 <h3><a href="/blog/2025/12/an-svg-is-all-you-need.html">An SVG is all you need</a></h3>
428 <p class="featured-excerpt">
429 SVGs are way more capable than many people realise. A completely self-contained SVG file can
430 fetch data, generate visualisations, and render interactive controls — all client-side,
431 no server required. Including one I made 20 years ago that still works.
432 </p>
433 <div class="featured-meta">
434 <span>December 2025</span>
435 <span class="tag">research</span>
436 <span class="tag">visualisation</span>
437 </div>
438 </div>
439 </a>
440
441 <div class="post-list">
442 <div class="post-item">
443 <div class="post-title"><a href="/blog/2026/03/weeknotes-2026-09.html">Weeknotes 2026 week 9</a></div>
444 <span class="post-date">Mar 2026</span>
445 </div>
446 <div class="post-item">
447 <div class="post-title"><a href="/blog/2026/02/weeknotes-2026-08.html">Weeknotes weeks 7–8</a></div>
448 <span class="post-date">Feb 2026</span>
449 </div>
450 <div class="post-item">
451 <div class="post-title"><a href="/blog/2025/12/claude-and-dune.html">Claude and Dune</a></div>
452 <span class="post-date">Dec 2025</span>
453 </div>
454 <div class="post-item">
455 <div class="post-title"><a href="/blog/2025/11/foundations-of-computer-science.html">Foundations of Computer Science</a></div>
456 <span class="post-date">Nov 2025</span>
457 </div>
458 <div class="post-item">
459 <div class="post-title"><a href="/blog/2025/09/caching-opam-solutions2.html">Caching opam solutions, part 2</a></div>
460 <span class="post-date">Sep 2025</span>
461 </div>
462 <div class="post-item">
463 <div class="post-title"><a href="/blog/2025/08/ocaml-lsp-mcp.html">Using ocaml-lsp-server via an MCP server</a></div>
464 <span class="post-date">Aug 2025</span>
465 </div>
466 <div class="post-item">
467 <div class="post-title"><a href="/blog/2025/07/odoc-3-live-on-ocaml-org.html">Odoc 3 is live on OCaml.org!</a></div>
468 <span class="post-date">Jul 2025</span>
469 </div>
470 <div class="post-item">
471 <div class="post-title"><a href="/blog/2025/04/odoc-3.html">Odoc 3: So what?</a></div>
472 <span class="post-date">Apr 2025</span>
473 </div>
474 </div>
475</section>
476
477<div class="divider"><hr></div>
478
479<!-- Highlights -->
480<section class="posts-section" style="padding-bottom: 24px;">
481 <div class="section-header">
482 <h2 class="section-title">Explore</h2>
483 </div>
484</section>
485
486<div class="highlights">
487 <div class="highlight-card">
488 <span class="highlight-icon">λ</span>
489 <h3><a href="/notebooks/">Interactive notebooks</a></h3>
490 <p>OCaml code running live in the browser. Foundations of Computer Science supervision material, ONNX Runtime inference, and more.</p>
491 </div>
492 <div class="highlight-card">
493 <span class="highlight-icon">¶</span>
494 <h3><a href="/reference/">Reference docs</a></h3>
495 <p>Package documentation built with odoc 3. Browse type signatures, module hierarchies, and search across everything.</p>
496 </div>
497 <div class="highlight-card">
498 <span class="highlight-icon">§</span>
499 <h3><a href="/blog/2025/04/this-site.html">About this site</a></h3>
500 <p>Built entirely with odoc — the OCaml documentation generator. Because when you have a hammer, everything looks like a nail.</p>
501 </div>
502</div>
503
504<!-- Footer -->
505<footer class="footer">
506 <span>jon ludlam</span>
507 <span>Built with <a href="https://github.com/ocaml/odoc">odoc</a></span>
508</footer>
509
510</body>
511</html>