diff --git a/.air.toml b/.air.toml
new file mode 100644
index 0000000..4dfde31
--- /dev/null
+++ b/.air.toml
@@ -0,0 +1,97 @@
+#:schema https://json.schemastore.org/any.json
+
+env_files = []
+root = "."
+testdata_dir = "testdata"
+tmp_dir = "tmp"
+
+[build]
+ args_bin = []
+ bin = "./tmp/main"
+
+ # Your Go entrypoint lives here
+ cmd = "go build -o ./tmp/main ./cmd/server"
+
+ # Faster feedback for UI work
+ delay = 200
+
+ entrypoint = ["./tmp/main"]
+
+ exclude_dir = [
+ ".git",
+ "tmp",
+ "vendor",
+ "testdata",
+ "node_modules"
+ ]
+
+ exclude_file = []
+ exclude_regex = ["_test.go"]
+ exclude_unchanged = true
+ follow_symlink = false
+ full_bin = ""
+
+ include_dir = []
+
+ # Watch your actual frontend files too
+ include_ext = [
+ "go",
+ "tpl",
+ "tmpl",
+ "html",
+ "css",
+ "js",
+ "svg"
+ ]
+
+ include_file = []
+
+ kill_delay = "500ms"
+ log = "build-errors.log"
+
+ poll = false
+ poll_interval = 0
+
+ post_cmd = []
+ pre_cmd = []
+
+ rerun = false
+ rerun_delay = 500
+
+ send_interrupt = true
+
+ # Don't silently continue using an old binary if the new build fails
+ stop_on_error = true
+
+[color]
+ app = ""
+ build = "yellow"
+ main = "magenta"
+ mode = ""
+ runner = "green"
+ watcher = "cyan"
+
+[log]
+ main_only = false
+ silent = false
+ time = false
+
+[misc]
+ clean_on_exit = true
+
+# Air has built-in browser live reload
+[proxy]
+ enabled = true
+
+ # Your Go server
+ app_port = 8080
+
+ # Open THIS in Chrome
+ proxy_port = 8090
+
+ # Give your app a moment to restart
+ app_start_timeout = 10000
+
+[screen]
+ clear_on_rebuild = false
+ keep_scroll = true
\ No newline at end of file
diff --git a/internal/http/home_test.go b/internal/http/home_test.go
index 7fe4913..2dc748a 100644
--- a/internal/http/home_test.go
+++ b/internal/http/home_test.go
@@ -245,21 +245,37 @@ func TestHomeIncludesSelectedWorkAndTooling(t *testing.T) {
t.Fatalf("status = %d", rec.Code)
}
body := rec.Body.String()
+ // Verify the current editorial content, not IDs and secondary repository
+ // links belonging to the retired full-length tooling section.
for _, expected := range []string{
`href="/work/salon-rebuild/"`, `href="/work/portfolio"`,
- `id="workspace-project-title"`, `id="tooling-title"`,
+ `id="selected-work-title"`, `id="capabilities-title"`,
+ `Portfolio & Client Workspace`,
+ `go-jsonld-schema`, `djm-cli`,
`https://github.com/danieljmanningdev/djm-cli`,
`https://github.com/danieljmanningdev/go-jsonld-schema`,
- `https://github.com/danieljmanningdev/go-web-core`,
- `https://github.com/danieljmanningdev/go-web-security`,
- `https://github.com/danieljmanningdev/go-web-auth`,
- `https://github.com/danieljmanningdev/go-starter-auth-app`,
+ `data-theme="light"`,
+ `class="editorial-feature__media"`,
+ `salon-rebuild-home-480.avif`, `salon-rebuild-home-480.webp`,
+ `site-ending--home`,
+ `aria-labelledby="contact-title"`,
+ `href="mailto:daniel@danieljmanningdev.com"`,
} {
if !strings.Contains(body, expected) {
t.Errorf("missing %q", expected)
}
}
- if strings.Contains(body, `marquee-track`) {
- t.Error("homepage must not autoplay an unpausable capability list")
+ for _, removed := range []string{
+ `marquee-track`,
+ `daniel-avatar.svg`,
+ `class="footer-cta"`,
+ `Make the complex feel`,
+ } {
+ if strings.Contains(body, removed) {
+ t.Errorf("homepage must not restore removed content %q", removed)
+ }
+ }
+ if count := strings.Count(body, `id="contact"`); count != 1 {
+ t.Errorf("expected one contact section, got %d", count)
}
}
diff --git a/scripts/check-browser.cjs b/scripts/check-browser.cjs
index 1793406..dba6096 100644
--- a/scripts/check-browser.cjs
+++ b/scripts/check-browser.cjs
@@ -48,6 +48,19 @@ async function check(name, callback) {
catch (error) { failures.push({ name, error: String(error.stack || error) }); results.push({ name, passed: false }); }
}
+async function decodeVisibleImage(page, image) {
+ await image.scrollIntoViewIfNeeded();
+ const element = await image.elementHandle();
+ try {
+ // Scrolling schedules native lazy loading; Firefox may not have a valid
+ // image request yet. Wait for loaded pixels instead of racing decode().
+ await page.waitForFunction(el => el.complete && el.naturalWidth > 0, element);
+ await image.evaluate(el => el.decode());
+ } finally {
+ await element.dispose();
+ }
+}
+
function overflowInPage() {
const width = document.documentElement.clientWidth;
const bad = [];
@@ -84,6 +97,14 @@ function overflowInPage() {
const response = await page.goto(base + route, { waitUntil: 'load' });
assert.equal(response.status(), status, 'Unexpected document status');
await page.evaluate(() => document.fonts.ready);
+ if (route === '/') {
+ // A full-page screenshot alone does not trigger every lazy image.
+ // Scroll and decode all homepage assets, then restore the top.
+ for (const image of await page.locator('img').all()) {
+ await decodeVisibleImage(page, image);
+ }
+ await page.evaluate(() => window.scrollTo({ top: 0, behavior: 'instant' }));
+ }
await page.evaluate(() => new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))));
const overflow = await page.evaluate(overflowInPage);
assert.ok(overflow.scrollWidth <= width + 1, JSON.stringify(overflow));
@@ -99,10 +120,11 @@ function overflowInPage() {
const scan = await new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'wcag22aa']).analyze();
const name = `${engine}-${route.replace(/[^a-z0-9]/gi, '_') || 'home'}-${width}`;
fs.writeFileSync(path.join(reportDirectory, `${name}.axe.json`), JSON.stringify({ url: route, violations: scan.violations, incomplete: scan.incomplete }, null, 2));
- assert.deepEqual(scan.violations.map(v => ({ id: v.id, impact: v.impact, nodes: v.nodes.map(n => n.target) })), [], 'Automated accessibility findings');
+ // Keep screenshots even when an accessibility assertion fails.
if (route === '/' || (route === '/work/portfolio' && width === 390)) {
await page.screenshot({ path: path.join(reportDirectory, `${name}.png`), fullPage: true });
}
+ assert.deepEqual(scan.violations.map(v => ({ id: v.id, impact: v.impact, nodes: v.nodes.map(n => n.target) })), [], 'Automated accessibility findings');
}
});
}
@@ -127,28 +149,57 @@ function overflowInPage() {
await page.keyboard.press('Enter');
assert.equal(await menu.getAttribute('open'), null);
});
- await check(`${engine} images decode and use responsive source`, async () => {
+ await check(`${engine} images decode and use responsive sources`, async () => {
await page.goto(base + '/');
- const image = page.locator('.home-work-preview__image');
- await image.scrollIntoViewIfNeeded();
- await page.waitForFunction(() => {
- const el = document.querySelector('.home-work-preview__image');
- return el && el.complete && el.naturalWidth > 0;
- });
- await image.evaluate(el => el.decode());
- const info = await image.evaluate(el => ({ source: el.currentSrc, width: el.naturalWidth, height: el.naturalHeight }));
- assert.ok(info.width > 0 && info.height > 0);
- assert.match(info.source, /salon-rebuild-home-(480|960|1600)\.(avif|webp)$/);
- results.push({ name: `${engine} responsive image selected`, ...info, passed: true });
+ for (const selector of ['.editorial-feature__media img', '.editorial-project--lead .editorial-project__media img']) {
+ const image = page.locator(selector);
+ await decodeVisibleImage(page, image);
+ const info = await image.evaluate(el => ({ source: el.currentSrc, width: el.naturalWidth, height: el.naturalHeight }));
+ assert.ok(info.width > 0 && info.height > 0);
+ assert.match(info.source, /salon-rebuild-home-(480|960|1600)\.(avif|webp)$/);
+ results.push({ name: `${engine} responsive image selected: ${selector}`, ...info, passed: true });
+ }
});
+ for (const width of widths) {
+ await check(`${engine} dark homepage ending ${width}px`, async () => {
+ await page.setViewportSize({ width, height: 900 });
+ await page.goto(base + '/', { waitUntil: 'load' });
+ assert.equal(await page.locator('main[data-theme="light"]').count(), 1);
+ assert.equal(await page.locator('.editorial-hero__avatar').count(), 0, 'Removed avatar must not return');
+ assert.equal(await page.locator('.footer-cta').count(), 0, 'Removed duplicate CTA must not return');
+ assert.equal(await page.locator('#contact').count(), 1);
+ await page.evaluate(() => window.scrollTo({ top: document.documentElement.scrollHeight, behavior: 'instant' }));
+ await page.evaluate(() => new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))));
+ const ending = await page.evaluate(() => {
+ const header = document.querySelector('.site-header');
+ const panel = document.querySelector('.site-ending--home');
+ const footer = document.querySelector('.site-footer');
+ return {
+ headerBottom: header.getBoundingClientRect().bottom,
+ panelTop: panel.getBoundingClientRect().top,
+ footerTop: footer.getBoundingClientRect().top,
+ footerBottom: footer.getBoundingClientRect().bottom,
+ viewport: window.innerHeight,
+ colours: [header, panel, document.querySelector('#contact'), footer].map(el => getComputedStyle(el).backgroundColor),
+ };
+ });
+ assert.ok(ending.panelTop <= ending.headerBottom + 1, 'Light content must not show between header and ending');
+ assert.ok(Math.abs(ending.footerBottom - ending.viewport) <= 1, 'Footer must reach the viewport bottom');
+ assert.ok(ending.footerTop > ending.viewport / 2, 'Footer belongs at the end, below the contact content');
+ assert.equal(new Set(ending.colours).size, 1, 'Header, contact and footer must share the shell colour token');
+ if (width === 390 || width === 1440) {
+ await page.screenshot({ path: path.join(reportDirectory, `${engine}-home-ending-${width}.png`) });
+ }
+ });
+ }
await check(`${engine} no JavaScript content and navigation`, async () => {
const noJS = await browser.newContext({ javaScriptEnabled: false, viewport: { width: 390, height: 900 }, reducedMotion: 'reduce' });
try {
const noJSPage = await noJS.newPage();
await noJSPage.goto(base + '/', { waitUntil: 'domcontentloaded' });
// DOM readiness can precede stylesheet layout, especially in Firefox.
- await noJSPage.locator('#workspace-project-title').waitFor({ state: 'visible' });
- await noJSPage.locator('#tooling-title').waitFor({ state: 'visible' });
+ await noJSPage.getByRole('heading', { name: 'Portfolio & Client Workspace', exact: true }).waitFor({ state: 'visible' });
+ await noJSPage.getByRole('heading', { name: 'go-jsonld-schema', exact: true }).waitFor({ state: 'visible' });
await noJSPage.locator('.mobile-nav summary').click();
await noJSPage.locator('.mobile-nav-link').first().waitFor({ state: 'visible' });
} finally {
diff --git a/web/static/css/vanilla/pages/home.css b/web/static/css/vanilla/pages/home.css
index fb88b3a..e734481 100644
--- a/web/static/css/vanilla/pages/home.css
+++ b/web/static/css/vanilla/pages/home.css
@@ -1,252 +1,445 @@
/* ==========================================================================
- HOME PAGE
+ HOME PAGE — MINIMAL EDITORIAL REDESIGN
Daniel J. Manning
https://danieljmanningdev.com
========================================================================== */
-.home-page { overflow: clip; }
-
-/* Hero */
-.home-hero { position: relative; isolation: isolate; border-bottom: 1px solid color-mix(in oklab, white 8%, transparent); }
-.home-hero__glow-field { position: absolute; inset: 0; z-index: -10; overflow: hidden; pointer-events: none; }
-.home-hero__glow { position: absolute; width: 20rem; height: 20rem; border-radius: 999px; filter: blur(64px); }
-.home-hero__glow--signal { top: 5rem; left: 5%; background: color-mix(in oklab, var(--color-signal-500) 13%, transparent); }
-.home-hero__glow--electric { top: 33.333%; right: -6rem; background: color-mix(in oklab, var(--color-electric-400) 9%, transparent); }
-.home-hero__layout { display: grid; min-height: calc(100svh - 5rem); align-items: center; gap: 3.5rem; padding-block: 5.5rem; }
-.home-hero__copy { max-width: 64rem; }
-.home-hero__identity { display: flex; align-items: center; gap: 1rem; margin-bottom: 2rem; }
-.home-hero__identity-line { width: 2.5rem; height: 1px; background: var(--color-signal-400); }
-.home-hero__identity-text { color: color-mix(in oklab, var(--color-signal-300) 70%, transparent); font-size: 0.75rem; letter-spacing: 0.3em; text-transform: uppercase; }
-.home-hero__title { font-size: clamp(3.125rem, 12vw, 4.25rem); font-weight: 500; line-height: 0.92; letter-spacing: -0.045em; }
-.home-hero__title-line { display: block; }
-.home-hero__title-accent { color: var(--color-signal-400); }
-.home-hero__intro { max-width: 42rem; margin-top: 2.25rem; color: var(--color-mist-300); font-size: 1.125rem; line-height: 2rem; }
-.home-hero__actions { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 2rem; }
-.home-hero__metrics { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); max-width: 48rem; gap: 1.5rem; margin-top: 3rem; padding-top: 1.75rem; border-top: 1px solid color-mix(in oklab, white 9%, transparent); }
-.home-hero__metric--wide { grid-column: span 2; }
-.home-hero__metric-value { margin-top: 0.5rem; color: var(--color-mist-100); font-size: 0.875rem; font-weight: 600; }
-
-/* Hero workspace preview */
-.home-workspace { position: relative; width: 100%; max-width: 36rem; margin-inline: auto; }
-.home-workspace__glow { position: absolute; inset: -2rem; z-index: -10; border-radius: 3rem; background: linear-gradient(135deg, color-mix(in oklab, var(--color-signal-500) 16%, transparent), transparent, color-mix(in oklab, var(--color-electric-400) 10%, transparent)); filter: blur(64px); }
-.home-workspace__panel { overflow: hidden; border-radius: 2.75rem; }
-.home-workspace__toolbar { display: flex; align-items: center; justify-content: space-between; padding: 1rem 1.25rem; border-bottom: 1px solid color-mix(in oklab, white 8%, transparent); }
-.home-workspace__dots { display: flex; gap: 0.5rem; }
-.home-workspace__dot { width: 0.5rem; height: 0.5rem; border-radius: 999px; background: color-mix(in oklab, white 18%, transparent); }
-.home-workspace__dot--accent { background: color-mix(in oklab, var(--color-signal-300) 70%, transparent); }
-.home-workspace__label { color: var(--color-mist-700); font-family: var(--font-mono); font-size: 0.66rem; letter-spacing: 0.18em; text-transform: uppercase; }
-.home-workspace__body { padding: 1.25rem; }
-.home-workspace__heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 1.25rem; }
-.home-workspace__principle { margin-top: 0.5rem; color: var(--color-mist-50); font-size: 1.5rem; font-weight: 500; letter-spacing: -0.035em; }
-.home-workspace__status { display: inline-flex; flex-shrink: 0; align-items: center; gap: 0.5rem; padding: 0.375rem 0.75rem; border: 1px solid color-mix(in oklab, var(--color-signal-300) 20%, transparent); border-radius: 999px; background: color-mix(in oklab, var(--color-signal-300) 7%, transparent); color: var(--color-signal-200); font-size: 0.75rem; font-weight: 700; }
-.home-workspace__status-dot { width: 0.375rem; height: 0.375rem; border-radius: 999px; background: var(--color-signal-300); }
-.home-workspace__cards { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.75rem; margin-top: 1.75rem; }
-.home-workspace__card { padding: 1.25rem; border: 1px solid color-mix(in oklab, white 8%, transparent); border-radius: 1.5rem; background: color-mix(in oklab, black 18%, transparent); }
-.home-workspace__card-title { margin-top: 1.25rem; color: var(--color-mist-50); font-size: 1.125rem; font-weight: 600; }
-.home-workspace__card-copy { margin-top: 0.5rem; color: var(--color-mist-500); font-size: 0.75rem; line-height: 1.25rem; }
-.home-workspace__flow { grid-column: span 2; padding: 1.25rem; border: 1px solid color-mix(in oklab, white 8%, transparent); border-radius: 1.5rem; background: #050915; }
-.home-workspace__flow-header { display: flex; align-items: center; justify-content: space-between; }
-.home-workspace__live { color: var(--color-signal-300); font-family: var(--font-mono); font-size: 0.65rem; }
-.home-workspace__steps { display: grid; grid-template-columns: auto 1fr; gap: 1rem; margin-top: 1.25rem; font-size: 0.875rem; }
-.home-workspace__step-number { display: grid; width: 2rem; height: 2rem; place-items: center; border-radius: 0.75rem; background: color-mix(in oklab, var(--color-signal-400) 12%, transparent); color: var(--color-signal-200); font-family: var(--font-mono); font-size: 0.75rem; }
-.home-workspace__step-title { color: var(--color-mist-100); font-weight: 600; }
-.home-workspace__step-copy { margin-top: 0.25rem; color: var(--color-mist-700); font-size: 0.75rem; }
-.home-capability-strip { padding-block: 1rem; border-top: 1px solid color-mix(in oklab, white 7%, transparent); }
-.home-capability-strip__track { display: flex; width: max-content; align-items: center; gap: 2.5rem; padding-right: 2.5rem; color: var(--color-mist-700); font-size: 0.68rem; font-weight: 700; letter-spacing: 0.24em; text-transform: uppercase; white-space: nowrap; }
-.home-capability-strip__accent { color: var(--color-signal-300); }
-
-/* Shared homepage section structure */
-.home-section { scroll-margin-top: 6rem; border-bottom: 1px solid color-mix(in oklab, white 8%, transparent); padding-block: 6rem; }
-.home-section__intro { display: grid; gap: 2rem; }
-.home-section__title { margin-top: 1.75rem; color: var(--color-mist-50); font-size: 2.25rem; font-weight: 500; line-height: 1.02; letter-spacing: -0.045em; text-wrap: balance; }
-.home-section__copy { max-width: 42rem; color: var(--color-mist-500); font-size: 1.125rem; line-height: 2rem; }
-.home-gradient-text { background: linear-gradient(90deg, var(--color-signal-300), var(--color-electric-300)); background-clip: text; -webkit-background-clip: text; color: transparent; }
-.home-accent-text { color: var(--color-signal-300); }
-
-/* Selected work */
-.home-work-intro { align-items: end; }
-.home-work-card { display: block; margin-top: 3.5rem; padding: 1rem; border-radius: 2.75rem; }
-.home-work-card__layout { display: grid; gap: 2.5rem; }
-.home-work-card__copy { position: relative; z-index: 10; padding-block: 1.25rem; }
-.home-work-card__meta { display: flex; align-items: center; gap: 0.75rem; }
-.home-work-card__badge { padding: 0.375rem 0.75rem; border: 1px solid color-mix(in oklab, var(--color-signal-300) 20%, transparent); border-radius: 999px; background: color-mix(in oklab, var(--color-signal-300) 7%, transparent); color: var(--color-signal-200); font-size: 0.67rem; font-weight: 700; letter-spacing: 0.18em; text-transform: uppercase; }
-.home-work-card__year { color: var(--color-mist-700); font-size: 0.75rem; }
-.home-work-card__title { margin-top: 2rem; color: var(--color-mist-50); font-size: 2.25rem; font-weight: 500; line-height: 0.98; letter-spacing: -0.045em; text-wrap: balance; }
-.home-work-card__title-accent { display: block; }
-.home-work-card__description { max-width: 36rem; margin-top: 1.5rem; color: var(--color-mist-500); line-height: 1.75rem; }
-.home-work-card__tags { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 2rem; color: var(--color-mist-300); font-size: 0.75rem; }
-.home-work-card__tag { padding: 0.375rem 0.75rem; border: 1px solid color-mix(in oklab, white 9%, transparent); border-radius: 999px; }
-.home-work-card__link { display: inline-flex; align-items: center; gap: 0.75rem; margin-top: 2.5rem; color: var(--color-mist-100); font-size: 0.875rem; font-weight: 700; }
-.home-work-card__arrow { color: var(--color-signal-300); transition: transform var(--duration-fast) var(--ease-standard); }
-.home-work-card:hover .home-work-card__arrow { transform: translateX(0.25rem); }
-.home-work-preview { position: relative; min-height: 27rem; overflow: hidden; padding: 1.25rem; border: 1px solid color-mix(in oklab, white 9%, transparent); border-radius: 2rem; background: linear-gradient(135deg, color-mix(in oklab, var(--color-signal-500) 12%, transparent), #071127, color-mix(in oklab, var(--color-electric-400) 7%, transparent)); }
-.home-work-preview__glow { position: absolute; top: -4rem; right: -4rem; width: 16rem; height: 16rem; border-radius: 999px; background: color-mix(in oklab, var(--color-signal-400) 14%, transparent); filter: blur(64px); }
-.home-work-preview__window { position: relative; display: grid; height: 100%; grid-template-rows: auto 1fr; overflow: hidden; border: 1px solid color-mix(in oklab, white 10%, transparent); border-radius: 1.5rem; background: rgb(5 11 24 / 0.92); box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.4); }
-.home-work-preview__toolbar { display: flex; align-items: center; justify-content: space-between; padding: 1rem 1.25rem; border-bottom: 1px solid color-mix(in oklab, white 8%, transparent); }
-.home-work-preview__dots { display: flex; align-items: center; gap: 0.5rem; }
-.home-work-preview__dot { width: 0.5rem; height: 0.5rem; border-radius: 999px; background: color-mix(in oklab, white 18%, transparent); }
-.home-work-preview__dot--accent { background: color-mix(in oklab, var(--color-signal-300) 70%, transparent); }
-.home-work-preview__label { color: var(--color-mist-700); font-family: var(--font-mono); font-size: 0.62rem; letter-spacing: 0.15em; text-transform: uppercase; }
-.home-work-preview__body { display: grid; gap: 1rem; padding: 1.25rem; }
-.home-work-preview__sidebar, .home-work-preview__surface, .home-work-preview__stat { padding: 1rem; border: 1px solid color-mix(in oklab, white 8%, transparent); border-radius: 1rem; background: color-mix(in oklab, white 3%, transparent); }
-.home-work-preview__accent-line { width: 4rem; height: 0.5rem; border-radius: 999px; background: color-mix(in oklab, var(--color-signal-300) 35%, transparent); }
-.home-work-preview__nav { display: grid; gap: 0.75rem; margin-top: 1.75rem; }
-.home-work-preview__nav-line { height: 2rem; border-radius: 0.75rem; background: color-mix(in oklab, white 5%, transparent); }
-.home-work-preview__nav-line--active { background: color-mix(in oklab, var(--color-signal-400) 10%, transparent); }
-.home-work-preview__content { display: grid; align-content: start; gap: 1rem; }
-.home-work-preview__stats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0.75rem; }
-.home-work-preview__stat-line { width: 2.5rem; height: 0.5rem; border-radius: 999px; background: color-mix(in oklab, white 12%, transparent); }
-.home-work-preview__stat-value { width: 3rem; height: 1.75rem; margin-top: 1.25rem; border-radius: 0.5rem; background: color-mix(in oklab, var(--color-signal-300) 25%, transparent); }
-.home-work-preview__stat-value--electric { background: color-mix(in oklab, var(--color-electric-300) 20%, transparent); }
-.home-work-preview__stat-value--indigo { background: rgb(165 180 252 / 0.2); }
-.home-work-preview__surface-header { display: flex; align-items: center; justify-content: space-between; }
-.home-work-preview__surface-title { width: 6rem; height: 0.5rem; border-radius: 999px; background: color-mix(in oklab, white 12%, transparent); }
-.home-work-preview__surface-action { width: 5rem; height: 1.75rem; border-radius: 999px; background: color-mix(in oklab, var(--color-signal-400) 12%, transparent); }
-.home-work-preview__rows { display: grid; gap: 0.75rem; margin-top: 1.25rem; }
-.home-work-preview__row { height: 3rem; border-radius: 0.75rem; background: color-mix(in oklab, white 5%, transparent); }
-
-/* Capabilities */
-.home-capabilities__layout { display: grid; gap: 3rem; }
-.home-capabilities__intro-title { max-width: 36rem; }
-.home-capabilities__intro-copy { max-width: 32rem; margin-top: 1.75rem; color: var(--color-mist-500); line-height: 1.75rem; }
-.home-capabilities__list { border-block: 1px solid color-mix(in oklab, white 9%, transparent); }
-.home-capability { display: grid; gap: 1.25rem; padding-block: 2.25rem; }
-.home-capability + .home-capability { border-top: 1px solid color-mix(in oklab, white 9%, transparent); }
-.home-capability__number { color: var(--color-signal-300); font-family: var(--font-mono); font-size: 0.875rem; }
-.home-capability__title { color: var(--color-mist-50); font-size: 1.5rem; font-weight: 500; }
-.home-capability__copy { max-width: 42rem; margin-top: 1rem; color: var(--color-mist-500); line-height: 1.75rem; }
-
-/* Working style */
-.home-style__layout { display: grid; gap: 3.5rem; }
-.home-style__intro-copy { max-width: 36rem; margin-top: 1.75rem; color: var(--color-mist-500); font-size: 1.125rem; line-height: 2rem; }
-.home-style__cards { display: grid; gap: 1rem; }
-.home-style__card { padding: 1.5rem; border-radius: 2rem; }
-.home-style__number { color: var(--color-signal-300); font-family: var(--font-mono); font-size: 0.875rem; }
-.home-style__number--electric { color: var(--color-electric-300); }
-.home-style__card-title { margin-top: 4rem; color: var(--color-mist-50); font-size: 1.25rem; font-weight: 600; }
-.home-style__card-copy { margin-top: 0.75rem; color: var(--color-mist-500); font-size: 0.875rem; line-height: 1.5rem; }
-
-/* Journal */
-.home-journal-section { padding-block: 6rem; border-bottom: 1px solid color-mix(in oklab, white 8%, transparent); }
-.home-journal-card { display: grid; gap: 2.5rem; padding: 2rem; border-radius: 2.75rem; }
-.home-journal-card__title { max-width: 56rem; }
-.home-journal-card__copy { max-width: 42rem; margin-top: 1.5rem; color: var(--color-mist-500); font-size: 1.125rem; line-height: 2rem; }
-.home-journal-card__arrow { position: relative; z-index: 10; display: grid; width: 4rem; height: 4rem; place-items: center; border: 1px solid color-mix(in oklab, var(--color-signal-300) 25%, transparent); border-radius: 999px; background: color-mix(in oklab, var(--color-signal-300) 8%, transparent); color: var(--color-signal-200); font-size: 1.5rem; transition: background var(--duration-fast) var(--ease-standard), color var(--duration-fast) var(--ease-standard); }
-.home-journal-card:hover .home-journal-card__arrow { background: var(--color-signal-400); color: white; }
-
-/* Contact */
-.home-contact { scroll-margin-top: 6rem; padding-block: 6rem; }
-.home-contact__panel { position: relative; overflow: hidden; padding: 2rem; border: 1px solid color-mix(in oklab, white 9%, transparent); border-radius: 2.75rem; background: linear-gradient(135deg, color-mix(in oklab, white 6%, transparent), color-mix(in oklab, white 2%, transparent), color-mix(in oklab, var(--color-signal-400) 7%, transparent)); }
-.home-contact__glow { position: absolute; top: -7rem; right: -7rem; width: 24rem; height: 24rem; border-radius: 999px; background: color-mix(in oklab, var(--color-signal-400) 12%, transparent); filter: blur(64px); pointer-events: none; }
-.home-contact__layout { position: relative; display: grid; gap: 3rem; }
-.home-contact__title { max-width: 64rem; margin-top: 1.75rem; color: var(--color-mist-50); font-size: clamp(3rem, 7vw, 7rem); font-weight: 500; line-height: 0.9; letter-spacing: -0.06em; text-wrap: balance; }
-.home-contact__title-accent { display: block; }
-.home-contact__aside { max-width: 24rem; }
-.home-contact__copy { color: var(--color-mist-500); line-height: 1.75rem; }
-.home-contact__button { margin-top: 1.75rem; }
+.home-page {
+ overflow: clip;
+ background: var(--color-background);
+ color: var(--color-text-primary);
+}
-@media (min-width: 40rem) {
- .home-hero__glow { width: 28rem; height: 28rem; }
- .home-hero__title { font-size: 6rem; }
- .home-hero__intro { font-size: 1.25rem; }
- .home-hero__metrics { grid-template-columns: repeat(3, minmax(0, 1fr)); }
- .home-hero__metric--wide { grid-column: span 1; }
- .home-workspace__body { padding: 1.75rem; }
- .home-section { padding-block: 7.5rem; }
- .home-section__title { font-size: 3.75rem; }
- .home-work-card { padding: 1.75rem; }
- .home-work-card__copy { padding-inline: 0.75rem; }
- .home-work-card__title { font-size: 3rem; }
- .home-work-preview { min-height: 34rem; padding: 1.75rem; }
- .home-work-preview__body { grid-template-columns: 0.38fr 1fr; }
- .home-capability { grid-template-columns: 4rem 1fr; }
- .home-capability__title { font-size: 1.875rem; }
- .home-style__cards { grid-template-columns: repeat(3, minmax(0, 1fr)); }
- .home-style__card { min-height: 18rem; }
- .home-style__card:nth-child(2) { transform: translateY(2rem); }
- .home-style__card:nth-child(3) { transform: translateY(4rem); }
- .home-journal-section { padding-block: 7.5rem; }
- .home-journal-card { padding: 3rem; }
- .home-contact { padding-block: 8rem; }
- .home-contact__panel { padding: 3rem; }
+.home-page a {
+ color: inherit;
}
-@media (min-width: 64rem) {
- .home-hero__layout { grid-template-columns: minmax(0, 1.08fr) minmax(0, 0.92fr); gap: 5rem; padding-block: 6rem; }
- .home-hero__title { font-size: clamp(4rem, 7.5vw, 7.25rem); }
- .home-workspace { justify-self: end; }
- .home-section { padding-block: 9rem; }
- .home-section__intro { grid-template-columns: minmax(0, 0.75fr) minmax(0, 1.25fr); }
- .home-section__intro > .home-section__copy { justify-self: end; }
- .home-work-card__layout { grid-template-columns: minmax(0, 0.82fr) minmax(0, 1.18fr); align-items: center; }
- .home-capabilities__layout { grid-template-columns: minmax(0, 0.72fr) minmax(0, 1.28fr); }
- .home-capabilities__intro { position: sticky; top: 7rem; align-self: start; }
- .home-style__layout { grid-template-columns: repeat(12, minmax(0, 1fr)); }
- .home-style__intro { grid-column: span 5; }
- .home-style__cards { grid-column: span 7; }
- .home-journal-card { grid-template-columns: minmax(0, 1fr) auto; align-items: end; padding: 4rem; }
- .home-contact { padding-block: 10rem; }
- .home-contact__panel { padding: 4rem; }
- .home-contact__layout { grid-template-columns: minmax(0, 1fr) auto; align-items: end; }
-}
-
-/* Responsive content and reusable tooling. No autoplaying essential content. */
-.home-capability-list {
+.editorial-kicker {
+ color: var(--color-text-muted);
+ font-size: 0.72rem;
+ font-weight: 500;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+}
+
+.editorial-kicker--dark {
+ color: var(--color-text-muted);
+}
+
+.editorial-hero {
+ padding-block: clamp(5rem, 9vw, 8rem) clamp(4.5rem, 8vw, 7rem);
+}
+
+.editorial-hero__grid {
+ display: grid;
+ gap: clamp(3rem, 7vw, 7rem);
+ align-items: center;
+}
+
+.editorial-hero__identity {
+ display: grid;
+ gap: 2rem;
+ align-items: center;
+}
+
+.editorial-hero__copy {
+ max-width: 28rem;
+}
+
+.editorial-hero__name {
+ margin-top: 0.5rem;
+ font-size: clamp(2rem, 4vw, 3.15rem);
+ font-weight: 500;
+ line-height: 1;
+ letter-spacing: -0.045em;
+}
+
+.editorial-hero__role {
+ margin-top: 0.75rem;
+ color: var(--color-text-muted);
+ font-size: clamp(1.25rem, 2.3vw, 1.75rem);
+ font-weight: 400;
+ letter-spacing: -0.025em;
+}
+
+.editorial-hero__intro {
+ margin-top: 1.55rem;
+ color: var(--color-text-secondary);
+ font-size: 1rem;
+ line-height: 1.52;
+}
+
+.editorial-hero__links {
display: flex;
flex-wrap: wrap;
+ gap: 0.6rem 1.2rem;
+ margin-top: 1.75rem;
+ color: var(--color-text-muted);
+ font-size: 0.78rem;
+}
+
+.editorial-hero__links a,
+.editorial-text-link,
+.editorial-about__links a {
+ text-decoration: none;
+ text-underline-offset: 0.2em;
+}
+
+.editorial-hero__links a:hover,
+.editorial-text-link:hover,
+.editorial-about__links a:hover {
+ color: var(--color-accent);
+}
+
+.editorial-feature {
+ display: grid;
+ min-height: 13.5rem;
+ gap: var(--space-6);
+ align-items: center;
+ padding: clamp(1.4rem, 3vw, 2.2rem);
+ border: 1px solid var(--color-border);
+ background: var(--color-surface);
+ text-decoration: none;
+ transition:
+ transform var(--duration-normal) var(--ease-fluid),
+ border-color var(--duration-fast) var(--ease-standard);
+}
+
+.editorial-feature:hover {
+ transform: translateY(-0.2rem);
+ border-color: var(--color-accent);
+}
+
+.editorial-feature__title {
+ margin-top: 0.85rem;
+ font-size: clamp(1.4rem, 2.4vw, 2rem);
+ font-weight: 600;
+ letter-spacing: -0.035em;
+}
+
+.editorial-feature__copy {
+ max-width: 31rem;
+ margin-top: 0.55rem;
+ color: var(--color-text-secondary);
+ font-size: 0.88rem;
+ line-height: 1.45;
+}
+
+.editorial-feature__media {
+ display: block;
+ min-width: 0;
+ overflow: hidden;
+ aspect-ratio: 16 / 9;
+ border: 1px solid var(--color-border-subtle);
+ background: var(--color-background-strong);
+}
+
+.editorial-feature__media img {
+ display: block;
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ object-position: top left;
+}
+
+.editorial-arrow {
+ display: inline-flex;
+ align-items: center;
+ gap: 1rem;
+ margin-top: 2.5rem;
+ color: var(--color-accent);
+ font-size: 0.78rem;
+}
+
+.editorial-feature:hover .editorial-arrow span,
+.editorial-project:hover .editorial-project__arrow {
+ color: var(--color-accent);
+ transform: translate(0.14rem, -0.14rem);
+}
+
+.editorial-work,
+.editorial-strip,
+.editorial-about {
+ border-top: 1px solid var(--color-border);
+}
+
+.editorial-work {
+ padding-block: clamp(2.75rem, 6vw, 5.5rem) clamp(5.5rem, 9vw, 8rem);
+}
+
+.editorial-section-head {
+ display: flex;
+ align-items: start;
justify-content: space-between;
- gap: 0.75rem 1.5rem;
- color: var(--color-mist-300);
- font-size: 0.75rem;
+ gap: 2rem;
+}
+
+.editorial-section-title {
+ margin-top: 1.5rem;
+ font-size: clamp(2rem, 4.7vw, 4rem);
+ font-weight: 400;
+ line-height: 1.02;
+ letter-spacing: -0.05em;
+}
+
+.editorial-text-link {
+ flex-shrink: 0;
+ margin-top: 0.75rem;
+ color: var(--color-text-muted);
+ font-size: 0.78rem;
+}
+
+.editorial-project-grid {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: clamp(1.5rem, 4vw, 3.5rem);
+ margin-top: clamp(3rem, 6vw, 5rem);
+}
+
+.editorial-project,
+.editorial-project:visited {
+ min-width: 0;
+ text-decoration: none;
+}
+
+.editorial-project__media {
+ position: relative;
+ overflow: hidden;
+ aspect-ratio: 16 / 10;
+ background: var(--color-background-strong);
+}
+
+.editorial-project__media img,
+.editorial-project__media picture {
+ display: block;
+ width: 100%;
+ height: 100%;
+}
+
+.editorial-project__media img {
+ object-fit: cover;
+ transition: transform 450ms var(--ease-fluid);
+}
+
+.editorial-project:hover .editorial-project__media img {
+ transform: scale(1.018);
+}
+
+.editorial-project__body {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) auto;
+ gap: 1.25rem;
+ align-items: start;
+ padding-top: 1rem;
+}
+
+.editorial-project__meta {
+ color: var(--color-text-muted);
+ font-size: 0.7rem;
+ letter-spacing: 0.035em;
+ text-transform: uppercase;
+}
+
+.editorial-project__body h3 {
+ margin-top: 0.35rem;
+ font-size: clamp(1.15rem, 2vw, 1.55rem);
font-weight: 600;
- letter-spacing: 0.06em;
+ letter-spacing: -0.035em;
+}
+
+.editorial-project__body p:not(.editorial-project__meta) {
+ max-width: 36rem;
+ margin-top: 0.45rem;
+ color: var(--color-text-muted);
+ font-size: 0.83rem;
+ line-height: 1.45;
+}
+
+.editorial-project__arrow {
+ color: var(--color-text-muted);
+ font-size: 1rem;
+ transition:
+ transform var(--duration-fast) var(--ease-standard),
+ color var(--duration-fast) var(--ease-standard);
+}
+
+.editorial-project-stack {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: 2rem;
+}
+
+.editorial-project--compact .editorial-project__media {
+ aspect-ratio: 16 / 6;
}
-.home-work-card__meta { flex-wrap: wrap; }
-.home-work-card + .home-work-card { margin-top: 2rem; }
-.home-work-card--workspace { border-color: var(--color-accent-border); }
-.home-work-card__copy, .home-hero__copy { min-width: 0; }
-.home-workspace__label, .home-workspace__step-copy, .home-work-card__year {
+.editorial-project__media--workspace img {
+ object-position: center 22%;
+}
+
+.editorial-project__media--code,
+.editorial-project__media--terminal {
+ display: grid;
+ align-content: center;
+ gap: 0.55rem;
+ padding: clamp(1.4rem, 3vw, 2.2rem);
+ font-family: var(--font-mono);
+ font-size: clamp(0.72rem, 1.2vw, 0.9rem);
+}
+
+.editorial-project__media--code {
+ background: var(--color-surface);
+ color: var(--color-text-muted);
+}
+
+.editorial-project__media--code span:nth-child(2) {
+ color: var(--color-accent);
+}
+
+.editorial-project__media--terminal {
+ background: var(--color-ink-950);
color: var(--color-mist-300);
}
-.home-system-preview {
- padding: clamp(1.25rem, 3vw, 2.5rem);
- border: 1px solid var(--color-accent-border);
- border-radius: 1.75rem;
- background: linear-gradient(135deg, var(--color-ink-850), var(--color-ink-950));
-}
-.home-system-preview__grid { display: grid; gap: 1rem; margin-top: 1.5rem; }
-.home-system-preview__grid > div { padding: 1.25rem; border: 1px solid var(--color-border-strong); border-radius: 1rem; }
-.home-system-preview dt { color: var(--color-mist-50); font-weight: 600; }
-.home-system-preview dd { margin-top: 0.5rem; color: var(--color-mist-300); font-size: 0.875rem; }
-.home-system-preview__foundation { margin-top: 1.5rem; color: var(--color-signal-200); font-size: 0.8125rem; line-height: 1.75; }
-
-.home-tooling { padding-block: clamp(4rem, 6vw, 6rem); }
-.home-tooling__grid { display: grid; gap: 1.25rem; margin-top: 2.5rem; }
-.home-tool-card { min-width: 0; padding: 1.5rem; border: 1px solid var(--color-border-strong); border-radius: 1.5rem; background: var(--color-background-elevated); }
-.home-tool-card h3 { margin-top: 1rem; color: var(--color-mist-50); font-size: 1.25rem; overflow-wrap: anywhere; }
-.home-tool-card h3 a { display: inline-block; padding-block: 0.5rem; }
-.home-tool-card h3 a, .home-tool-card__links a { text-decoration: underline; text-underline-offset: 0.2em; text-decoration-thickness: 1px; }
-.home-tool-card p:not(.eyebrow) { margin-top: 0.875rem; color: var(--color-mist-300); font-size: 0.9375rem; line-height: 1.75; }
-.home-tool-card pre { margin-top: 1.25rem; padding: 1rem; border-radius: 0.75rem; background: var(--color-ink-950); color: var(--color-signal-200); font-size: 0.875rem; line-height: 1.8; white-space: pre-wrap; overflow-wrap: anywhere; }
-.home-tool-card__links { display: flex; flex-wrap: wrap; gap: 0.25rem 1rem; margin-top: 1rem; }
-.home-tool-card__links a { display: inline-flex; align-items: center; min-height: 2.75rem; color: var(--color-signal-200); font-size: 0.875rem; }
-.home-tooling__all { margin-top: 1.5rem; }
-
-@media (min-width: 30rem) {
- .home-system-preview__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
+.editorial-project__terminal-ok {
+ color: var(--color-electric-300);
+}
+
+.editorial-strip,
+.editorial-about {
+ padding-block: clamp(4.5rem, 8vw, 7rem);
+}
+
+.editorial-strip__grid,
+.editorial-about__grid {
+ display: grid;
+ gap: clamp(3rem, 7vw, 7rem);
+}
+
+.editorial-capabilities {
+ border-top: 1px solid var(--color-border);
+}
+
+.editorial-capabilities a {
+ display: grid;
+ grid-template-columns: 2.25rem minmax(0, 1fr) auto;
+ gap: 1rem;
+ align-items: center;
+ padding-block: 1.4rem;
+ border-bottom: 1px solid var(--color-border);
+ text-decoration: none;
}
+
+.editorial-capabilities a > span:first-child {
+ color: var(--color-text-muted);
+ font-size: 0.7rem;
+}
+
+.editorial-capabilities strong {
+ font-size: clamp(1rem, 2vw, 1.3rem);
+ font-weight: 500;
+}
+
+.editorial-capabilities a:hover strong,
+.editorial-capabilities a:hover span:last-child {
+ color: var(--color-accent);
+}
+
+.editorial-about__statement {
+ max-width: 50rem;
+ font-size: clamp(1.7rem, 4vw, 3.2rem);
+ font-weight: 400;
+ line-height: 1.08;
+ letter-spacing: -0.045em;
+}
+
+.editorial-about__links {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.8rem 1.5rem;
+ align-self: end;
+ color: var(--color-text-muted);
+ font-size: 0.78rem;
+}
+
+@media (min-width: 40rem) {
+ .editorial-feature {
+ grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
+ }
+
+ .editorial-feature__media {
+ aspect-ratio: 4 / 3;
+ }
+}
+
@media (min-width: 48rem) {
- .home-tooling__grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
+ .editorial-project--compact {
+ display: grid;
+ grid-template-columns: minmax(12rem, 0.82fr) minmax(0, 1.18fr);
+ gap: 1.25rem;
+ align-items: start;
+ }
+
+ .editorial-project--compact .editorial-project__media {
+ aspect-ratio: 16 / 10;
+ }
+
+ .editorial-project--compact .editorial-project__body {
+ padding-top: 0.2rem;
+ }
}
-@media (max-width: 29.999rem) {
- .home-workspace__heading { flex-wrap: wrap; }
- .home-workspace__cards { grid-template-columns: 1fr; }
- .home-workspace__flow { grid-column: auto; }
- .home-work-card, .home-contact__panel { border-radius: 1.5rem; }
- .home-contact__panel { padding: 1.25rem; }
- .home-contact__title { font-size: clamp(2.25rem, 10vw, 3rem); line-height: 1.05; }
+
+@media (min-width: 64rem) {
+ .editorial-hero__grid {
+ grid-template-columns: minmax(0, 1.13fr) minmax(22rem, 0.87fr);
+ }
+
+ .editorial-project-grid {
+ grid-template-columns: minmax(0, 0.92fr) minmax(0, 1.08fr);
+ align-items: start;
+ }
+
+ .editorial-project--lead {
+ position: sticky;
+ top: 7rem;
+ }
+
+ .editorial-strip__grid {
+ grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
+ align-items: start;
+ }
+
+ .editorial-about__grid {
+ grid-template-columns: 0.38fr minmax(0, 1.4fr) auto;
+ align-items: start;
+ }
}
-.home-work-preview__image-frame picture { display: block; width: 100%; }
+@media (max-width: 47.999rem) {
+ .editorial-section-head {
+ display: grid;
+ }
+
+ .editorial-text-link {
+ margin-top: 0;
+ }
+
+ .editorial-feature {
+ min-height: 12rem;
+ }
+
+ /* Text previews need height for their contents, not a 16:6 minimum width. */
+ .editorial-project--compact .editorial-project__media--code,
+ .editorial-project--compact .editorial-project__media--terminal {
+ aspect-ratio: auto;
+ min-height: 10rem;
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .editorial-feature,
+ .editorial-project__media img,
+ .editorial-project__arrow {
+ transition: none;
+ }
+}
diff --git a/web/static/css/vanilla/shell.css b/web/static/css/vanilla/shell.css
index 1385d84..7317854 100644
--- a/web/static/css/vanilla/shell.css
+++ b/web/static/css/vanilla/shell.css
@@ -7,394 +7,271 @@
.site-shell {
position: relative;
isolation: isolate;
-
display: flex;
min-height: 100dvh;
flex-direction: column;
-
overflow-x: clip;
-
- background:
- radial-gradient(circle at 10% -8%, color-mix(in oklab, var(--color-signal) 17%, transparent), transparent 35rem),
- radial-gradient(circle at 92% 10%, color-mix(in oklab, var(--color-electric) 11%, transparent), transparent 32rem),
- linear-gradient(180deg, var(--color-background-panel), var(--color-background) 58%, var(--color-background-panel));
-
+ background: var(--color-background);
color: var(--color-text-primary);
}
-.site-shell::before {
- position: fixed;
- inset: 0;
- z-index: -2;
-
- pointer-events: none;
-
- content: "";
- opacity: 0.11;
-
- background-image:
- linear-gradient(rgb(255 255 255 / 0.07) 1px, transparent 1px),
- linear-gradient(90deg, rgb(255 255 255 / 0.07) 1px, transparent 1px);
- background-size: 5rem 5rem;
-
- mask-image: linear-gradient(to bottom, black, transparent 74%);
-}
-
-.site-shell::after {
- position: fixed;
- inset: 0;
- z-index: -1;
-
- pointer-events: none;
-
- content: "";
- opacity: 0.022;
-
- background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 180 180' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.92' numOctaves='3' stitchTiles='stitchTiles'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.8'/%3E%3C/svg%3E");
-}
-
-.site-main {
- flex: 1;
-}
-
-.htmx-indicator {
- opacity: 0;
- visibility: hidden;
-
- transition:
- opacity var(--duration-fast) var(--ease-standard),
- visibility var(--duration-fast) var(--ease-standard);
-}
+.site-main { flex: 1; }
+.htmx-indicator { opacity: 0; visibility: hidden; }
.htmx-request .htmx-indicator,
-.htmx-request.htmx-indicator {
- opacity: 1;
- visibility: visible;
-}
+.htmx-request.htmx-indicator { opacity: 1; visibility: visible; }
.skip-link {
position: fixed;
inset-block-start: 0.75rem;
inset-inline-start: 0.75rem;
z-index: var(--z-overlay);
-
translate: 0 -180%;
-
border-radius: var(--radius-pill);
background: var(--color-accent-light);
-
padding: 0.75rem 1rem;
-
color: var(--color-text-inverse);
font-size: var(--text-sm);
font-weight: var(--font-bold);
-
transition: translate var(--duration-fast) var(--ease-standard);
}
-
-.skip-link:focus {
- translate: 0;
-}
+.skip-link:focus { translate: 0; }
.page-progress {
position: fixed;
inset-inline: 0;
inset-block-start: 0;
z-index: var(--z-overlay);
-
height: 0.125rem;
transform-origin: left center;
-
- background: var(--color-accent-light);
- box-shadow: 0 0 1.375rem rgb(96 165 250 / 0.68);
+ background: var(--color-signal-400);
+ box-shadow: 0 0 1.25rem color-mix(in oklab, var(--color-signal-400) 55%, transparent);
}
-/* --------------------------------------------------------------------------
- HEADER
- -------------------------------------------------------------------------- */
-
+/* Header */
.site-header {
position: sticky;
top: 0;
z-index: var(--z-header);
-
+ min-height: var(--site-header-height);
border-bottom: 1px solid var(--color-border);
-
- background: rgb(5 11 24 / 0.82);
-
- backdrop-filter: blur(24px);
- -webkit-backdrop-filter: blur(24px);
+ background: var(--color-shell-background);
}
.primary-nav {
- display: flex;
- min-height: 5rem;
+ position: relative;
+ display: grid;
+ min-height: var(--site-header-height);
+ grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
+ align-items: center;
+ gap: 1rem;
+}
+.nav-status {
+ display: inline-flex;
+ width: fit-content;
align-items: center;
- justify-content: space-between;
+ gap: 0.7rem;
+ color: var(--color-text-muted);
+ font-size: 0.68rem;
+}
- gap: var(--space-5);
+.nav-status__dot {
+ width: 0.34rem;
+ height: 0.34rem;
+ border-radius: 50%;
+ background: var(--color-status-available);
+ box-shadow: 0 0 0.8rem color-mix(in oklab, var(--color-status-available) 38%, transparent);
}
.brand-link {
- display: inline-flex;
- flex-shrink: 0;
- align-items: center;
+ display: grid;
+ width: 4rem;
+ height: 4rem;
+ align-self: start;
+ place-items: center;
+ margin-bottom: -1rem;
+ border-radius: 0 0 999px 999px;
+ background: var(--color-shell-background);
}
.brand-logo {
- width: auto;
- height: 2.25rem;
+ width: 2rem;
+ height: auto;
}
.desktop-nav {
display: none;
+ justify-self: end;
align-items: center;
- gap: var(--space-7);
+ gap: 1.25rem;
}
-.nav-link {
- color: var(--color-text-subtle);
- font-size: var(--text-sm);
-
+.nav-link,
+.header-project-link {
+ color: var(--color-text-muted);
+ font-size: 0.68rem;
+ text-decoration: none;
transition: color var(--duration-fast) var(--ease-standard);
}
-.nav-link:hover {
- color: var(--color-text-primary);
-}
-
-.nav-actions {
- display: flex;
- align-items: center;
- gap: 0.625rem;
-}
+.nav-link:hover,
+.header-project-link:hover { color: var(--color-accent-light); }
.header-project-link {
- display: none;
- min-height: 2.5rem;
- padding-inline: 1rem;
-}
-
-.mobile-nav {
- position: relative;
+ padding: 0.45rem 0.95rem;
+ border: 1px solid var(--color-border-strong);
+ border-radius: 0.5rem;
}
+.mobile-nav { justify-self: end; position: relative; }
.mobile-nav summary {
display: grid;
width: 2.5rem;
height: 2.5rem;
-
list-style: none;
place-items: center;
-
- border: 1px solid rgb(255 255 255 / 0.12);
- border-radius: var(--radius-pill);
-
- background: rgb(255 255 255 / 0.04);
- color: var(--color-text-secondary);
-}
-
-.mobile-nav summary::-webkit-details-marker {
- display: none;
-}
-
-.mobile-nav-icon {
- display: grid;
- gap: 0.375rem;
-}
-
-.mobile-nav-icon span {
- width: 1rem;
- height: 1px;
-
- background: currentColor;
-
- transition: transform var(--duration-fast) var(--ease-standard);
-}
-
-.mobile-nav[open] .mobile-nav-icon span:first-child {
- transform: translateY(0.21875rem) rotate(45deg);
-}
-
-.mobile-nav[open] .mobile-nav-icon span:last-child {
- transform: translateY(-0.21875rem) rotate(-45deg);
+ border: 1px solid var(--color-border-strong);
+ border-radius: 999px;
+ background: var(--color-surface);
+ color: var(--color-text-primary);
}
-
+.mobile-nav summary::-webkit-details-marker { display: none; }
+.mobile-nav-icon { display: grid; gap: 0.35rem; }
+.mobile-nav-icon span { width: 1rem; height: 1px; background: currentColor; }
+.mobile-nav[open] .mobile-nav-icon span:first-child { transform: translateY(0.18rem) rotate(45deg); }
+.mobile-nav[open] .mobile-nav-icon span:last-child { transform: translateY(-0.18rem) rotate(-45deg); }
.mobile-nav-panel {
position: absolute;
top: 3rem;
right: 0;
+ width: min(18rem, calc(100vw - 2.5rem));
+ padding: 0.75rem;
+ border: 1px solid var(--color-border-strong);
+ border-radius: 1rem;
+ background: var(--color-background-elevated);
+ box-shadow: var(--shadow-lg);
+}
+.mobile-nav-links { display: grid; gap: 0.15rem; }
+.mobile-nav-link,
+.mobile-nav-cta {
+ padding: 0.8rem 0.9rem;
+ border-radius: 0.7rem;
+ color: var(--color-text-muted);
+ font-size: 0.85rem;
+ text-decoration: none;
+}
+.mobile-nav-link:hover { background: var(--color-surface-hover); color: var(--color-text-primary); }
+.mobile-nav-cta { margin-top: 0.35rem; background: var(--color-accent); color: var(--color-text-inverse); text-align: center; }
+
+/* The existing homepage contact and footer share one dark viewport.
+ A minimum height (not a fixed height) lets zoomed or wrapped text grow. */
+.site-ending {
+ background: var(--color-shell-background);
+ color: var(--color-text-primary);
+}
- width: 16rem;
-
- padding: var(--space-3);
- border-radius: var(--radius-2xl);
+.site-ending--home {
+ display: flex;
+ min-height: calc(100svh - var(--site-header-height));
+ min-height: calc(100dvh - var(--site-header-height));
+ flex-direction: column;
}
-.mobile-nav-links {
+.editorial-contact {
display: grid;
- gap: var(--space-1);
+ flex: 1;
+ align-items: center;
+ scroll-margin-top: var(--site-header-height);
+ background: var(--color-shell-background);
+ color: var(--color-text-primary);
}
-.mobile-nav-link {
- border-radius: var(--radius-xl);
- padding: 0.75rem 1rem;
-
- color: var(--color-text-secondary);
- font-size: var(--text-sm);
-
- transition:
- background-color var(--duration-fast) var(--ease-standard),
- color var(--duration-fast) var(--ease-standard);
+.editorial-contact__inner {
+ display: grid;
+ gap: 1.5rem;
+ padding-block: clamp(5rem, 9vw, 8rem);
}
-.mobile-nav-link:hover {
- background: var(--color-surface-hover);
- color: var(--color-white);
+.editorial-contact h2 {
+ max-width: 50rem;
+ font-size: clamp(2.5rem, 6vw, 5rem);
+ font-weight: 400;
+ line-height: 0.98;
+ letter-spacing: -0.055em;
}
-.mobile-nav-cta {
- margin-top: var(--space-2);
- border-radius: var(--radius-xl);
- padding: 0.75rem 1rem;
-
- background: var(--color-accent);
- color: var(--color-white);
-
- font-size: var(--text-sm);
- font-weight: var(--font-bold);
- text-align: center;
+.editorial-contact a {
+ display: inline-flex;
+ width: fit-content;
+ min-height: 2.75rem;
+ align-items: center;
+ gap: 1rem;
+ margin-top: 1rem;
+ color: var(--color-accent);
+ font-size: 0.9rem;
+ text-decoration: none;
+ text-underline-offset: 0.2em;
}
-/* --------------------------------------------------------------------------
- FOOTER
- -------------------------------------------------------------------------- */
+.editorial-contact a:hover {
+ color: var(--color-accent-light);
+ text-decoration: underline;
+}
+/* Footer — the removed duplicate CTA stays removed. */
.site-footer {
position: relative;
z-index: var(--z-raised);
-
+ flex-shrink: 0;
margin-top: auto;
border-top: 1px solid var(--color-border);
-
- background: rgb(3 8 20 / 0.38);
-}
-
-.footer-inner {
- padding-block: var(--space-12);
-}
-
-.footer-cta {
- display: grid;
- gap: var(--space-10);
-
- border-bottom: 1px solid var(--color-border);
- padding-bottom: var(--space-12);
-}
-
-.footer-title {
- margin-top: var(--space-6);
- max-width: var(--content-xl);
-
- color: var(--color-text-primary);
-
- font-size: clamp(2.25rem, 5vw, 3.75rem);
- font-weight: var(--font-medium);
- line-height: var(--leading-heading);
- letter-spacing: var(--tracking-display);
- text-wrap: balance;
+ background: var(--color-shell-background);
}
+.site-ending--home .site-footer { border-top: 0; }
+.footer-inner { padding-block: var(--space-8); }
.gradient-text {
- background: linear-gradient(90deg, var(--color-accent-light), var(--color-electric));
+ background: linear-gradient(90deg, var(--color-signal-300), var(--color-electric-300));
background-clip: text;
-webkit-background-clip: text;
color: transparent;
}
-
-.footer-contact-button {
- width: fit-content;
-}
-
.footer-meta {
display: grid;
gap: var(--space-8);
-
- padding-top: var(--space-8);
-
- color: var(--color-text-subtle);
+ color: var(--color-text-muted);
font-size: var(--text-sm);
}
-
-.footer-logo-link {
- display: inline-flex;
- width: fit-content;
- align-items: center;
-}
-
-.footer-logo {
- width: auto;
- height: 2.25rem;
-}
-
-.footer-links {
- display: flex;
- flex-wrap: wrap;
- gap: 0.75rem var(--space-6);
-}
-
-.footer-link {
- transition: color var(--duration-fast) var(--ease-standard);
-}
-
-.footer-link:hover {
- color: var(--color-text-primary);
-}
-
-.footer-copyright {
- font-size: var(--text-xs);
-}
-
+.footer-logo-link { display: inline-flex; width: fit-content; align-items: center; }
+.footer-logo { width: auto; height: 2.25rem; }
+.footer-links { display: flex; flex-wrap: wrap; gap: 0.75rem var(--space-6); }
+.footer-link:hover { color: var(--color-accent-light); }
+.footer-copyright { color: var(--color-text-muted); font-size: var(--text-xs); }
@media (min-width: 40rem) {
- .brand-logo {
- height: 2.5rem;
- }
-
- .header-project-link {
- display: inline-flex;
- }
-
- .footer-inner {
- padding-block: var(--space-16);
- }
-
- .footer-meta {
- grid-template-columns: repeat(2, minmax(0, 1fr));
- }
+ .footer-meta { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 64rem) {
- .desktop-nav {
- display: flex;
- }
-
- .mobile-nav {
- display: none;
- }
-
- .footer-cta {
- grid-template-columns: minmax(0, 1fr) auto;
- align-items: end;
- }
-
- .footer-meta {
- grid-template-columns: minmax(0, 1fr) auto auto;
- align-items: center;
- }
-
- .footer-copyright {
- text-align: right;
+ .desktop-nav { display: flex; }
+ .mobile-nav { display: none; }
+ .footer-meta { grid-template-columns: minmax(0, 1fr) auto auto; align-items: center; }
+ .footer-copyright { text-align: right; }
+}
+
+@media (max-width: 39.999rem) {
+ /* Keep the compact dot, but do not remove its meaning from screen readers. */
+ .nav-status__text {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ margin: -1px;
+ padding: 0;
+ overflow: hidden;
+ clip-path: inset(50%);
+ white-space: nowrap;
+ border: 0;
}
+ .brand-link { width: 3.75rem; height: 3.75rem; }
}
diff --git a/web/static/css/vanilla/tokens.css b/web/static/css/vanilla/tokens.css
index 831c44b..7f959ed 100644
--- a/web/static/css/vanilla/tokens.css
+++ b/web/static/css/vanilla/tokens.css
@@ -7,8 +7,7 @@
:root {
/* ----------------------------------------------------------------------
COLOUR — FOUNDATIONS
- Mirrors the existing Tailwind theme while the vanilla CSS migration is
- in progress. These values are the visual source of truth.
+ Shared brand palette. These values are the visual source of truth.
---------------------------------------------------------------------- */
--color-ink-950: oklch(0.12 0.032 257);
@@ -26,6 +25,8 @@
--color-signal-300: oklch(0.83 0.095 241);
--color-signal-400: oklch(0.75 0.135 244);
--color-signal-500: oklch(0.66 0.17 248);
+ /* Same brand hue, deepened for readable links on light surfaces. */
+ --color-signal-700: oklch(0.5 0.17 248);
--color-electric-300: oklch(0.88 0.055 215);
--color-electric-400: oklch(0.8 0.095 221);
@@ -44,6 +45,8 @@
--color-background-elevated: var(--color-ink-900);
--color-background-panel: var(--color-ink-850);
--color-background-strong: var(--color-ink-800);
+ --color-shell-background: var(--color-black);
+ --color-status-available: var(--color-signal-400);
--color-text-primary: var(--color-mist-50);
--color-text-secondary: var(--color-mist-100);
@@ -179,6 +182,7 @@
--container-width: 82rem;
--container-gutter: clamp(1.25rem, 6vw, 3rem);
+ --site-header-height: 3.5rem;
--content-xs: 24rem;
--content-sm: 32rem;
@@ -276,3 +280,34 @@
--focus-ring:
0 0 0 3px color-mix(in oklab, var(--color-signal-300) 30%, transparent);
}
+
+/* Light content opts in without changing the journal, services or workspace.
+ Override the semantic tokens so base headings, paragraphs and strong text
+ resolve to the correct colours, rather than relying on inherited `color`. */
+[data-theme="light"] {
+ color-scheme: light;
+
+ --color-background: var(--color-mist-100);
+ --color-background-elevated: var(--color-mist-50);
+ --color-background-panel: var(--color-mist-50);
+ --color-background-strong: color-mix(in oklab, var(--color-mist-100) 85%, var(--color-mist-300));
+
+ --color-text-primary: var(--color-ink-950);
+ --color-text-secondary: var(--color-ink-800);
+ --color-text-muted: var(--color-mist-700);
+ --color-text-subtle: var(--color-mist-700);
+ --color-text-faint: var(--color-mist-700);
+ --color-text-inverse: var(--color-mist-50);
+
+ --color-accent: var(--color-signal-700);
+ --color-accent-light: var(--color-signal-700);
+ --color-accent-strong: var(--color-signal-700);
+ --color-border: color-mix(in oklab, var(--color-ink-950) 18%, transparent);
+ --color-border-subtle: color-mix(in oklab, var(--color-ink-950) 10%, transparent);
+ --color-border-strong: color-mix(in oklab, var(--color-ink-950) 28%, transparent);
+ --color-surface: var(--color-mist-50);
+ --color-surface-hover: var(--color-signal-200);
+ --color-accent-surface: color-mix(in oklab, var(--color-signal-700) 8%, transparent);
+ --color-accent-border: color-mix(in oklab, var(--color-signal-700) 35%, transparent);
+ --focus-ring: 0 0 0 3px var(--color-signal-700);
+}
diff --git a/web/static/images/daniel-avatar.svg b/web/static/images/daniel-avatar.svg
new file mode 100644
index 0000000..d41a554
--- /dev/null
+++ b/web/static/images/daniel-avatar.svg
@@ -0,0 +1,5 @@
+
diff --git a/web/templates/components/footer.html b/web/templates/components/footer.html
index f22ed0c..38f9ebb 100644
--- a/web/templates/components/footer.html
+++ b/web/templates/components/footer.html
@@ -1,42 +1,39 @@
{{define "footer"}}
-