add liveline page: recreation of benji.org/liveline editorial design

Recreates the benji.org/liveline page in the website folder using React,
Tailwind, and the same editorial design system. Charts are replaced with
static SVG placeholders.

Typography:
- Inter variable font from rsms.me (same source as next/font)
- font-weight 475 body, 560 headings, font-optical-sizing: auto
- 14px body, 20px line-height, -0.09px letter-spacing
- Near-black text rgb(17,17,17), 40% opacity secondary
- Display P3 accent colors with @supports fallback

Layout:
- 550px narrow content column, fixed TOC sidebar
- Stagger-in entrance animation with cascading delays
- Fixed header fade gradient (multi-stop white-to-transparent)
- Scroll-spy TOC highlighting via IntersectionObserver

Code blocks:
- Prism.js syntax highlighting with GitHub-light color palette
- Line numbers column with 20px right padding
- 12px / 18px font size

Props tables, dividers, captions, code blocks, bullet lists all
styled to match the original computed values.
This commit is contained in:
Tommy D. Rossi
2026-02-19 09:56:30 +01:00
parent 38098c6153
commit a8481af89b
6 changed files with 1308 additions and 2 deletions
+147
View File
@@ -0,0 +1,147 @@
/*
* Liveline page styles - recreating benji.org's editorial design system.
*
* Key design decisions from the original:
* - Inter variable font at weight 460 (between normal and medium)
* - Near-black text (#111) not pure black, with 40% opacity for secondary
* - Narrow 550px content column for optimal reading
* - Negative letter-spacing on headings for tight, refined feel
* - 7-layer box shadows for realistic depth
* - Display P3 accent colors (wider gamut than sRGB)
* - Staggered entrance animations with subtle translateY
* - Custom cubic-bezier easings with slight overshoot ("snappy", "swift")
* - Fading header gradient pseudo-element
* - Small font sizes (14px body) for editorial/compact aesthetic
*/
/* ========================================================================
Design tokens
======================================================================== */
.liveline-page {
--ll-font-primary: "Inter var", "Inter", system-ui, -apple-system,
BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--ll-font-secondary: "Newsreader", Georgia, "Times New Roman", serif;
--ll-font-code: "SF Mono", "SFMono-Regular", "Consolas", "Liberation Mono",
Menlo, Courier, monospace;
/* Spacing scale */
--ll-spacing-xxs: 0.5rem;
--ll-spacing-xs: 1rem;
--ll-spacing-sm: 1.5rem;
--ll-spacing-md: 2rem;
--ll-spacing-lg: 2.5rem;
--ll-spacing-xl: 3rem;
--ll-spacing-xxl: 3.5rem;
/* Timing */
--ll-duration-snappy: 220ms;
--ll-ease-snappy: cubic-bezier(0.175, 0.885, 0.32, 1.1);
--ll-duration-swift: 800ms;
--ll-ease-swift: cubic-bezier(0.175, 0.885, 0.32, 1.275);
--ll-duration-smooth: 300ms;
--ll-ease-smooth: cubic-bezier(0.19, 1, 0.22, 1);
/* Colors - near-black, not pure black */
--ll-text-primary: rgb(17, 17, 17);
--ll-text-secondary: rgba(0, 0, 0, 0.4);
--ll-text-tertiary: rgba(0, 0, 0, 0.25);
--ll-bg: #fff;
--ll-border: #e3e3e3;
--ll-code-bg: rgba(0, 0, 0, 0.02);
--ll-selection-bg: rgba(0, 0, 0, 0.08);
/* P3 accent colors (wider gamut) */
--ll-primary: #3e9fff;
--ll-secondary: #f09637;
/* Overlay / glass */
--ll-overlay-filter: blur(1rem);
--ll-overlay-bg: hsla(0, 0%, 100%, 0.8);
--ll-overlay-shadow: 0 0 0 1px rgba(0, 0, 0, 0.04),
0 1.625rem 3.375rem rgba(0, 0, 0, 0.04),
0 1rem 2rem rgba(0, 0, 0, 0.03),
0 0.625rem 1rem rgba(0, 0, 0, 0.024),
0 0.3125rem 0.5rem rgba(0, 0, 0, 0.02),
0 0.125rem 0.25rem rgba(0, 0, 0, 0.016),
0 0 0.125rem rgba(0, 0, 0, 0.01);
}
@supports (color: color(display-p3 1 1 1)) {
.liveline-page {
--ll-primary: color(display-p3 0.243137 0.623529 1 / 1);
--ll-secondary: color(display-p3 0.941176 0.588235 0.215686 / 1);
}
}
/* ========================================================================
Selection
======================================================================== */
.liveline-page {
font-optical-sizing: auto;
font-feature-settings: "liga" 1, "calt" 1;
}
.liveline-page ::selection {
background: var(--ll-selection-bg);
}
/* ========================================================================
Stagger-in animation
======================================================================== */
@keyframes ll-stagger-in {
0% {
opacity: 0;
transform: translateY(8px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
.liveline-article > * {
animation: ll-stagger-in 0.5s ease both;
}
.liveline-article > :nth-child(1) { animation-delay: 0s; }
.liveline-article > :nth-child(2) { animation-delay: 0.05s; }
.liveline-article > :nth-child(3) { animation-delay: 0.1s; }
.liveline-article > :nth-child(4) { animation-delay: 0.15s; }
.liveline-article > :nth-child(5) { animation-delay: 0.2s; }
.liveline-article > :nth-child(6) { animation-delay: 0.25s; }
.liveline-article > :nth-child(7) { animation-delay: 0.3s; }
.liveline-article > :nth-child(8) { animation-delay: 0.35s; }
.liveline-article > :nth-child(n + 9) { animation-delay: 0.4s; }
/* ========================================================================
Header fade gradient
======================================================================== */
.liveline-page::before {
content: "";
pointer-events: none;
z-index: 9;
position: fixed;
top: 0;
left: 0;
right: 0;
height: 8rem;
background: linear-gradient(
rgb(255, 255, 255) 0px,
rgba(255, 255, 255, 0.737) 19%,
rgba(255, 255, 255, 0.541) 34%,
rgba(255, 255, 255, 0.382) 47%,
rgba(255, 255, 255, 0.278) 56.5%,
rgba(255, 255, 255, 0.194) 65%,
rgba(255, 255, 255, 0.126) 73%,
rgba(255, 255, 255, 0.075) 80.2%,
rgba(255, 255, 255, 0.042) 86.1%,
rgba(255, 255, 255, 0.021) 91%,
rgba(255, 255, 255, 0.008) 95.2%,
rgba(255, 255, 255, 0.002) 98.2%,
rgba(255, 255, 255, 0) 100%
);
}