My aggregated monorepo of OCaml code, automaintained
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 511 lines 13 kB view raw
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 &rarr;</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 &mdash; 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&ndash;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">&lambda;</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">&para;</span> 494 <h3><a href="/reference/">Reference docs</a></h3> 495 <p>Package documentation built with odoc&nbsp;3. Browse type signatures, module hierarchies, and search across everything.</p> 496 </div> 497 <div class="highlight-card"> 498 <span class="highlight-icon">&sect;</span> 499 <h3><a href="/blog/2025/04/this-site.html">About this site</a></h3> 500 <p>Built entirely with odoc &mdash; the OCaml documentation generator. Because when you have a hammer, everything looks like a&nbsp;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>