From 68ff64e78dc0c57052073fb89482aac9cf9a1a62 Mon Sep 17 00:00:00 2001 From: James DGX Spark Date: Wed, 15 Jul 2026 08:57:27 -0600 Subject: [PATCH 1/2] feat: introduce optical research console theme --- .gitignore | 1 + DESIGN.md | 194 ++++++++++++++++++++++++++++++--- package-lock.json | 26 ++--- package.json | 4 +- src/layouts/SiteLayout.astro | 13 ++- src/styles/global.css | 206 +++++++++++++++++++++-------------- src/styles/typeset.css | 1 + 7 files changed, 325 insertions(+), 120 deletions(-) diff --git a/.gitignore b/.gitignore index 9d19539..cae2205 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ pnpm-debug.log* .playwright-libs/ home-desktop*.png entry-mobile*.png +optical-*.png .wrangler/ # impeccable-ignore-start diff --git a/DESIGN.md b/DESIGN.md index 000ae9f..36a867d 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -1,24 +1,184 @@ -# Design +--- +version: alpha +name: Optical Research Console +description: A precise research instrument that remains calm and highly readable for long technical reports. +colors: + primary: "#071513" + secondary: "#536764" + tertiary: "#007D78" + neutral: "#F3F7F6" + surface: "#FFFFFF" + rule: "#C9D6D3" + signal: "#16C8BD" + evidenceText: "#C3FFF8" + positive: "#117A4E" + warning: "#C88912" + danger: "#C14343" + mediaBlack: "#030A09" +typography: + h1: + fontFamily: IBM Plex Sans Variable + fontSize: 6.6rem + fontWeight: 620 + lineHeight: 0.94 + letterSpacing: "-0.052em" + h2: + fontFamily: IBM Plex Sans Variable + fontSize: 3rem + fontWeight: 600 + lineHeight: 1 + letterSpacing: "-0.035em" + body-md: + fontFamily: Newsreader Variable + fontSize: 1.125rem + fontWeight: 400 + lineHeight: 1.8 + interface: + fontFamily: IBM Plex Sans Variable + fontSize: 1rem + fontWeight: 400 + lineHeight: 1.6 + body-sm: + fontFamily: IBM Plex Sans Variable + fontSize: 0.94rem + fontWeight: 400 + lineHeight: 1.65 + interface-sm: + fontFamily: IBM Plex Sans Variable + fontSize: 0.8rem + fontWeight: 400 + lineHeight: 1.6 + label: + fontFamily: IBM Plex Sans Variable + fontSize: 0.78rem + fontWeight: 650 + lineHeight: 1.2 + caption: + fontFamily: IBM Plex Sans Variable + fontSize: 0.7rem + fontWeight: 400 + lineHeight: 1.5 + micro: + fontFamily: IBM Plex Mono + fontSize: 0.56rem + fontWeight: 500 + lineHeight: 1.2 + metadata: + fontFamily: IBM Plex Mono + fontSize: 0.625rem + fontWeight: 500 + lineHeight: 1.2 + letterSpacing: 0.08em +rounded: + sm: 0px + md: 0px + lg: 0px + focus: 0.125em + mark: 0.2em + inline: 0.35em + code: 0.5em + media: 0.6em +spacing: + xs: 4px + sm: 8px + md: 16px + lg: 32px + xl: 64px +components: + filter-active: + backgroundColor: "{colors.primary}" + textColor: "{colors.neutral}" + rounded: "{rounded.sm}" + padding: 8px + evidence-label: + backgroundColor: "{colors.primary}" + textColor: "{colors.evidenceText}" + typography: "{typography.metadata}" + rounded: "{rounded.sm}" + padding: 6px + focus-control: + backgroundColor: "{colors.neutral}" + textColor: "{colors.primary}" + rounded: "{rounded.sm}" + padding: 8px + separator: + backgroundColor: "{colors.rule}" + textColor: "{colors.primary}" + rounded: "{rounded.sm}" + padding: 4px + signal-label: + backgroundColor: "{colors.primary}" + textColor: "{colors.signal}" + typography: "{typography.micro}" + rounded: "{rounded.sm}" + padding: 4px + status-positive: + backgroundColor: "{colors.positive}" + textColor: "{colors.surface}" + rounded: "{rounded.sm}" + padding: 4px + status-warning: + backgroundColor: "{colors.warning}" + textColor: "{colors.primary}" + rounded: "{rounded.sm}" + padding: 4px + status-danger: + backgroundColor: "{colors.danger}" + textColor: "{colors.surface}" + rounded: "{rounded.sm}" + padding: 4px + media-stage: + backgroundColor: "{colors.mediaBlack}" + textColor: "{colors.evidenceText}" + rounded: "{rounded.sm}" + padding: 4px +--- -## Direction -An engineering journal with the pacing of a high-end independent publication: strong editorial type, evidence-sized media, technical labels, and hairline rules. Layout—not decoration—creates hierarchy. +## Overview + +Optical Research Console treats the progress archive as a scientific instrument, not a magazine and not a science-fiction dashboard. The primary surface is **Monitor**: readers scan current state, open investigations, and inspect evidence. Dense labels, calibrated rules, coordinate grids, and tabular numerals establish technical credibility; generous reading measures and a dedicated long-form serif preserve comfort. + +## Colors + +- **Primary** is a deep green-black used for core text and the lead investigation slab. +- **Neutral** is a cool laboratory canvas rather than warm paper. +- **Tertiary** is the accessible interaction accent. Use it for links, focus, indices, and measured status. +- **Signal** is a brighter spectral cyan reserved for dark surfaces and evidence labels. +- Grid lines use a low-opacity mixture of Primary. They organize space but must never compete with text. +- Dark mode keeps the green-tinted optical character rather than becoming neutral charcoal. ## Typography -- Display: Newsreader Variable, 420–500 weight, tight optical display tracking. -- Interface: Manrope Variable, compact and direct. -- Metadata/code: JetBrains Mono Variable. -- Rendered Markdown: shadcn/typeset with Newsreader body, Manrope headings, and an 18px / 1.78 reading rhythm. -## Color -Warm paper (`#f2f0e9`), green-black ink (`#191d1b`), vermilion action/evidence accent (`#e34a29`), restrained green for verified/published state. Dark mode keeps the same tinted character. +- **IBM Plex Sans Variable** carries titles and interface copy. It feels engineered without sacrificing legibility. +- **Newsreader Variable** is reserved for long report bodies. Research findings should remain comfortable over thousands of words. +- **IBM Plex Mono** carries metadata, experiment IDs, dates, status, and tabular values. Never use mono for paragraphs. +- Headings are assertive but not monumental. The hierarchy comes from weight, alignment, and information density—not decorative display typography. + +## Layout + +- Homepage: calibrated masthead, one dark lead investigation paired with primary evidence, then compact ruled archive rows. +- Entry: technical title field with subtle vertical coordinates, full-width evidence, then a 760px reading column. +- Imagery is evidence and should be shown large enough to inspect. +- Use square corners, hairline rules, and explicit alignment. Avoid floating cards and arbitrary elevation. +- Mobile preserves the reading sequence: status, title, evidence, report. Tables scroll horizontally rather than compressing numeric columns. + +## Elevation & Depth + +No box shadows and no glass layers. Depth is communicated by ink-on-canvas contrast, full-bleed evidence, rules, and surface changes. The site should resemble a calibrated optical workstation rather than a stack of cards. + +## Shapes + +Application chrome uses square corners. Small proportional radii in the inherited Typeset layer remain available for focus outlines, code, keys, and inline semantics. The only circles are semantic status indicators. The crosshair wordmark and corner registration marks are functional visual signatures for the research system. + +## Components + +- Filters are outlined metadata controls; active filters invert to Primary. +- Evidence labels sit directly on dark image/video surfaces using Signal text. +- Status colors must accompany text labels and never communicate meaning alone. +- Focus states use Tertiary with sufficient contrast and must remain visible in both themes. -## Composition -- Homepage: large journal masthead, one lead investigation, then ruled archive rows. -- Entry: editorial title and deck, full-width evidence, then a narrow 760px reading measure. -- Never place experiment imagery in tiny decorative thumbnails when it is the evidence. +## Do's and Don'ts -## Motion -Only subtle image scale and state transitions. Respect reduced motion. +**Do** use spectral cyan sparingly, show real experiment outputs, favor tabular numerals, preserve article measure, and let rules establish structure. -## Accessibility -Visible focus, semantic hierarchy, 44px-equivalent mobile controls, readable contrast, native video controls, captions/alt text supported in the media schema. +**Don't** use neon-on-black for full articles, blue-violet gradients, glassmorphism, pill-shaped UI, fake telemetry, tiny decorative evidence, or mono body copy. diff --git a/package-lock.json b/package-lock.json index 9e971a2..ef4e457 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,9 @@ "version": "0.0.1", "dependencies": { "@astrojs/mdx": "^7.0.3", - "@fontsource-variable/jetbrains-mono": "^5.2.8", - "@fontsource-variable/manrope": "^5.2.8", + "@fontsource-variable/ibm-plex-sans": "^5.2.8", "@fontsource-variable/newsreader": "^5.2.10", + "@fontsource/ibm-plex-mono": "^5.2.7", "astro": "^7.0.9", "sharp": "^0.35.3" }, @@ -1288,28 +1288,28 @@ "node": ">=18" } }, - "node_modules/@fontsource-variable/jetbrains-mono": { + "node_modules/@fontsource-variable/ibm-plex-sans": { "version": "5.2.8", - "resolved": "https://registry.npmjs.org/@fontsource-variable/jetbrains-mono/-/jetbrains-mono-5.2.8.tgz", - "integrity": "sha512-WBA9elru6Jdp5df2mES55wuOO0WIrn3kpXnI4+W2ek5u3ZgLS9XS4gmIlcQhiZOWEKl95meYdvK7xI+ETLCq/Q==", + "resolved": "https://registry.npmjs.org/@fontsource-variable/ibm-plex-sans/-/ibm-plex-sans-5.2.8.tgz", + "integrity": "sha512-n5PF2iFa0CZT0QYTPzxvZ39opC9LnU0zdoRccoADbs+Dtsd+lbXOZF7RNuIPHcQX1dKjF63sxnRImQIB5eD0Ag==", "license": "OFL-1.1", "funding": { "url": "https://github.com/sponsors/ayuhito" } }, - "node_modules/@fontsource-variable/manrope": { - "version": "5.2.8", - "resolved": "https://registry.npmjs.org/@fontsource-variable/manrope/-/manrope-5.2.8.tgz", - "integrity": "sha512-nc9lOuCRz73UHnovDE2bwXUdghE2SEOc7Aii0qGe3CLyE03W1a7VnY5Z6euRiapiKbCkGS+eXbY3s/kvWeGeSw==", + "node_modules/@fontsource-variable/newsreader": { + "version": "5.2.10", + "resolved": "https://registry.npmjs.org/@fontsource-variable/newsreader/-/newsreader-5.2.10.tgz", + "integrity": "sha512-MdI2iRwrqWpMOU/2aV2BgfZ4dJNlj/XlYaY8Zb7t87mWqHskYW0XiUANkt1cyOCiEfW2VQ0bQ5vZgGPp6B2B4w==", "license": "OFL-1.1", "funding": { "url": "https://github.com/sponsors/ayuhito" } }, - "node_modules/@fontsource-variable/newsreader": { - "version": "5.2.10", - "resolved": "https://registry.npmjs.org/@fontsource-variable/newsreader/-/newsreader-5.2.10.tgz", - "integrity": "sha512-MdI2iRwrqWpMOU/2aV2BgfZ4dJNlj/XlYaY8Zb7t87mWqHskYW0XiUANkt1cyOCiEfW2VQ0bQ5vZgGPp6B2B4w==", + "node_modules/@fontsource/ibm-plex-mono": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/@fontsource/ibm-plex-mono/-/ibm-plex-mono-5.2.7.tgz", + "integrity": "sha512-MKAb8qV+CaiMQn2B0dIi1OV3565NYzp3WN5b4oT6LTkk+F0jR6j0ZN+5BKJiIhffDC3rtBULsYZE65+0018z9w==", "license": "OFL-1.1", "funding": { "url": "https://github.com/sponsors/ayuhito" diff --git a/package.json b/package.json index f1122cb..0e0aa12 100644 --- a/package.json +++ b/package.json @@ -17,9 +17,9 @@ }, "dependencies": { "@astrojs/mdx": "^7.0.3", - "@fontsource-variable/jetbrains-mono": "^5.2.8", - "@fontsource-variable/manrope": "^5.2.8", + "@fontsource-variable/ibm-plex-sans": "^5.2.8", "@fontsource-variable/newsreader": "^5.2.10", + "@fontsource/ibm-plex-mono": "^5.2.7", "astro": "^7.0.9", "sharp": "^0.35.3" }, diff --git a/src/layouts/SiteLayout.astro b/src/layouts/SiteLayout.astro index b2e3ded..2825fe8 100644 --- a/src/layouts/SiteLayout.astro +++ b/src/layouts/SiteLayout.astro @@ -1,7 +1,8 @@ --- -import '@fontsource-variable/manrope/index.css'; +import '@fontsource-variable/ibm-plex-sans/index.css'; import '@fontsource-variable/newsreader/index.css'; -import '@fontsource-variable/jetbrains-mono/index.css'; +import '@fontsource/ibm-plex-mono/400.css'; +import '@fontsource/ibm-plex-mono/500.css'; import '../styles/global.css'; interface Props { @@ -21,7 +22,7 @@ const pageTitle = title === 'Research Progress' ? title : `${title} — Research - + {pageTitle} diff --git a/src/pages/journal/[cursor].json.ts b/src/pages/journal/[cursor].json.ts new file mode 100644 index 0000000..9ba7c48 --- /dev/null +++ b/src/pages/journal/[cursor].json.ts @@ -0,0 +1,30 @@ +import type { APIRoute, GetStaticPaths } from 'astro'; +import { getJournal, JOURNAL_PAGE_SIZE, toJournalCard, type JournalCard } from '../../lib/journal'; +import { paginateJournal } from '../../lib/journal-pagination.mjs'; + +interface JournalManifest { + cursor: string; + nextCursor: string | null; + entries: JournalCard[]; +} + +export const getStaticPaths = (async () => { + const { remaining } = await getJournal(); + const cards = remaining.map(toJournalCard); + const pages = paginateJournal(cards, JOURNAL_PAGE_SIZE) as JournalManifest[]; + + return pages.slice(1).map((page) => ({ + params: { cursor: page.cursor }, + props: { page }, + })); +}) satisfies GetStaticPaths; + +export const GET: APIRoute = ({ props }) => { + const { page } = props as { page: JournalManifest }; + return new Response(JSON.stringify(page), { + headers: { + 'Content-Type': 'application/json; charset=utf-8', + 'Cache-Control': 'public, max-age=300, s-maxage=31536000, immutable', + }, + }); +}; diff --git a/src/styles/global.css b/src/styles/global.css index 2b6b1e4..95c6867 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -109,7 +109,7 @@ button { font: inherit; } .filter-bar { display: flex; gap: .4rem; padding: 1.1rem 0 2rem; overflow: auto; } .filter-bar button { background: transparent; color: var(--muted); border: 1px solid var(--faint); padding: .55rem .8rem; cursor: pointer; font: 500 .6rem var(--font-mono); text-transform: uppercase; letter-spacing: .04em; } .filter-bar button:hover, .filter-bar button.active { color: var(--paper); background: var(--ink); border-color: var(--ink); } -.journal-row { border-top: 1px solid var(--faint); } +.journal-row { border-top: 1px solid var(--faint); content-visibility: auto; contain-intrinsic-size: auto 170px; } .journal-row:last-child { border-bottom: 1px solid var(--faint); } .journal-row > a { min-height: 170px; display: grid; grid-template-columns: 54px minmax(0, 1fr) minmax(130px, 220px); gap: clamp(1rem, 3vw, 3rem); align-items: center; padding: 1.35rem .2rem; text-decoration: none; } .journal-row:hover { background: var(--paper-raised); } @@ -120,6 +120,12 @@ button { font: inherit; } .row-end img { width: 100%; height: 92px; object-fit: cover; grid-column: 1 / -1; filter: saturate(.8) contrast(1.04); } .row-end > span { justify-self: end; font-size: 1rem; color: var(--accent); } .empty-filter { color: var(--muted); padding: 4rem 0; } +.journal-sentinel { min-height: 96px; display: flex; align-items: center; justify-content: space-between; gap: 1rem; border-bottom: 1px solid var(--faint); color: var(--muted); font: 500 .62rem var(--font-mono); text-transform: uppercase; letter-spacing: .05em; } +.journal-load-button { min-height: 44px; border: 1px solid var(--faint); background: transparent; color: var(--ink); padding: .7rem 1rem; cursor: pointer; font: 500 .62rem var(--font-mono); text-transform: uppercase; letter-spacing: .05em; } +.journal-load-button:hover, .journal-load-button:focus-visible { color: var(--paper); background: var(--ink); border-color: var(--ink); } +.journal-load-button:disabled { cursor: wait; opacity: .55; } +.journal-noscript { color: var(--muted); font-size: .8rem; } +.archive-head a { text-underline-offset: .2em; } .site-footer { border-top: 1px solid var(--faint); padding: 2rem clamp(1.2rem, 4vw, 4.5rem); display: flex; justify-content: space-between; color: var(--muted); font: .6rem/1.5 var(--font-mono); } .site-footer p { margin: 0; } diff --git a/tests/journal-build-contract.test.mjs b/tests/journal-build-contract.test.mjs new file mode 100644 index 0000000..539ae7f --- /dev/null +++ b/tests/journal-build-contract.test.mjs @@ -0,0 +1,19 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; + +test('static build exposes an initial journal batch and a cursor manifest', async () => { + const homepage = await readFile(new URL('../dist/index.html', import.meta.url), 'utf8'); + const renderedHomepage = homepage.split('