diff --git a/.github/workflows/visual-review-project-pages.yml b/.github/workflows/visual-review-project-pages.yml index 3f9c7a23..01568d21 100644 --- a/.github/workflows/visual-review-project-pages.yml +++ b/.github/workflows/visual-review-project-pages.yml @@ -50,7 +50,7 @@ jobs: - name: Capture home across 4 viewports plus reduced motion run: node scripts/capture-home.mjs - - name: Capture 9 project routes across 4 viewports + - name: Capture 7 featured project routes across 4 viewports run: node scripts/capture-project-pages.mjs - name: Upload rendered review artifact diff --git a/design/ux-contract.json b/design/ux-contract.json index 991211e0..40a8abf5 100644 --- a/design/ux-contract.json +++ b/design/ux-contract.json @@ -177,13 +177,11 @@ "/harnex", "/korgis", "/local-asr-server", + "/decisio", "/closedroom", "/aura-finance", "/redact-guard", - "/redact-guard-android", - "/performance-lab", - "/traffic-monitoring", - "/traffic-monitoring-android" + "/redact-guard-android" ] }, "criticalJourneys": [ @@ -196,7 +194,7 @@ { "id": "J2", "persona": "Technical Peer / AI Engineer", - "flow": "Hero -> Applications (test beds) -> Observability (real run) -> Deep Dive Project Route / GitHub", + "flow": "Hero -> Applications (test beds) -> Experiments (reproducible evidence) -> Deep Dive Project Route / GitHub", "targetComprehensionSec": 60 }, { diff --git a/package.json b/package.json index 4f11076e..d0a486d3 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "build": "astro build", "preview": "astro preview", "check": "astro check", - "test": "node --test tests/journey-path-model.test.mjs tests/home-experience-contract.test.mjs tests/project-page-contract.test.mjs", + "test": "node --test tests/journey-path-model.test.mjs tests/home-experience-contract.test.mjs tests/project-page-contract.test.mjs tests/redactguard-evidence.test.mjs tests/redactguard-summary.test.mjs", "smoke": "node tests/smoke.js", "verify": "npm run build && npm run test && npm run smoke", "astro": "astro" diff --git a/public/images/decisio/decisio-mark-512.old.png b/public/images/decisio/decisio-mark-512.old.png new file mode 100644 index 00000000..0b0648cd Binary files /dev/null and b/public/images/decisio/decisio-mark-512.old.png differ diff --git a/public/images/decisio/decisio-mark-512.png b/public/images/decisio/decisio-mark-512.png new file mode 100644 index 00000000..b4dcb021 Binary files /dev/null and b/public/images/decisio/decisio-mark-512.png differ diff --git a/public/images/decisio/logo.png b/public/images/decisio/logo.png new file mode 100644 index 00000000..0b0648cd Binary files /dev/null and b/public/images/decisio/logo.png differ diff --git a/scripts/capture-home.mjs b/scripts/capture-home.mjs index ccca0d40..285e142e 100644 --- a/scripts/capture-home.mjs +++ b/scripts/capture-home.mjs @@ -11,6 +11,20 @@ const viewports = [ { name: 'mobile-320', width: 320, height: 700 }, ]; +// Exercise the actual control presented at each viewport (native select on phones). +const selectExperiment = async (page, id) => { + const tab = page.locator('[data-experiment-tab][data-track-id="' + id + '"]'); + if (await tab.isVisible()) { + await tab.click(); + return; + } + const index = await tab.evaluate((element) => + [...document.querySelectorAll('[data-experiment-tab]')].indexOf(element) + ); + if (index < 0) throw new Error('Missing experiment: ' + id); + await page.locator('[data-experiment-mobile-select]').selectOption(String(index)); +}; + const selectorFor = (element) => { const tag = element.tagName.toLowerCase(); if (element.id) return `${tag}#${element.id}`; @@ -40,13 +54,15 @@ const readDiagnostics = async (page) => page.evaluate(() => { const actionTargets = Array.from(document.querySelectorAll('.hero-actions a, .primary-button, .secondary-button')) .map((node) => { const rect = node.getBoundingClientRect(); + const style = getComputedStyle(node); + if (!rect.width || !rect.height || style.display === 'none' || style.visibility === 'hidden') return null; return { label: node.textContent?.replace(/\s+/g, ' ').trim() || '', width: Math.round(rect.width), height: Math.round(rect.height), }; }) - .filter((item) => item.width < 44 || item.height < 44); + .filter((item) => item && (item.width < 44 || item.height < 44)); const smallText = Array.from(body.querySelectorAll('*')) .map((element) => { @@ -68,7 +84,7 @@ const readDiagnostics = async (page) => page.evaluate(() => { }; }) .filter(Boolean) - .slice(0, 50); + .slice(0, 150); const stageVisibility = stages.map((stage) => { const copy = stage.querySelector('.stage-copy'); @@ -108,6 +124,76 @@ const browser = await chromium.launch({ headless: true }); const report = []; try { + // Header collision contract: test the actual rendered bounds across the + // compact/full-navigation breakpoint, including widths where the old 1140px + // shell made the role paint over the first navigation item. + const headerChecks = []; + for (const width of [320, 390, 620, 768, 1024, 1280, 1440, 1450, 1451, 1600, 1700, 1920]) { + const context = await browser.newContext({ + viewport: { width, height: 900 }, + reducedMotion: 'reduce', + }); + const page = await context.newPage(); + try { + await page.goto(baseUrl + '/', { waitUntil: 'networkidle' }); + await page.evaluate(() => document.fonts?.ready); + const layout = await page.evaluate(() => { + const header = document.querySelector('.site-header'); + const brand = header?.querySelector('.brand'); + const role = header?.querySelector('.brand-role'); + const nav = header?.querySelector('.site-nav'); + const actions = header?.querySelector('.header-actions'); + const toggle = header?.querySelector('[data-menu-toggle]'); + const links = [...(nav?.querySelectorAll('.nav-link') ?? [])]; + const visible = el => el && getComputedStyle(el).display !== 'none' && + getComputedStyle(el).visibility !== 'hidden'; + const rect = el => { + if (!el) return null; + const box = el.getBoundingClientRect(); + return { left: box.left, right: box.right, width: box.width }; + }; + return { + width: window.innerWidth, + documentOverflow: Math.max(0, document.documentElement.scrollWidth - window.innerWidth), + brand: rect(brand), + role: visible(role) ? rect(role) : null, + navigation: visible(nav) ? rect(nav) : null, + actions: rect(actions), + navLinks: visible(nav) ? links.map(rect) : [], + menuVisible: Boolean(visible(toggle)), + }; + }); + const navExpected = width > 1450; + const noCollision = layout.brand && layout.actions && + layout.brand.right + 4 <= (layout.navigation?.left ?? layout.actions.left) && + (!layout.navigation || layout.navigation.right + 4 <= layout.actions.left) && + (!layout.role || layout.role.right <= layout.brand.right + 1); + const linksFit = !layout.navigation || + layout.navLinks.every(box => box.left >= layout.navigation.left - 1 && + box.right <= layout.navigation.right + 1); + let menuWorks = true; + if (!navExpected) { + const toggle = page.locator('.site-header [data-menu-toggle]'); + await toggle.click(); + menuWorks = await toggle.getAttribute('aria-expanded') === 'true' && + await page.locator('.site-header .site-nav').isVisible(); + await page.keyboard.press('Escape'); + menuWorks = menuWorks && await toggle.getAttribute('aria-expanded') === 'false'; + } + const record = { ...layout, navExpected, noCollision, linksFit, menuWorks }; + headerChecks.push(record); + if (!noCollision || !linksFit || layout.documentOverflow > 0 || + Boolean(layout.navigation) !== navExpected || + layout.menuVisible === navExpected || !menuWorks) { + throw new Error('Header collision or navigation regression: ' + JSON.stringify(record)); + } + } finally { + await page.close(); + await context.close(); + } + } + await fs.writeFile(path.join(outputDir, 'header-collision-check.json'), JSON.stringify(headerChecks, null, 2)); + for (const viewport of viewports) { const context = await browser.newContext({ viewport: { width: viewport.width, height: viewport.height }, @@ -142,6 +228,443 @@ try { await context.close(); } + // DECIDE: verify actual mobile horizontal movement and desktop comparison. + // Screenshots are review artifacts, not a replacement for DOM/interaction assertions. + const decisionChecks = []; + for (const viewport of [ + { name: 'mobile-320', width: 320, height: 700 }, + { name: 'mobile-390', width: 390, height: 844 }, + { name: 'tablet-768', width: 768, height: 1024 }, + { name: 'desktop-1440', width: 1440, height: 1100 }, + ]) { + for (const theme of ['light', 'dark']) { + const context = await browser.newContext({ + viewport: { width: viewport.width, height: viewport.height }, + reducedMotion: 'reduce', + }); + const page = await context.newPage(); + try { + await page.goto(baseUrl + '/?theme=' + theme, { waitUntil: 'networkidle' }); + await page.evaluate(() => document.fonts?.ready); + const surface = page.locator('#strategy [data-decision-cards]'); + const initial = await page.evaluate(() => { + const region = document.querySelector('#strategy [data-decision-viewport]'); + const cards = [...document.querySelectorAll('#strategy [data-mode]')]; + return { + theme: document.documentElement.dataset.theme, + modeOrder: cards.map((el) => el.dataset.mode), + factorsPerCard: cards.map((el) => el.querySelectorAll('.factor').length), + snap: region ? getComputedStyle(region).scrollSnapType : null, + railScrolls: region ? region.scrollWidth > region.clientWidth + 5 : false, + pageOverflowPx: Math.max(0, document.documentElement.scrollWidth - window.innerWidth), + cardTextSizes: cards.flatMap((card) => + [...card.querySelectorAll('p, h4, span, dt, dd')] + .filter((el) => el.getBoundingClientRect().height > 0 && + [...el.childNodes].some((n) => n.nodeType === Node.TEXT_NODE && n.textContent?.trim())) + .map((el) => Number.parseFloat(getComputedStyle(el).fontSize)) + ), + }; + }); + + await surface.screenshot({ + path: path.join(outputDir, 'decide__' + viewport.name + '__' + theme + '.png'), + }); + + let advancedToSecond = true; + let advancedToThird = true; + if (viewport.width < 1000) { + await page.locator('[data-decision-next]').click(); + await page.waitForFunction(() => + document.querySelector('[data-decision-current]')?.textContent?.trim() === '02' + ); + advancedToSecond = await page.locator('[data-decision-current]').textContent() === '02'; + await page.locator('[data-decision-next]').click(); + await page.waitForFunction(() => + document.querySelector('[data-decision-current]')?.textContent?.trim() === '03' + ); + advancedToThird = await page.locator('[data-decision-current]').textContent() === '03'; + } + + const check = { viewport: viewport.name, ...initial, advancedToSecond, advancedToThird }; + decisionChecks.push(check); + if ( + initial.theme !== theme || + initial.modeOrder.join(',') !== 'local,hybrid,cloud' || + initial.factorsPerCard.some((count) => count !== 5) || + initial.pageOverflowPx > 0 || + initial.cardTextSizes.some((size) => !Number.isFinite(size) || size < 14) || + (viewport.width < 1000 && (!initial.railScrolls || !initial.snap.includes('mandatory'))) || + (viewport.width === 1440 && initial.railScrolls) || + !advancedToSecond || !advancedToThird + ) { + throw new Error('DECIDE responsive carousel regression: ' + JSON.stringify(check)); + } + } finally { + await page.close(); + await context.close(); + } + } + } + await fs.writeFile(path.join(outputDir, 'decide-card-check.json'), JSON.stringify(decisionChecks, null, 2)); + + // TEST: capture the real logos/screenshots and exercise the mobile card rail. + const testGalleryChecks = []; + for (const viewport of [ + { name: 'mobile-320', width: 320, height: 700 }, + { name: 'mobile-390', width: 390, height: 844 }, + { name: 'tablet-768', width: 768, height: 1024 }, + { name: 'desktop-1440', width: 1440, height: 1100 }, + ]) { + for (const theme of ['light', 'dark']) { + const context = await browser.newContext({ + viewport: { width: viewport.width, height: viewport.height }, + reducedMotion: 'reduce', + }); + const page = await context.newPage(); + try { + await page.goto(baseUrl + '/?theme=' + theme, { waitUntil: 'networkidle' }); + await page.evaluate(() => document.fonts?.ready); + const surface = page.locator('#applications [data-test-gallery]'); + await surface.scrollIntoViewIfNeeded(); + await page.evaluate(async () => { + const images = [...document.querySelectorAll('#applications .gallery-card img')]; + await Promise.all(images.map(async (image) => { + image.loading = 'eager'; + try { await image.decode(); } catch { /* captured as naturalWidth = 0 */ } + })); + }); + + const diagnostics = await page.evaluate(() => { + const root = document.querySelector('#applications [data-test-gallery]'); + const rail = root?.querySelector('[data-test-viewport]'); + const slides = [...(root?.querySelectorAll('.gallery-slide') ?? [])]; + const images = [...(root?.querySelectorAll('.gallery-card img') ?? [])]; + const labels = [...(root?.querySelectorAll('.gallery-card h4, .gallery-card p, .gallery-card span') ?? [])]; + return { + theme: document.documentElement.dataset.theme, + applications: slides.map((node) => node.getAttribute('data-application')), + sources: images.map((node) => node.getAttribute('src')), + brokenImages: images.filter((img) => !img.complete || !img.naturalWidth).map((img) => img.src), + linksValid: slides.every((slide) => { + const link = slide.querySelector('.gallery-card'); + return link?.getAttribute('href')?.startsWith('/') && link?.getAttribute('href') !== '/'; + }), + scrollSnap: rail ? getComputedStyle(rail).scrollSnapType : '', + railScrolls: rail ? rail.scrollWidth > rail.clientWidth + 5 : false, + pageOverflowPx: Math.max(0, document.documentElement.scrollWidth - window.innerWidth), + cardHeights: slides.map((slide) => + Math.round(slide.querySelector('.gallery-card')?.getBoundingClientRect().height || 0)), + screenshotHeights: slides.map((slide) => + Math.round(slide.querySelector('.preview-window')?.getBoundingClientRect().height || 0)), + typography: { + heading: getComputedStyle(root.querySelector('.value-headline')).fontFamily, + appName: getComputedStyle(root.querySelector('.app-name')).fontFamily, + question: getComputedStyle(root.querySelector('.product-question')).fontFamily, + metadata: getComputedStyle(root.querySelector('.app-category')).fontFamily, + chips: getComputedStyle(root.querySelector('.feature-chip')).fontFamily, + headlineWeight: getComputedStyle(root.querySelector('.value-headline')).fontWeight, + }, + tinyLabels: labels + .filter((node) => node.getBoundingClientRect().height > 0 && + [...node.childNodes].some((child) => child.nodeType === Node.TEXT_NODE && child.textContent?.trim()) && + Number.parseFloat(getComputedStyle(node).fontSize) < 14) + .map((node) => node.textContent?.trim().slice(0, 50)), + }; + }); + await surface.screenshot({ + path: path.join(outputDir, 'test__' + viewport.name + '__' + theme + '.png'), + }); + + let reachedSecond = true; + let reachedThird = true; + if (viewport.width < 1000) { + await page.locator('[data-test-next]').click(); + await page.waitForFunction(() => document.querySelector('[data-test-current]')?.textContent?.trim() === '02'); + reachedSecond = (await page.locator('[data-test-current]').textContent())?.trim() === '02'; + await page.locator('[data-test-next]').click(); + await page.waitForFunction(() => document.querySelector('[data-test-current]')?.textContent?.trim() === '03'); + reachedThird = (await page.locator('[data-test-current]').textContent())?.trim() === '03'; + } + + const check = { viewport: viewport.name, ...diagnostics, reachedSecond, reachedThird }; + testGalleryChecks.push(check); + if ( + diagnostics.theme !== theme || + diagnostics.applications.join(',') !== 'redact-guard,aura-finance,closedroom' || + diagnostics.sources.length !== 6 || + diagnostics.brokenImages.length !== 0 || + !diagnostics.linksValid || + diagnostics.pageOverflowPx > 0 || + diagnostics.tinyLabels.length > 0 || + diagnostics.cardHeights.some((height) => + height <= 0 || height > (viewport.width === 320 ? 760 : viewport.width === 390 ? 710 : 660)) || + diagnostics.screenshotHeights.some((height) => height < 150 || height > 185) || + !diagnostics.typography.heading.includes('Plus Jakarta Sans') || + !diagnostics.typography.appName.includes('Plus Jakarta Sans') || + !diagnostics.typography.question.includes('Inter') || + !diagnostics.typography.metadata.includes('Inter') || + !diagnostics.typography.chips.includes('Inter') || + (viewport.width < 1000 && (!diagnostics.railScrolls || !diagnostics.scrollSnap.includes('mandatory'))) || + (viewport.width === 1440 && diagnostics.railScrolls) || + !reachedSecond || !reachedThird + ) { + throw new Error('TEST gallery visual/asset regression: ' + JSON.stringify(check)); + } + } finally { + await page.close(); + await context.close(); + } + } + } + await fs.writeFile( + path.join(outputDir, 'test-gallery-check.json'), + JSON.stringify(testGalleryChecks, null, 2), + ); + + // MEASURE: verify the evidence-first mobile experience and progressively + // disclosed comparisons. This intentionally does not require the legacy + // horizontal KPI carousel or a chart visible before the user asks for it. + const measureChecks = []; + for (const viewport of [ + { name: 'mobile-320', width: 320, height: 700 }, + { name: 'mobile-390', width: 390, height: 844 }, + { name: 'tablet-768', width: 768, height: 1024 }, + { name: 'desktop-1440', width: 1440, height: 1100 }, + ]) { + for (const theme of ['light', 'dark']) { + const context = await browser.newContext({ + viewport: { width: viewport.width, height: viewport.height }, + reducedMotion: 'reduce', + }); + const page = await context.newPage(); + try { + await page.goto(baseUrl + '/?theme=' + theme, { waitUntil: 'networkidle' }); + await page.evaluate(() => document.fonts?.ready); + const stage = page.locator('#evidence .experiment-explorer'); + await stage.scrollIntoViewIfNeeded(); + await page.waitForFunction(() => { + const root = document.querySelector('[data-experiment-explorer]'); + if (!root) return false; + return ['[data-evidence-result]', '[data-evidence-error]', '[data-evidence-empty]'] + .some(selector => root.querySelector(selector)?.hidden === false); + }, { timeout: 15000 }); + + const initial = await page.evaluate(() => { + const root = document.querySelector('#evidence [data-experiment-explorer]'); + const visible = node => node && node.getBoundingClientRect().width > 0 && + node.getBoundingClientRect().height > 0 && + getComputedStyle(node).visibility !== 'hidden'; + const result = root.querySelector('[data-evidence-result]'); + const highlights = [...root.querySelectorAll('.mcb-highlights-viewport .highlight')]; + const summary = root.querySelector('.mcb-highlights-viewport'); + const mobileSelect = root.querySelector('[data-experiment-mobile-select]'); + const chooser = root.querySelector('.experiment-mobile-chooser'); + const tabs = [...root.querySelectorAll('[data-experiment-tab]')]; + const takeaway = root.querySelector('[data-mcb-takeaway]'); + const headerBrand = document.querySelector('.site-header .brand'); + const headerNav = document.querySelector('.site-header .site-nav'); + const navMenu = document.querySelector('.site-header [data-menu-toggle]'); + const finding = root.querySelector('[data-readout-title]'); + const findingDetail = root.querySelector('[data-readout]'); + const explore = root.querySelector('[data-mcb-explore-details]'); + const chart = root.querySelector('[data-mcb-chart-details]'); + const methods = root.querySelector('.mcb-method-details'); + const bounds = node => { + const rect = node?.getBoundingClientRect(); + return rect ? { left: rect.left, right: rect.right, width: rect.width } : null; + }; + return { + theme: document.documentElement.dataset.theme, + pageOverflowPx: Math.max(0, document.documentElement.scrollWidth - window.innerWidth), + selected: tabs.find(t => t.getAttribute('aria-selected') === 'true')?.dataset.trackId, + published: tabs.filter(t => t.dataset.published === 'true').length, + unpublished: tabs.filter(t => t.dataset.published === 'false').length, + resultVisible: !result.hidden, + headline: finding?.textContent?.trim() || '', + detail: findingDetail?.textContent?.trim() || '', + findingVisible: visible(takeaway), + findingBackground: takeaway ? getComputedStyle(takeaway).backgroundColor : '', + panelBackground: getComputedStyle(root.querySelector('.evidence-panel')).backgroundColor, + headlineSize: finding ? parseFloat(getComputedStyle(finding).fontSize) : 0, + highlights: highlights.map(node => ({ + role: node.dataset.highlightRole, + label: node.querySelector('span')?.textContent?.trim(), + value: node.querySelector('strong')?.textContent?.trim(), + bounds: bounds(node), + })), + overviewWidth: summary?.clientWidth ?? 0, + overviewScrollWidth: summary?.scrollWidth ?? 0, + experimentSelectVisible: visible(chooser) && visible(mobileSelect), + experimentRailVisible: visible(root.querySelector('.experiment-selector-wrap')), + selectedOption: mobileSelect?.value, + explorerClosed: !explore?.open, + chartClosed: !chart?.open, + methodsClosed: !methods?.open, + comparisonWithinDisclosure: Boolean(explore?.contains(root.querySelector('.mcb-model-browser'))), + scopeWithinMethods: Boolean(methods?.contains(root.querySelector('[data-mcb-scope]'))), + contentOrder: [...root.querySelectorAll( + '[data-mcb-takeaway], .mcb-highlights-viewport, .mcb-explore-details, .mcb-method-details' + )].map(node => + node.matches('[data-mcb-takeaway]') ? 'finding' : + node.matches('.mcb-highlights-viewport') ? 'highlights' : + node.matches('.mcb-explore-details') ? 'explore' : 'methods' + ), + headerBrand: bounds(headerBrand), + headerNav: bounds(headerNav), + headerMenuVisible: visible(navMenu), + }; + }); + + await page.locator('#evidence').screenshot({ + path: path.join(outputDir, 'measure__' + viewport.name + '__' + theme + '.png'), + }); + + const isPhone = viewport.width <= 390; + let switchWorks = true; + if (isPhone) { + await selectExperiment(page, 'vlm-capability'); + switchWorks = (await page.locator('[data-experiment-tab][aria-selected="true"]') + .getAttribute('data-track-id')) === 'vlm-capability' && + await page.locator('[data-evidence-empty]').isVisible(); + await selectExperiment(page, 'model-capability'); + await page.locator('[data-evidence-result]').waitFor({ state: 'visible' }); + switchWorks = switchWorks && + (await page.locator('[data-experiment-tab][aria-selected="true"]') + .getAttribute('data-track-id')) === 'model-capability'; + } + + await page.locator('[data-mcb-explore-details] > summary').click(); + const exploreOpen = await page.locator('[data-mcb-explore-details]').evaluate(node => node.open); + const localFilter = page.locator('.mcb-list-filters [data-mcb-filter="local"]'); + await localFilter.click(); + const filterWorks = (await localFilter.getAttribute('aria-pressed')) === 'true' && + await page.locator('[data-mcb-model-cards] [data-mcb-card]').count() > 0; + const showAll = page.locator('[data-mcb-show-all]'); + let showAllWorks = true; + if (await showAll.isVisible()) { + await showAll.click(); + showAllWorks = (await showAll.getAttribute('aria-expanded')) === 'true'; + } + await page.locator('[data-mcb-chart-details] > summary').click(); + const chartOpen = await page.locator('[data-mcb-chart-details]').evaluate(node => node.open); + const plotFocusable = (await page.locator('.plot-canvas').getAttribute('tabindex')) === '0'; + await page.locator('.mcb-method-details > summary').click(); + const methodsOpen = await page.locator('.mcb-method-details').evaluate(node => node.open); + const scopedCount = (await page.locator('[data-mcb-scope]').textContent())?.trim() || ''; + + await selectExperiment(page, 'vlm-capability'); + const unpublishedVisible = await page.locator('[data-evidence-empty]').isVisible(); + const unpublishedSelected = await page.locator( + '[data-experiment-tab][aria-selected="true"]' + ).getAttribute('data-track-id'); + + const check = { + viewport: viewport.name, ...initial, switchWorks, exploreOpen, filterWorks, + showAllWorks, chartOpen, plotFocusable, methodsOpen, scopedCount, + unpublishedVisible, unpublishedSelected, + }; + measureChecks.push(check); + + if ( + initial.theme !== theme || + initial.pageOverflowPx > 0 || + initial.selected !== 'model-capability' || + initial.published !== 3 || initial.unpublished !== 3 || + !initial.resultVisible || !initial.findingVisible || + !initial.headline || !initial.detail || initial.headlineSize < 19 || + initial.findingBackground === initial.panelBackground || + initial.highlights.map(metric => metric.role).join(',') !== + 'best-local,best-overall' || + initial.highlights.some(metric => !metric.value || metric.value === '—') || + initial.highlights.some(metric => metric.bounds?.width < 75) || + initial.overviewScrollWidth > initial.overviewWidth + 4 || + initial.contentOrder.join(',') !== 'finding,highlights,explore,methods' || + !initial.explorerClosed || !initial.chartClosed || !initial.methodsClosed || + !initial.comparisonWithinDisclosure || !initial.scopeWithinMethods || + (isPhone && (!initial.experimentSelectVisible || initial.experimentRailVisible)) || + (!isPhone && (!initial.experimentRailVisible || initial.experimentSelectVisible)) || + !initial.headerBrand || initial.headerBrand.right > viewport.width || + !initial.headerMenuVisible || + !switchWorks || !exploreOpen || !filterWorks || !showAllWorks || + !chartOpen || !plotFocusable || !methodsOpen || !scopedCount || + !unpublishedVisible || unpublishedSelected !== 'vlm-capability' + ) { + throw new Error('MEASURE progressive disclosure regression: ' + JSON.stringify(check)); + } + } finally { + await page.close(); + await context.close(); + } + } + } + await fs.writeFile(path.join(outputDir, 'measure-mobile-check.json'), JSON.stringify(measureChecks, null, 2)); + + // The BUILD redesign must be inspected in both themes, independently of + // the whole-page journey animation. Element captures make visual PR review + // practical without changing how navigation or the method path works. + const buildShowcaseChecks = []; + for (const viewport of [ + { name: 'desktop-1440', width: 1440, height: 1100 }, + { name: 'mobile-390', width: 390, height: 844 }, + ]) { + for (const theme of ['light', 'dark']) { + const buildContext = await browser.newContext({ + viewport: { width: viewport.width, height: viewport.height }, + reducedMotion: 'reduce', + }); + const buildPage = await buildContext.newPage(); + try { + await buildPage.goto(baseUrl + '/?theme=' + theme, { waitUntil: 'networkidle' }); + await buildPage.evaluate(() => document.fonts?.ready); + const diagnostics = await buildPage.evaluate(() => { + const showcase = document.querySelector('#infrastructure .systems-showcase'); + const runtimes = Array.from(showcase?.querySelectorAll('.runtime-card') ?? []); + const primitives = Array.from(showcase?.querySelectorAll('.primitive') ?? []); + return { + theme: document.documentElement.dataset.theme, + runtimeIds: runtimes.map((card) => card.getAttribute('data-system')), + primitiveIds: primitives.map((card) => card.getAttribute('data-system')), + validLinks: [...runtimes, ...primitives].every( + (card) => card.getAttribute('href')?.startsWith('/') && card.getAttribute('href') !== '/' + ), + horizontalOverflowPx: Math.max(0, document.documentElement.scrollWidth - window.innerWidth), + narrowText: [...(showcase?.querySelectorAll('p, h3, h4, span') ?? [])] + .filter((node) => { + const rect = node.getBoundingClientRect(); + const style = getComputedStyle(node); + return rect.width > 0 && rect.height > 0 && + style.visibility !== 'hidden' && + Number.parseFloat(style.fontSize) < 14 && + [...node.childNodes].some((child) => child.nodeType === Node.TEXT_NODE && child.textContent?.trim()); + }).map((node) => node.className || node.tagName.toLowerCase()), + }; + }); + const record = { viewport: viewport.name, ...diagnostics }; + buildShowcaseChecks.push(record); + await buildPage.locator('#infrastructure .systems-showcase').screenshot({ + path: path.join(outputDir, 'build__' + viewport.name + '__' + theme + '.png'), + }); + if ( + diagnostics.theme !== theme || + diagnostics.runtimeIds.join(',') !== 'korgis,harnex' || + diagnostics.primitiveIds.join(',') !== 'decisio,local-asr-server' || + !diagnostics.validLinks || + diagnostics.horizontalOverflowPx !== 0 || + diagnostics.narrowText.length > 0 + ) { + throw new Error('BUILD brand/responsive visual regression: ' + JSON.stringify(record)); + } + } finally { + await buildPage.close(); + await buildContext.close(); + } + } + } + await fs.writeFile( + path.join(outputDir, 'build-showcase-check.json'), + JSON.stringify(buildShowcaseChecks, null, 2), + ); + const reducedContext = await browser.newContext({ viewport: { width: 390, height: 844 }, reducedMotion: 'reduce', @@ -172,6 +695,366 @@ try { await reducedPage.close(); await reducedContext.close(); + + // Regression: JEV rows are created at runtime. Astro's scoped CSS does not + // automatically apply to these elements, so check actual computed layout. + const jevEvidence = { + schema_version: 1, + benchmark: 'jev-vs-llm', + publication: { + scope: 'non_restricted_aggregates_only', + restricted_jev_typesafe_clm_metrics_included: false, + comparison_status: 'partial_provider_subset', + }, + experiments: ['routing', 'calibration', 'scaling', 'workflow', 'agent'].map((id) => ({ + id, status: id === 'routing' ? 'published_subset' : 'not_published', + })), + routing: { + status: 'published_subset', + providers: ['local-korgis'], + models: [ + { model: 'qwen3.5-9b-q4km', provider: 'local-korgis', accuracy: 0.64, latency_p50_ms: 8300, requests: 77 }, + { model: 'qwen3.5-2b-q4km', provider: 'local-korgis', accuracy: 0.567, latency_p50_ms: 2000, requests: 77 }, + { model: 'nemotron-nano-4b', provider: 'local-korgis', accuracy: 0.5, latency_p50_ms: 6000, requests: 77 }, + { model: 'nemotron-nano-4b-q8', provider: 'local-korgis', accuracy: 0.521, latency_p50_ms: 6100, requests: 77 }, + { model: 'qwen3.5-0.8b-q4km', provider: 'local-korgis', accuracy: 0.25, latency_p50_ms: 704, requests: 24 }, + ], + }, + }; + const matchedMcbEvidence = { + cells: [ + { model_key: 'gpt-5.6-luna', capability_id: 'intent-classification', + primary_metric: 'accuracy', primary_value: 0.8246753246753247, + sample_count: 154, benchmark_signature: 'same-mcb-benchmark', deployment: 'api' }, + { model_key: 'jev-1.13.0', capability_id: 'intent-classification', + primary_metric: 'accuracy', primary_value: 0.7467532467532467, + sample_count: 154, benchmark_signature: 'same-mcb-benchmark', deployment: 'api' }, + { model_key: 'decisio-qwen3.5-4b-q4km', capability_id: 'intent-classification', + primary_metric: 'accuracy', primary_value: 0.7012987012987013, + sample_count: 154, benchmark_signature: 'same-mcb-benchmark', deployment: 'local' }, + { model_key: 'qwen3.5-9b-q4km', capability_id: 'intent-classification', + primary_metric: 'accuracy', primary_value: 0.6948051948051948, + sample_count: 154, benchmark_signature: 'same-mcb-benchmark', deployment: 'local' }, + { model_key: 'jev-other', capability_id: 'intent-classification', + primary_metric: 'accuracy', primary_value: 0.99, + sample_count: 77, benchmark_signature: 'unrelated', deployment: 'api' }, + ], + models: [], + capabilities: [], + frontier: { points: [] }, + }; + + const jevLayouts = []; + for (const viewport of [ + { name: 'desktop', width: 1440, height: 1000, expectedColumns: 3 }, + { name: 'mobile', width: 390, height: 844, expectedColumns: 2 }, + ]) { + const context = await browser.newContext({ + viewport: { width: viewport.width, height: viewport.height }, + reducedMotion: 'reduce', + }); + const page = await context.newPage(); + await page.route('**/experiments/jev-vs-llm/results/published/portfolio-summary.json', (route) => + route.fulfill({ + status: 200, + contentType: 'application/json', + headers: { 'access-control-allow-origin': '*' }, + body: JSON.stringify(jevEvidence), + }), + ); + await page.route('**/experiments/model-capability-benchmark/results/published/models-overview.json', (route) => + route.fulfill({ + status: 200, + contentType: 'application/json', + headers: { 'access-control-allow-origin': '*' }, + body: JSON.stringify(matchedMcbEvidence), + }), + ); + await page.goto(`${baseUrl}/`, { waitUntil: 'domcontentloaded' }); + await selectExperiment(page, 'jev-vs-llm-routing'); + await page.locator('[data-jev-mcb-rows] .jev-mcb-row').first().waitFor({ state: 'visible' }); + + const bridge = await page.evaluate(() => { + const rows = [...document.querySelectorAll('[data-jev-mcb-rows] .jev-mcb-row')]; + const labels = rows.map((row) => row.textContent || ''); + const sourceNote = document.querySelector('[data-jev-mcb-meta]')?.textContent || ''; + const details = document.querySelector('[data-jev-local-details]'); + const row = rows[0]; + return { + names: labels, + count: rows.length, + consistentSource: sourceNote.includes('154') && sourceNote.includes('shared benchmark signature'), + grid: row ? getComputedStyle(row).display : null, + localInitiallyClosed: details instanceof HTMLDetailsElement && !details.open, + }; + }); + if ( + bridge.count !== 4 || + bridge.grid !== 'grid' || + !bridge.names.some((name) => name.includes('JEV 1.13.0') && name.includes('74.7%')) || + !bridge.names.some((name) => name.includes('GPT-5.6 Luna') && name.includes('82.5%')) || + !bridge.consistentSource || + !bridge.localInitiallyClosed + ) { + throw new Error(`Missing or non-comparable JEV/GPT benchmark bridge on ${viewport.name}: ${JSON.stringify(bridge)}`); + } + + await page.locator('[data-jev-summary]').screenshot({ + path: path.join(outputDir, `jev-mcb-bridge__${viewport.name}.png`), + }); + await page.locator('[data-jev-local-details] summary').click(); + await page.locator('[data-jev-rows] .jev-model-row').first().waitFor({ state: 'visible' }); + + const diagnostics = await page.evaluate(() => { + const row = document.querySelector('[data-jev-rows] .jev-model-row'); + const track = row?.querySelector('.jev-model-row__track'); + const fill = row?.querySelector('.jev-model-row__fill'); + const name = row?.querySelector('.jev-model-row__name'); + const accuracy = row?.querySelector('.jev-model-row__accuracy'); + const latency = row?.querySelector('.jev-model-row__latency'); + const css = row ? getComputedStyle(row) : null; + const rowChildren = [name, accuracy, latency].map((node) => + node?.getBoundingClientRect() || null, + ); + const mobile = window.innerWidth <= 520; + return { + rowCount: document.querySelectorAll('[data-jev-rows] .jev-model-row').length, + display: css?.display || null, + renderedColumns: css?.gridTemplateColumns?.split(' ').length ?? 0, + trackHeight: track?.getBoundingClientRect().height || 0, + fillWidth: fill?.getBoundingClientRect().width || 0, + columnsHaveWidth: rowChildren.every((rect) => rect && rect.width > 20), + columnsAligned: mobile + ? Boolean(rowChildren[0] && rowChildren[1] && rowChildren[2] && + rowChildren[0].bottom <= rowChildren[1].top + 3) + : Boolean(rowChildren[0] && rowChildren[1] && rowChildren[2] && + rowChildren[0].left < rowChildren[1].left && + rowChildren[1].left < rowChildren[2].left), + takeaway: document.querySelector('[data-jev-takeaway-headline]')?.textContent?.trim(), + horizontalOverflowPx: Math.max(0, document.documentElement.scrollWidth - window.innerWidth), + }; + }); + jevLayouts.push({ viewport: viewport.name, ...diagnostics }); + + await page.locator('[data-jev-summary]').screenshot({ + path: path.join(outputDir, `jev-comparison__${viewport.name}.png`), + }); + await page.close(); + await context.close(); + + if ( + diagnostics.rowCount !== 4 || + diagnostics.display !== 'grid' || + diagnostics.renderedColumns !== viewport.expectedColumns || + diagnostics.trackHeight < 5 || + diagnostics.fillWidth < 5 || + !diagnostics.columnsHaveWidth || + !diagnostics.columnsAligned || + !diagnostics.takeaway || + diagnostics.horizontalOverflowPx > 0 + ) { + throw new Error(`JEV visual regression in ${viewport.name}: ${JSON.stringify(diagnostics)}`); + } + } + await fs.writeFile( + path.join(outputDir, 'jev-layout-check.json'), + JSON.stringify(jevLayouts, null, 2), + ); + + const redactionEvidence = { + schema_version: 1, + benchmark: 'redactguard-local-anonymization', + publication: { + status: 'dataset_evidence_only', + has_published_model_metrics: false, + contains_raw_pii: false, + }, + datasets: [ + { id: 'smoke', title: 'Smoke', kind: 'smoke', + focus: 'Contract and pipeline checks', cases: 20, + source_path: 'experiments/redactguard-local-anonymization/data/smoke/cases.jsonl', + results_status: 'no_published_run' }, + { id: 'healthcare', title: 'Healthcare', kind: 'challenge', + focus: 'Clinical and sensitive PII', cases: 48, + source_path: 'experiments/redactguard-local-anonymization/data/healthcare/cases.jsonl', + results_status: 'no_published_run' }, + { id: 'ambiguous', title: 'Ambiguous PII', kind: 'challenge', + focus: 'Context-dependent private vs public data', cases: 48, + source_path: 'experiments/redactguard-local-anonymization/data/ambiguous/cases.jsonl', + results_status: 'no_published_run' }, + { id: 'challenge', title: 'Hard cases', kind: 'challenge', + focus: 'Semantic ambiguity and difficult PII', cases: 46, + source_path: 'experiments/redactguard-local-anonymization/data/challenge/cases.jsonl', + results_status: 'no_published_run' }, + { id: 'document-e2e', title: 'Document E2E', kind: 'documents', + focus: 'Extraction plus privacy evaluation', cases: 5, + source_path: 'experiments/redactguard-local-anonymization/data/documents/manifest.jsonl', + results_status: 'no_published_run' }, + { id: 'realistic', title: 'Realistic documents', kind: 'documents', + focus: 'Heterogeneous formats and annotated PII', documents: 11, gold_spans: 1566, + source_path: 'experiments/redactguard-local-anonymization/data/realistic/annotations/summary.json', + results_status: 'no_published_run' }, + ], + models: [], + }; + const redactionLayouts = []; + for (const viewport of [ + { name: 'desktop', width: 1440, height: 950 }, + { name: 'mobile', width: 390, height: 844 }, + ]) { + const context = await browser.newContext({ + viewport: { width: viewport.width, height: viewport.height }, + reducedMotion: 'reduce', + }); + const page = await context.newPage(); + + await page.route('**/redactguard-local-anonymization/results/published/portfolio-summary.json', + (route) => route.fulfill({ + status: 200, + contentType: 'application/json', + headers: { 'access-control-allow-origin': '*' }, + body: JSON.stringify(redactionEvidence), + }), + ); + await page.goto(`${baseUrl}/`, { waitUntil: 'domcontentloaded' }); + await selectExperiment(page, 'redactguard-local-anonymization'); + await page.locator('[data-rg-dataset="realistic"]').waitFor({ state: 'visible' }); + + const before = await page.evaluate(() => ({ + count: document.querySelector('[data-rg-count]')?.textContent?.trim(), + spans: document.querySelector('[data-rg-extra]')?.textContent?.trim(), + pills: document.querySelectorAll('[data-rg-datasets] button').length, + resultHidden: document.querySelector('[data-rg-comparison]')?.hidden, + pendingVisible: !document.querySelector('[data-rg-no-models]')?.hidden, + activeStyle: getComputedStyle( + document.querySelector('[data-rg-dataset="realistic"]'), + ).backgroundColor, + overflow: Math.max(0, document.documentElement.scrollWidth - window.innerWidth), + })); + await page.locator('[data-rg-dataset="healthcare"]').click(); + const after = await page.evaluate(() => ({ + count: document.querySelector('[data-rg-count]')?.textContent?.trim(), + title: document.querySelector('[data-rg-dataset-name]')?.textContent?.trim(), + active: document.querySelector('[data-rg-dataset="healthcare"]')?.getAttribute('aria-pressed'), + })); + + redactionLayouts.push({ viewport: viewport.name, before, after }); + if ( + before.count !== '11' || + before.spans !== '1,566 gold PII spans' || + before.pills !== 6 || + !before.resultHidden || + !before.pendingVisible || + before.overflow > 0 || + after.count !== '48' || + after.title !== 'Healthcare' || + after.active !== 'true' + ) { + throw new Error(`RedactGuard public evidence visual regression: ${JSON.stringify({ viewport, before, after })}`); + } + + await page.locator('[data-rg-summary]').screenshot({ + path: path.join(outputDir, 'redactguard-evidence__' + viewport.name + '.png'), + }); + + // Synthetic metrics are test fixtures ONLY. No numerical model quality + // figures are checked into the published RedactGuard evidence artifact. + const row = (model, dataset, cohort, recall, leakage, latency) => ({ + model, dataset, cases: 48, + pii_recall: recall, + system_pii_recall: recall, + system_leakage_rate: leakage, + precision: 0.93, + latency_p95_ms: latency, + provenance: { + run_id: cohort, cohort_id: cohort, + comparison_scope: 'same_run_exploratory', + }, + }); + await page.close(); + const measuredEvidence = { + ...redactionEvidence, + publication: { + ...redactionEvidence.publication, + status: 'model_aggregates_published', + has_published_model_metrics: true, + }, + models: [ + row('test-qwen', 'healthcare', 'hc-one', 0.84, 0.16, 760), + row('test-nemotron', 'healthcare', 'hc-one', 0.91, 0.09, 1200), + row('test-qwen', 'ambiguous', 'amb-one', 0.66, 0.34, 900), + ], + }; + // Independent browser page: fixture modes must not share UI state. + const measuredPage = await context.newPage(); + await measuredPage.route('**/redactguard-local-anonymization/results/published/portfolio-summary.json', + (route) => route.fulfill({ + status: 200, + contentType: 'application/json', + headers: { 'access-control-allow-origin': '*' }, + body: JSON.stringify(measuredEvidence), + }), + ); + await measuredPage.goto(`${baseUrl}/`, { waitUntil: 'domcontentloaded' }); + await selectExperiment(measuredPage, 'redactguard-local-anonymization'); + // This fixture has no Challenge run. Explicitly choose Healthcare + // before asserting measured insights, rather than assuming a winner exists. + await measuredPage.locator('[data-rg-dataset="healthcare"]').waitFor({ state: 'visible' }); + await measuredPage.locator('[data-rg-dataset="healthcare"]').click(); + await measuredPage.locator('[data-rg-insights] .rg-insight-card').first().waitFor({ state: 'visible', timeout: 12000 }); + await measuredPage.locator('[data-rg-advanced] summary').click(); + await measuredPage.locator('[data-rg-matrix-scroll] table').waitFor({ state: 'visible' }); + + const modelPreview = await measuredPage.evaluate(() => ({ + insightCards: document.querySelectorAll('[data-rg-insights] .rg-insight-card').length, + comparisonRows: document.querySelectorAll('[data-rg-model-rows] .rg-model-row').length, + recallTracks: document.querySelectorAll('[data-rg-model-rows] .rg-measure--recall .rg-measure__track').length, + summaryTitle: document.querySelector('[data-rg-takeaway-text]')?.textContent || '', + visibleGrid: getComputedStyle(document.querySelector('[data-rg-model-rows] .rg-model-row')).display, + })); + if (modelPreview.insightCards !== 3 || modelPreview.comparisonRows !== 2 || + modelPreview.recallTracks !== 2 || modelPreview.visibleGrid !== 'grid') { + throw new Error('RedactGuard insight-first model rows regressed: ' + JSON.stringify(modelPreview)); + } + const measured = await measuredPage.evaluate(() => ({ + matrixVisible: !document.querySelector('[data-rg-matrix]').hidden, + rows: document.querySelectorAll('[data-rg-matrix-scroll] tbody tr').length, + cells: document.querySelector('[data-rg-matrix-scroll]')?.textContent, + paretoVisible: !document.querySelector('[data-rg-pareto]').hidden, + dots: document.querySelectorAll('[data-rg-pareto-plot] circle').length, + overflow: Math.max(0, document.documentElement.scrollWidth - window.innerWidth), + })); + if (!measured.matrixVisible || measured.rows !== 2 || + !measured.cells.includes('84.0%') || + !measured.paretoVisible || measured.dots !== 2 || + measured.overflow !== 0) { + throw new Error('Measured RedactGuard UI regression in ' + + viewport.name + ': ' + JSON.stringify(measured)); + } + await measuredPage.locator('[data-rg-metric="system_leakage_rate"]').click(); + const leakage = await measuredPage.locator('[data-rg-matrix-scroll]').textContent(); + if (!leakage.includes('16.0%') || !leakage.includes('9.0%')) { + throw new Error('RedactGuard metric toggle failed in ' + viewport.name); + } + await measuredPage.locator('[data-rg-summary]').screenshot({ + path: path.join(outputDir, 'redactguard-measured-fixture__' + viewport.name + '.png'), + }); + await measuredPage.locator('[data-rg-dataset="ambiguous"]').click(); + const crossRun = await measuredPage.evaluate(() => ({ + paretoHidden: document.querySelector('[data-rg-pareto]').hidden, + pendingVisible: !document.querySelector('[data-rg-pareto-missing]').hidden, + })); + if (!crossRun.paretoHidden || !crossRun.pendingVisible) { + throw new Error('RedactGuard incorrectly drew an unpaired Pareto: ' + + JSON.stringify(crossRun)); + } + await measuredPage.close(); + await context.close(); + } + await fs.writeFile( + path.join(outputDir, 'redactguard-layout-check.json'), + JSON.stringify(redactionLayouts, null, 2), + ); } finally { await browser.close(); } diff --git a/scripts/capture-project-pages.mjs b/scripts/capture-project-pages.mjs index 49fcc950..7091035e 100644 --- a/scripts/capture-project-pages.mjs +++ b/scripts/capture-project-pages.mjs @@ -9,12 +9,10 @@ const routes = [ 'harnex', 'korgis', 'local-asr-server', + 'decisio', 'closedroom', 'aura-finance', 'redact-guard', - 'performance-lab', - 'traffic-monitoring', - 'traffic-monitoring-android', ]; const phaseIds = ['decide', 'build', 'test', 'measure', 'decide-again']; diff --git a/src/components/landing/AboutSignal.astro b/src/components/landing/AboutSignal.astro index 2f54a69a..c20f3f78 100644 --- a/src/components/landing/AboutSignal.astro +++ b/src/components/landing/AboutSignal.astro @@ -135,10 +135,10 @@ const proofPoints = [ margin: 0 0 var(--space-4); color: var(--ink-display); font-family: var(--font-display); - font-size: clamp(1.75rem, 3.1vw, 2.95rem); - font-weight: 820; - letter-spacing: -0.042em; - line-height: 1.08; + font-size: var(--text-section-title); + font-weight: var(--weight-display); + letter-spacing: var(--tracking-section-title); + line-height: var(--leading-section-title); text-wrap: balance; } @@ -150,14 +150,14 @@ const proofPoints = [ max-width: 620px; margin: 0; color: var(--ink-muted); - font-size: clamp(1rem, 1.25vw, 1.12rem); + font-size: var(--text-section-intro); line-height: 1.6; text-wrap: pretty; } .about-summary strong { color: var(--ink-display); - font-weight: 720; + font-weight: var(--weight-heading); } .about-actions { @@ -183,8 +183,8 @@ const proofPoints = [ display: inline-flex; align-items: center; justify-content: center; - width: 42px; - height: 42px; + width: var(--touch-target-min); + height: var(--touch-target-min); border-radius: var(--radius-full); border: 1px solid var(--border-medium); background: var(--surface-card); @@ -293,7 +293,7 @@ const proofPoints = [ color: var(--accent-blue); font-family: var(--font-mono); font-size: var(--text-caption); - font-weight: 700; + font-weight: var(--weight-heading); } .proof-item h3 { @@ -301,7 +301,7 @@ const proofPoints = [ color: var(--ink-display); font-family: var(--font-display); font-size: 1rem; - font-weight: 780; + font-weight: var(--weight-display); line-height: 1.35; } @@ -362,7 +362,7 @@ const proofPoints = [ } .about-content h2 { - font-size: clamp(1.65rem, 8vw, 2.3rem); + font-size: var(--text-section-title-mobile); } .portrait-circle-wrapper { @@ -403,6 +403,14 @@ const proofPoints = [ } } + @media (max-width: 760px) { + .about-summary { font-size: var(--text-section-intro-mobile); } + } + + @media (min-width: 681px) and (max-width: 760px) { + .about-content h2 { font-size: var(--text-section-title-mobile); } + } + @media (prefers-reduced-motion: reduce) { .portrait-circle { transition: none; diff --git a/src/components/landing/BuildStage.astro b/src/components/landing/BuildStage.astro index 39338101..31e1183b 100644 --- a/src/components/landing/BuildStage.astro +++ b/src/components/landing/BuildStage.astro @@ -1,8 +1,8 @@ --- -/** Plane 02: Architecture & Infrastructure / BUILD. The graphic is managed independently. */ +/** Plane 02: reusable infrastructure / BUILD. */ import { landingData } from '../../content/locales/en/landing'; import PlaneTemplate from './PlaneTemplate.astro'; -import EcosystemDiagram from './EcosystemDiagram.astro'; +import BuildSystemsShowcase from './BuildSystemsShowcase.astro'; const { buildStage } = landingData; --- @@ -17,7 +17,7 @@ const { buildStage } = landingData; intro={buildStage.intro} handoff={buildStage.handoff} nextLabel="TEST" - visualLabel="Local AI Execution Layer Ecosystem Diagram" + visualLabel="Reusable AI infrastructure systems" > - + diff --git a/src/components/landing/BuildSystemsShowcase.astro b/src/components/landing/BuildSystemsShowcase.astro new file mode 100644 index 00000000..0a6f3535 --- /dev/null +++ b/src/components/landing/BuildSystemsShowcase.astro @@ -0,0 +1,627 @@ +--- +/** + * BUILD / Architecture Studio + * Two execution environments, followed by cross-workload primitives. + * Uses the shared home palette and the canonical landing project data. + */ +import { landingData } from '../../content/locales/en/landing'; + +const { buildStage } = landingData; +const baseUrl = import.meta.env.BASE_URL.replace(/\/$/, ''); + +const hrefFor = (href: string, external?: boolean) => { + if (external || href.startsWith('http')) return href; + return baseUrl + '/' + href.replace(/^\//, ''); +}; + +const runtimeProjects = buildStage.projects.filter( + (project) => project.id === 'korgis' || project.id === 'harnex', +); +const specializedProjects = buildStage.projects.filter( + (project) => project.id !== 'korgis' && project.id !== 'harnex', +); + +const platformFor = (id: string) => + id === 'harnex' ? 'ANDROID · ON-DEVICE' : 'MAC · DESKTOP & SERVER'; +const scopeFor = (id: string) => + id === 'harnex' ? 'Android · Offline · On-device' : 'macOS · Workstation · Server'; +--- + +
+
+

Execution environments

+

Two platforms. Dedicated runtimes.

+
+ + + +
+
+

Specialized components

+

Built for specific workloads

+
+ + + +

+ {String(buildStage.projects.length).padStart(2, '0')} reusable systems + + {String(runtimeProjects.length).padStart(2, '0')} execution environments + + {String(specializedProjects.length).padStart(2, '0')} specialized components +

+
+ +

+ Each system solves a different execution problem. Real product integrations are shown in the following TEST stage. +

+
+ + diff --git a/src/components/landing/DecisionStage.astro b/src/components/landing/DecisionStage.astro index 543f866f..a11ddaa6 100644 --- a/src/components/landing/DecisionStage.astro +++ b/src/components/landing/DecisionStage.astro @@ -14,10 +14,10 @@ const { decisionStage } = landingData; surface="subtle" kicker="" title="Where should AI run?" - intro="Start with the workload, not the model. Privacy, latency, control, capability and scale tell you whether Local, Hybrid or Cloud makes sense." + intro="Start with the workload, not the model. Compare what Local, Hybrid and Cloud make possible—and the constraints each one introduces." handoff={decisionStage.handoff} nextLabel="BUILD" - visualLabel="Local, Hybrid and Cloud workload decision matrix" + visualLabel="Local, Hybrid and Cloud deployment choices and trade-offs" > diff --git a/src/components/landing/DecisionTable.astro b/src/components/landing/DecisionTable.astro index 40b34668..b583b629 100644 --- a/src/components/landing/DecisionTable.astro +++ b/src/components/landing/DecisionTable.astro @@ -1,606 +1,684 @@ --- -interface ComparisonItem { - text: string; - strength?: boolean; +/** + * DECIDE / Value-first deployment cards. + * Native horizontal scrolling and scroll-snap work without JavaScript; + * lightweight controls progressively enhance the rail on narrow screens. + */ +interface Factor { + label: string; + value: string; + emphasis?: boolean; } -interface CriterionRow { - dimension: string; - detail: string; - local: ComparisonItem; - hybrid: ComparisonItem; - cloud: ComparisonItem; +interface DeploymentMode { + id: 'local' | 'hybrid' | 'cloud'; + name: string; + number: string; + eyebrow: string; + headline: string; + description: string; + bestFor: string; + factors: Factor[]; } -const criteria: CriterionRow[] = [ +const modes: DeploymentMode[] = [ { - dimension: 'Privacy', - detail: 'Where data is processed', - local: { text: 'Stays on device', strength: true }, - hybrid: { text: 'Split by policy' }, - cloud: { text: 'Processed remotely' }, + id: 'local', + name: 'Local AI', + number: '01', + eyebrow: 'ON-DEVICE', + headline: 'Privacy and control, by design.', + description: + 'Run AI close to the data. Keep sensitive processing on the device and own the runtime end to end.', + bestFor: 'Sensitive workloads, offline reliability and predictable local interactions.', + factors: [ + { label: 'Privacy', value: 'Data stays on device', emphasis: true }, + { label: 'Latency', value: 'Can work offline', emphasis: true }, + { label: 'Control', value: 'Own the runtime', emphasis: true }, + { label: 'Models', value: 'Device-bound' }, + { label: 'Scale', value: 'Hardware-limited' }, + ], }, { - dimension: 'Network & latency', - detail: 'Connectivity dependency', - local: { text: 'Works without network', strength: true }, - hybrid: { text: 'Local fallback' }, - cloud: { text: 'Network required' }, + id: 'hybrid', + name: 'Hybrid', + number: '02', + eyebrow: 'POLICY-ROUTED', + headline: 'Control where it matters. Power when needed.', + description: + 'Keep critical steps local, then route selected tasks to remote capacity when the workload calls for it.', + bestFor: 'Mixed workloads with different privacy, capability and capacity needs.', + factors: [ + { label: 'Privacy', value: 'Split by policy', emphasis: true }, + { label: 'Latency', value: 'Local fallback possible' }, + { label: 'Control', value: 'Shared across layers' }, + { label: 'Models', value: 'Route by workload', emphasis: true }, + { label: 'Scale', value: 'Cloud burst when needed', emphasis: true }, + ], }, { - dimension: 'Runtime control', - detail: 'Deployment & model lifecycle', - local: { text: 'You control the runtime', strength: true }, - hybrid: { text: 'Shared control' }, - cloud: { text: 'Provider managed' }, - }, - { - dimension: 'Model capability', - detail: 'Largest model available', - local: { text: 'Bound by device' }, - hybrid: { text: 'Routes by workload', strength: true }, - cloud: { text: 'Frontier models', strength: true }, - }, - { - dimension: 'Elastic scale', - detail: 'Burst capacity', - local: { text: 'Fixed by hardware' }, - hybrid: { text: 'Cloud overflow', strength: true }, - cloud: { text: 'Elastic', strength: true }, + id: 'cloud', + name: 'Cloud', + number: '03', + eyebrow: 'REMOTE INFRASTRUCTURE', + headline: 'Frontier capability. Elastic scale.', + description: + 'Use hosted models and managed infrastructure when reach, model strength and variable demand take priority.', + bestFor: 'High-capability workloads, burst demand and centralized operations.', + factors: [ + { label: 'Privacy', value: 'Remote processing' }, + { label: 'Latency', value: 'Network-dependent' }, + { label: 'Control', value: 'Provider-managed' }, + { label: 'Models', value: 'Frontier access', emphasis: true }, + { label: 'Scale', value: 'Elastic capacity', emphasis: true }, + ], }, ]; --- -
-
- - - - - - - - - - - - - - - - - - {criteria.map((row, index) => ( - <> - {index === 3 && ( - - - - )} - - - - - - - - - - - - ))} - - - - - - - - -
Decision factor - - - Local AI - Local-first lens - - - - - Hybrid - - - - - Cloud - -
- Trade-offs - Where Local AI trades capacity for control -
- {row.dimension} - {row.detail} - - Local AI - - {row.local.strength && ( - <>Strength: - )} - {row.local.text} - - - Hybrid - - {row.hybrid.strength && ( - <>Strength: - )} - {row.hybrid.text} - - - Cloud - - {row.cloud.strength && ( - <>Strength: - )} - {row.cloud.text} - -
Best fit - Local AI - Privacy, latency & control - - Hybrid - Variable workloads - - Cloud - Maximum capability -
+
+
+
+

THREE WAYS TO RUN AI

+

The right architecture depends on the workload.

+
+

Swipe to compare

+
+ +
+
    + {modes.map((mode, index) => ( +
  1. +
    +
    +
    + +
    + {mode.name} + {mode.eyebrow} +
    +
    + {mode.number} / 03 +
    + +

    {mode.headline}

    +

    {mode.description}

    + + + +

    THE TRADE-OFFS

    +
    + {mode.factors.map((factor) => ( +
    +
    {factor.label}
    +
    + {factor.emphasis && } + {factor.value} +
    +
    + ))} +
    +
    + +
    + BEST WHEN +

    {mode.bestFor}

    +
    +
  2. + ))} +
+
+ + -
+
-

Analyzing an AI workload?

-

I share architecture analyses and trade-off breakdowns regularly.

+ NEXT · BUILD +

Choosing where AI should run is the first step. Making that runtime reliable is the next.

- - Follow on LinkedIn - + + Explore the runtime layer
+ + diff --git a/src/components/landing/EvidenceStage.astro b/src/components/landing/EvidenceStage.astro index 525d96f9..70c384dd 100644 --- a/src/components/landing/EvidenceStage.astro +++ b/src/components/landing/EvidenceStage.astro @@ -1,7 +1,11 @@ --- -/** Dedicated Measure stage tuned to the approved Performance Lab reference. */ +/** Plane 04: reproducible experiments / MEASURE. */ +import { landingData } from '../../content/locales/en/landing'; import StageRail from './StageRail.astro'; -import EvidenceMatrix from './EvidenceMatrix.astro'; +import ExperimentResultsExplorer from './ExperimentResultsExplorer.astro'; + +const { measureStage } = landingData; +const experiments = measureStage.systems[0]; ---
-
-

Performance Lab

-

Can this workload run locally?

-

- Performance Lab benchmarks the real model on real devices - to turn evidence into a Local, Hybrid or Cloud decision. -

-
+

{measureStage.kicker}

+

{measureStage.title}

+

{measureStage.intro}

-
- +
+
+
+ {experiments.title} + + {experiments.tag} +
+ + + GitHub + +
+ +
@@ -39,12 +50,12 @@ import EvidenceMatrix from './EvidenceMatrix.astro'; overflow: clip; padding: clamp(54px, 5.5vw, 76px) 0 clamp(72px, 7vw, 96px); background: - linear-gradient( - to bottom, - var(--journey-surface-canvas, var(--surface-canvas)) 0%, - var(--journey-surface-canvas, var(--surface-canvas)) calc(100% - var(--journey-transition-size, 120px)), - color-mix(in srgb, var(--accent-teal) 3%, var(--surface-canvas)) 100% - ); + radial-gradient( + ellipse 62% 38% at 74% 30%, + color-mix(in srgb, var(--accent-blue) 4%, transparent), + transparent 74% + ), + var(--journey-surface-tint, var(--surface-subtle)); } .evidence-shell { @@ -56,107 +67,148 @@ import EvidenceMatrix from './EvidenceMatrix.astro'; min-width: 0; display: flex; flex-direction: column; - gap: clamp(24px, 3.5vw, 36px); + gap: clamp(28px, 3.4vw, 40px); padding-inline: clamp(64px, 7vw, 96px); } .evidence-hero { - width: 100%; - display: flex; - flex-direction: column; - } - - .evidence-copy { - min-width: 0; - max-width: 720px; + width: min(100%, 900px); + align-self: flex-start; } .evidence-kicker { margin: 0 0 10px; + color: var(--accent-blue-dark); font-family: var(--font-display); font-size: var(--text-caption); - line-height: 1.2; - font-weight: 820; + font-weight: var(--weight-display); letter-spacing: 0.075em; text-transform: uppercase; - color: var(--accent-blue); - } - - @media (max-width: 640px) { - .stage-wip-ribbon-wrap { - width: 145px; - height: 145px; - } - - .stage-wip-ribbon { - top: 28px; - right: -38px; - width: 190px; - padding: 6px 0; - font-size: 0.62rem; - gap: 5px; - letter-spacing: 0.05em; - } - - .stage-wip-icon { - width: 12px; - height: 12px; - } } .evidence-heading { + max-width: 760px; margin: 0; - font-family: var(--font-display); - font-size: clamp(2.1rem, 2.9vw, 3.1rem); - line-height: 1.05; - letter-spacing: -0.048em; - font-weight: 850; color: var(--ink-display); - text-wrap: pretty; + font-family: var(--font-display); + font-size: var(--text-section-title); + font-weight: var(--weight-display); + letter-spacing: var(--tracking-section-title); + line-height: var(--leading-section-title); } .evidence-intro { - max-width: 680px; - margin: 14px 0 0; - font-size: clamp(1rem, 1.1vw, 1.12rem); - line-height: 1.55; + max-width: 700px; + margin: 12px 0 0; color: var(--ink-muted); - text-wrap: pretty; + font-size: var(--text-section-intro); + line-height: 1.55; } - .evidence-intro strong { - color: var(--ink-display); - font-weight: 740; + .experiments-surface { + width: 100%; + min-width: 0; } - .evidence-visual { - width: 100%; + .experiments-toolbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 20px; + margin-bottom: 12px; + } + + .experiments-toolbar__context { + display: inline-flex; + align-items: center; + gap: 9px; min-width: 0; } - @media (max-width: 760px) { - .evidence-stage { - padding: 50px 0 68px; - } + .experiments-toolbar__eyebrow { + color: var(--ink-display); + font-family: var(--font-display); + font-size: var(--text-caption); + font-weight: var(--weight-display); + } + + .experiments-toolbar__divider { + width: 18px; + height: 1px; + background: var(--border-medium); + } + .experiments-toolbar__meta { + color: var(--ink-subtle); + font-size: var(--text-caption); + line-height: 1.3; + } + + .experiments-toolbar a { + flex: 0 0 auto; + min-height: var(--touch-target-min); + display: inline-flex; + align-items: center; + color: var(--accent-blue-dark); + font-family: var(--font-display); + font-size: var(--text-caption); + font-weight: var(--weight-heading); + text-decoration: none; + } + + .experiments-toolbar a:hover, + .experiments-toolbar a:focus-visible { + color: var(--accent-blue); + } + + @media (max-width: 760px) { .evidence-layout { padding-left: 48px; padding-right: 12px; } - .evidence-heading { - font-size: clamp(1.85rem, 8vw, 2.5rem); + .evidence-hero { + align-self: flex-start; } } + @media (max-width: 760px) { + .evidence-heading { font-size: var(--text-section-title-mobile); } + .evidence-intro { font-size: var(--text-section-intro-mobile); } + } + @media (max-width: 540px) { - .evidence-stage { - padding: 44px 0 60px; + .evidence-stage { padding: 43px 0 64px; } + + /* Reserve only the continuous path gutter, not a second card gutter. */ + .evidence-shell :global(.stage-rail) { + left: 0; + right: auto; } .evidence-layout { - padding-left: 42px; - padding-right: 4px; + gap: 19px; + padding-left: 28px; + padding-right: 0; + } + + .evidence-heading { font-size: var(--text-section-title-mobile); line-height: var(--leading-section-title); } + .evidence-intro { font-size: var(--text-section-intro-mobile); line-height: 1.5; } + + .experiments-toolbar { + align-items: center; + gap: 12px; + margin-bottom: 14px; + } + + .experiments-toolbar__meta, + .experiments-toolbar__divider { + display: none; + } + + .experiments-toolbar a { + min-height: var(--touch-target-min); + padding: 7px 0 7px 10px; } } diff --git a/src/components/landing/ExperimentResultsExplorer.astro b/src/components/landing/ExperimentResultsExplorer.astro new file mode 100644 index 00000000..ca3e5d92 --- /dev/null +++ b/src/components/landing/ExperimentResultsExplorer.astro @@ -0,0 +1,4614 @@ +--- +interface Track { + id?: string; + title: string; + question: string; + status: string; + resultUrl?: string; + sourceUrl?: string; + resultKind?: 'jev-public-summary' | 'mcb-overview' | 'redactguard-public-summary'; + relatedResultUrl?: string; + relatedSourceUrl?: string; +} + +interface Props { + tracks: Track[]; +} + +import '../../styles/redactguardInsights.css'; + +const { tracks } = Astro.props; +const sortedTracks = [...tracks].sort((a, b) => Number(Boolean(b.resultUrl)) - Number(Boolean(a.resultUrl))); +const initialIndex = Math.max(0, sortedTracks.findIndex((track) => Boolean(track.resultUrl))); + +const selectorLabelFor = (track: Track) => { + const labels: Record = { + 'jev-vs-llm-routing': 'JEV vs LLM', + 'model-capability': 'Model Capability', + 'redactguard-local-anonymization': 'RedactGuard', + 'vlm-capability': 'VLM', + 'image-generation': 'Image Gen', + diarization: 'Diarization', + }; + return labels[track.id || ''] || track.title; +}; +--- + +
+
+
+

Benchmarks

+
+ {sortedTracks.filter((track) => Boolean(track.resultUrl)).length} / {sortedTracks.length} published +
+
+ + +
+
+ SWIPE TO EXPLORE +
+ 01 / {String(sortedTracks.length).padStart(2, '0')} + + +
+
+
+
+ {sortedTracks.map((track, index) => ( + + ))} +
+
+ +
+ + + + + + + +
+
+ + + + diff --git a/src/components/landing/LandingHeader.astro b/src/components/landing/LandingHeader.astro index c14a1beb..bad63d1e 100644 --- a/src/components/landing/LandingHeader.astro +++ b/src/components/landing/LandingHeader.astro @@ -173,7 +173,7 @@ const resolveHref = (href: string) => { .brand-name { font-family: var(--font-display); font-size: var(--text-caption); - font-weight: 850; + font-weight: var(--weight-display); letter-spacing: 0.045em; text-transform: uppercase; color: var(--ink-display); @@ -183,7 +183,7 @@ const resolveHref = (href: string) => { .brand-role { color: var(--ink-muted); font-size: var(--text-caption); - font-weight: 620; + font-weight: var(--weight-label); line-height: 1.4; white-space: nowrap; } @@ -211,7 +211,7 @@ const resolveHref = (href: string) => { color: var(--ink); text-decoration: none; font-size: var(--text-caption); - font-weight: 650; + font-weight: var(--weight-label); transition: color var(--duration-fast) ease; } diff --git a/src/components/landing/LandingHero.astro b/src/components/landing/LandingHero.astro index 32175de7..9a156280 100644 --- a/src/components/landing/LandingHero.astro +++ b/src/components/landing/LandingHero.astro @@ -58,6 +58,21 @@ const linkedinUrl = hero.identity.socials?.find((link) => link.platform === 'lin
+ +
@@ -120,7 +135,7 @@ const linkedinUrl = hero.identity.socials?.find((link) => link.platform === 'lin font-size: clamp(1.85rem, 4.35vw, 4.35rem); line-height: 1.1; letter-spacing: -0.055em; - font-weight: 860; + font-weight: var(--weight-display); color: var(--ink-display); white-space: nowrap; } @@ -152,7 +167,7 @@ const linkedinUrl = hero.identity.socials?.find((link) => link.platform === 'lin font-family: var(--font-display); font-size: clamp(1.32rem, 2.15vw, 2rem); line-height: 1.24; - font-weight: 760; + font-weight: var(--weight-heading); letter-spacing: -0.025em; color: var(--ink-display); } @@ -167,7 +182,7 @@ const linkedinUrl = hero.identity.socials?.find((link) => link.platform === 'lin .hero-explanation :global(strong) { color: var(--ink-display); - font-weight: 740; + font-weight: var(--weight-heading); } .hero-position { @@ -178,7 +193,7 @@ const linkedinUrl = hero.identity.socials?.find((link) => link.platform === 'lin margin: 0; font-family: var(--font-display); font-size: 0.9rem; - font-weight: 700; + font-weight: var(--weight-heading); color: var(--ink); } @@ -197,6 +212,64 @@ const linkedinUrl = hero.identity.socials?.find((link) => link.platform === 'lin gap: 10px; } + .hero-system-summary { + max-width: 1040px; + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + margin-top: clamp(28px, 4vw, 44px); + border-top: 1px solid var(--border-medium); + border-bottom: 1px solid var(--border-medium); + } + + .hero-system-summary a { + min-width: 0; + display: flex; + align-items: flex-start; + gap: 12px; + padding: 18px 18px 18px 0; + color: inherit; + text-decoration: none; + } + + .hero-system-summary a + a { + padding-left: 18px; + border-left: 1px solid var(--border-subtle); + } + + .summary-number { + flex: 0 0 auto; + color: var(--accent-blue); + font-family: var(--font-mono); + font-size: var(--text-caption); + font-weight: var(--weight-display); + line-height: 1.5; + } + + .hero-system-summary a > span:last-child { + min-width: 0; + display: flex; + flex-direction: column; + gap: 4px; + } + + .hero-system-summary strong { + color: var(--ink-display); + font-family: var(--font-display); + font-size: 0.9rem; + line-height: 1.3; + } + + .hero-system-summary small { + color: var(--ink-muted); + font-size: var(--text-caption); + line-height: 1.4; + } + + .hero-system-summary a:hover strong, + .hero-system-summary a:focus-visible strong { + color: var(--accent-blue-dark); + } + .hero-trust { display: flex; align-items: center; @@ -212,7 +285,7 @@ const linkedinUrl = hero.identity.socials?.find((link) => link.platform === 'lin .trust-role { color: var(--ink); - font-weight: 690; + font-weight: var(--weight-heading); } .hero-trust a { @@ -220,7 +293,7 @@ const linkedinUrl = hero.identity.socials?.find((link) => link.platform === 'lin align-items: center; gap: 5px; color: var(--ink-muted); - font-weight: 650; + font-weight: var(--weight-label); text-decoration: none; transition: color var(--duration-fast) ease; } @@ -277,6 +350,20 @@ const linkedinUrl = hero.identity.socials?.find((link) => link.platform === 'lin justify-content: center; } + .hero-system-summary { + grid-template-columns: 1fr; + } + + .hero-system-summary a, + .hero-system-summary a + a { + padding: 14px 0; + border-left: 0; + } + + .hero-system-summary a + a { + border-top: 1px solid var(--border-subtle); + } + .hero-trust { align-items: flex-start; flex-direction: column; diff --git a/src/components/landing/LoopClosure.astro b/src/components/landing/LoopClosure.astro index 67035c33..92c23723 100644 --- a/src/components/landing/LoopClosure.astro +++ b/src/components/landing/LoopClosure.astro @@ -84,7 +84,7 @@ const { loopClosure } = landingData; font-size: clamp(1.85rem, 3.2vw, 2.65rem); line-height: 1.12; letter-spacing: -0.035em; - font-weight: 800; + font-weight: var(--weight-display); color: var(--ink-display); } @@ -113,14 +113,14 @@ const { loopClosure } = landingData; .track-step { font-family: var(--font-display); font-size: var(--text-badge); - font-weight: 800; + font-weight: var(--weight-display); color: var(--ink-muted); letter-spacing: 0.06em; } .track-step--final { color: var(--accent-blue-dark); - font-weight: 850; + font-weight: var(--weight-display); background: var(--surface-blue-subtle); padding: 3px 10px; border-radius: var(--radius-full); @@ -128,7 +128,7 @@ const { loopClosure } = landingData; .track-arrow { color: var(--accent-blue); - font-weight: 800; + font-weight: var(--weight-display); font-size: var(--text-badge); } @@ -137,7 +137,7 @@ const { loopClosure } = landingData; font-family: var(--font-display); font-size: var(--text-caption); color: var(--accent-blue-dark); - font-weight: 750; + font-weight: var(--weight-heading); letter-spacing: 0.02em; } @@ -187,7 +187,7 @@ const { loopClosure } = landingData; .outcome-label { font-family: var(--font-display); font-size: var(--text-body); - font-weight: 850; + font-weight: var(--weight-display); letter-spacing: 0.05em; color: var(--ink-display); } diff --git a/src/components/landing/MethodThread.astro b/src/components/landing/MethodThread.astro index ad0de326..35a2ec4e 100644 --- a/src/components/landing/MethodThread.astro +++ b/src/components/landing/MethodThread.astro @@ -146,7 +146,7 @@ const pathD = direction === 'ltr' .pill-label { font-family: var(--font-display); font-size: var(--text-caption); - font-weight: 800; + font-weight: var(--weight-display); letter-spacing: 0.055em; color: var(--accent-blue-dark); } diff --git a/src/components/landing/PlaneTemplate.astro b/src/components/landing/PlaneTemplate.astro index 74fc66ac..091a1ba7 100644 --- a/src/components/landing/PlaneTemplate.astro +++ b/src/components/landing/PlaneTemplate.astro @@ -115,10 +115,10 @@ const titleId = `${id}-title`; max-width: 100%; margin: 0 0 var(--space-3); font-family: var(--font-display); - font-size: clamp(1.75rem, 3.1vw, 2.95rem); - line-height: 1.08; - letter-spacing: -0.042em; - font-weight: 820; + font-size: var(--text-section-title); + line-height: var(--leading-section-title); + letter-spacing: var(--tracking-section-title); + font-weight: var(--weight-display); color: var(--ink-display); text-wrap: pretty; } @@ -126,7 +126,7 @@ const titleId = `${id}-title`; .plane-intro .stage-intro { max-width: 680px; margin: 0; - font-size: clamp(1rem, 1.25vw, 1.12rem); + font-size: var(--text-section-intro); line-height: 1.6; color: var(--ink-muted); text-wrap: pretty; @@ -157,7 +157,6 @@ const titleId = `${id}-title`; .plane-section.stage-left .plane-intro, .plane-section.stage-right .plane-intro { align-self: flex-start; } - .plane-intro .stage-heading { font-size: clamp(1.65rem, 5vw, 2.35rem); } } @media (max-width: 760px) { @@ -166,8 +165,8 @@ const titleId = `${id}-title`; padding-right: 12px; } - .plane-intro .stage-heading { font-size: clamp(1.45rem, 6vw, 1.95rem); } - .plane-intro .stage-intro { font-size: 0.98rem; } + .plane-intro .stage-heading { font-size: var(--text-section-title-mobile); } + .plane-intro .stage-intro { font-size: var(--text-section-intro-mobile); } } @media (max-width: 540px) { diff --git a/src/components/landing/ProjectProofCard.astro b/src/components/landing/ProjectProofCard.astro index ab7baad1..9784372d 100644 --- a/src/components/landing/ProjectProofCard.astro +++ b/src/components/landing/ProjectProofCard.astro @@ -96,6 +96,13 @@ const isMobile = Boolean(
)} + {whatItTests && ( +
+ HOW IT CONNECTS +

{whatItTests}

+
+ )} + {features && features.length > 0 && (
@@ -467,7 +474,7 @@ const isMobile = Boolean( .proof-vertical-card__tag { color: var(--product-accent); font-family: var(--font-display); - font-size: 0.64rem; + font-size: var(--text-caption); font-weight: 850; letter-spacing: 0.08em; line-height: 1.2; @@ -545,13 +552,38 @@ const isMobile = Boolean( .proof-vertical-card__validation-label { color: var(--product-accent); font-family: var(--font-display); - font-size: 0.62rem; + font-size: var(--text-caption); font-weight: 850; letter-spacing: 0.08em; line-height: 1.2; text-transform: uppercase; } + .proof-vertical-card__relationship { + margin: 0 20px 14px; + padding: 12px 14px; + border-left: 2px solid color-mix(in srgb, var(--product-accent) 58%, var(--border-medium)); + background: color-mix(in srgb, var(--product-accent) 4%, var(--surface-subtle)); + border-radius: 0 var(--radius-sm) var(--radius-sm) 0; + } + + .proof-vertical-card__relationship span { + display: block; + margin-bottom: 5px; + color: var(--product-accent); + font-family: var(--font-display); + font-size: var(--text-caption); + font-weight: 820; + letter-spacing: 0.07em; + } + + .proof-vertical-card__relationship p { + margin: 0; + color: var(--ink-muted); + font-size: var(--text-caption); + line-height: 1.45; + } + .proof-vertical-card__validation-chips { display: flex; flex-wrap: wrap; @@ -562,7 +594,7 @@ const isMobile = Boolean( display: inline-flex; align-items: center; padding: 3px 8px; - font-size: 0.72rem; + font-size: var(--text-caption); font-weight: 650; line-height: 1.2; color: var(--ink); @@ -591,7 +623,7 @@ const isMobile = Boolean( .proof-vertical-card__cta-text { font-family: var(--font-display); - font-size: 0.84rem; + font-size: var(--text-caption); font-weight: 780; color: var(--product-accent); line-height: 1.2; @@ -633,6 +665,10 @@ const isMobile = Boolean( height: 175px; } + .proof-vertical-card__relationship { + margin: 0 16px 12px; + } + .proof-vertical-card__validation { padding: 0 16px 14px; } diff --git a/src/components/landing/StageRail.astro b/src/components/landing/StageRail.astro index e188e3f4..941cc1da 100644 --- a/src/components/landing/StageRail.astro +++ b/src/components/landing/StageRail.astro @@ -77,7 +77,7 @@ const label = String(number).padStart(2, '0'); 0 12px 30px rgba(8, 19, 51, 0.16); font-family: var(--font-display); font-size: var(--text-caption); - font-weight: 800; + font-weight: var(--weight-display); letter-spacing: 0.04em; opacity: var(--node-opacity, 0); transition: diff --git a/src/components/landing/TestProductGallery.astro b/src/components/landing/TestProductGallery.astro new file mode 100644 index 00000000..edf8ee23 --- /dev/null +++ b/src/components/landing/TestProductGallery.astro @@ -0,0 +1,608 @@ +--- +/** + * TEST / Product Proof Gallery. + * Real product logos and screenshots come from the canonical landing data. + * Mobile: native snap-scrolling rail, with optional arrow enhancement. + * Desktop: three simultaneous project cards. + */ +import { landingData } from '../../content/locales/en/landing'; + +const { testStage } = landingData; +const baseUrl = import.meta.env.BASE_URL.replace(/\/$/, ''); +const resolveHref = (href: string) => baseUrl + '/' + href.replace(/^\//, ''); + +const applicationId = (name: string) => + name === 'RedactGuard' ? 'redact-guard' : + name === 'Aura Finance' ? 'aura-finance' : 'closedroom'; +--- + + + + + + diff --git a/src/components/landing/TestStage.astro b/src/components/landing/TestStage.astro index 6aeed3de..324db4c4 100644 --- a/src/components/landing/TestStage.astro +++ b/src/components/landing/TestStage.astro @@ -1,13 +1,10 @@ --- -/** Plane 03: use real products as proving grounds. */ +/** Plane 03: show the real product interfaces that test local-AI infrastructure. */ import { landingData } from '../../content/locales/en/landing'; import PlaneTemplate from './PlaneTemplate.astro'; -import PlaneCardGrid from './PlaneCardGrid.astro'; -import ProjectProofCard from './ProjectProofCard.astro'; +import TestProductGallery from './TestProductGallery.astro'; const { testStage } = landingData; -const baseUrl = import.meta.env.BASE_URL.replace(/\/$/, ''); -const resolveHref = (href: string) => `${baseUrl}/${href.replace(/^\//, '')}`; --- `${baseUrl}/${href.replace(/^\//, '')}`; side="left" surface="subtle" kicker="" - title="Does it work in a real product?" - intro="Infrastructure only matters when a real application can depend on it." + title="Does it survive a real product?" + intro="Infrastructure only matters when real applications can depend on it. These products put privacy, usability and integration assumptions to the test." handoff={testStage.handoff} nextLabel="MEASURE" - visualLabel="Applications used as empirical proving grounds" + visualLabel="Three real AI products with application screenshots, workloads and infrastructure links" > - - {testStage.applications.map((app) => ( - - ))} - + diff --git a/src/components/layout/SiteFooter.astro b/src/components/layout/SiteFooter.astro index f87a1dbe..35510704 100644 --- a/src/components/layout/SiteFooter.astro +++ b/src/components/layout/SiteFooter.astro @@ -108,7 +108,7 @@ const baseUrl = import.meta.env.BASE_URL.replace(/\/$/, ''); .brand-name { font-family: var(--font-display); font-size: var(--text-caption); - font-weight: 700; + font-weight: var(--weight-heading); letter-spacing: -0.01em; color: var(--ink-display); transition: color var(--duration-fast) ease; @@ -140,7 +140,7 @@ const baseUrl = import.meta.env.BASE_URL.replace(/\/$/, ''); text-decoration: none; color: var(--ink-muted); font-size: var(--text-caption); - font-weight: 500; + font-weight: var(--weight-medium); transition: color var(--duration-fast) ease; } @@ -178,8 +178,8 @@ const baseUrl = import.meta.env.BASE_URL.replace(/\/$/, ''); display: inline-flex; align-items: center; justify-content: center; - width: 30px; - height: 30px; + width: var(--touch-target-min); + height: var(--touch-target-min); border-radius: var(--radius-full); color: var(--ink-muted); transition: diff --git a/src/components/layout/SiteHeader.astro b/src/components/layout/SiteHeader.astro index ce0c87cd..2d62b310 100644 --- a/src/components/layout/SiteHeader.astro +++ b/src/components/layout/SiteHeader.astro @@ -100,10 +100,17 @@ function getHref(href: string) { box-shadow: var(--shadow-sm); } + /* + * The homepage/content shell is 1140px; five question-length nav links plus + * the full identity cannot fit in that width. Give the shared header its own + * wider shell, then reserve non-shrinking tracks for nav/actions. The identity + * is the only track allowed to shrink, without painting over its neighbour. + */ .header-inner { + --shell-max-width: 1440px; height: 100%; display: grid; - grid-template-columns: auto minmax(0, 1fr) auto; + grid-template-columns: minmax(0, 1fr) max-content max-content; align-items: center; gap: clamp(12px, 1.7vw, 26px); } @@ -112,6 +119,8 @@ function getHref(href: string) { display: inline-flex; align-items: center; gap: 9px; + min-width: 0; + max-width: 100%; min-height: var(--touch-target-min); text-decoration: none; color: inherit; @@ -139,27 +148,33 @@ function getHref(href: string) { flex-direction: column; line-height: 1.22; min-width: 0; + max-width: 100%; + overflow: hidden; } .brand-name { font-family: var(--font-display); font-size: var(--text-caption); - font-weight: 760; + font-weight: var(--weight-heading); letter-spacing: -0.012em; color: var(--ink-display); white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .brand-role { font-family: var(--font-body); - font-size: 0.7rem; - font-weight: 500; + font-size: var(--text-caption); + font-weight: var(--weight-medium); color: var(--ink-muted); white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .site-nav { - min-width: 0; + min-width: max-content; display: flex; align-items: center; justify-content: flex-end; @@ -191,8 +206,8 @@ function getHref(href: string) { } .nav-link-text { - font-size: clamp(0.68rem, 0.72vw, 0.76rem); - font-weight: 580; + font-size: var(--text-caption); + font-weight: var(--weight-label); letter-spacing: -0.015em; line-height: 1.15; white-space: nowrap; @@ -200,6 +215,8 @@ function getHref(href: string) { transition: color var(--duration-fast) ease; } + .nav-link-icon { display: none; } + .nav-link::after { content: ''; position: absolute; @@ -244,11 +261,11 @@ function getHref(href: string) { } .header-cta-btn { - min-height: 36px; - padding: 6px 13px; + min-height: var(--touch-target-min); + padding: 8px 13px; border-radius: var(--radius-full); font-size: var(--text-caption); - font-weight: 670; + font-weight: var(--weight-heading); white-space: nowrap; cursor: pointer; display: inline-flex; @@ -259,8 +276,8 @@ function getHref(href: string) { .theme-toggle { display: grid; place-items: center; - width: 36px; - height: 36px; + width: var(--touch-target-min); + height: var(--touch-target-min); padding: 0; border: 1px solid var(--border-subtle); border-radius: var(--radius-full); @@ -293,9 +310,9 @@ function getHref(href: string) { .menu-toggle { display: none; - width: 38px; - height: 38px; - padding: 9px; + width: var(--touch-target-min); + height: var(--touch-target-min); + padding: 12px; border: 1px solid var(--border-subtle); border-radius: var(--radius-full); background: var(--surface-card); @@ -311,16 +328,14 @@ function getHref(href: string) { background: var(--ink); } - @media (max-width: 1200px) { - .brand-role { display: none; } - .site-nav { gap: 6px; } - .nav-link { padding: 4px 6px; } - .nav-link-text { font-size: 0.67rem; } - } - - @media (max-width: 980px) { + /* + * At and below 1450px, move the full navigation into the accessible menu. + * Breakpoints decide presentation only: constrained grid tracks and text + * overflow protection prevent collisions at every intermediate width/zoom. + */ + @media (max-width: 1450px) { .header-inner { - grid-template-columns: auto 1fr auto; + grid-template-columns: minmax(0, 1fr) max-content; } .menu-toggle { display: block; } @@ -334,7 +349,7 @@ function getHref(href: string) { flex-direction: column; align-items: stretch; gap: 0; - padding: 12px max(20px, calc((100vw - var(--shell-max)) / 2)); + padding: 12px max(20px, calc((100vw - var(--shell-max-width)) / 2)); background: color-mix(in srgb, var(--surface-card) 98%, transparent); border-bottom: 1px solid var(--border-medium); box-shadow: var(--shadow-md); @@ -354,12 +369,13 @@ function getHref(href: string) { } .nav-link-icon { + display: flex; color: var(--accent-blue); } .nav-link-text { font-size: 0.92rem; - font-weight: 600; + font-weight: var(--weight-label); text-align: left; } @@ -367,9 +383,14 @@ function getHref(href: string) { .nav-link::after { display: none; } } + @media (max-width: 820px) { + /* The full role lives in the hero; on phones the header stays legible. */ + .brand-role { display: none; } + } + @media (max-width: 620px) { .header-cta-btn { display: none; } .brand-signature { width: 34px; } - .brand-name { font-size: 0.78rem; } + .brand-name { font-size: var(--text-caption); } } diff --git a/src/content/locales/en/landing.ts b/src/content/locales/en/landing.ts index aaa87c56..1f0813c1 100644 --- a/src/content/locales/en/landing.ts +++ b/src/content/locales/en/landing.ts @@ -160,12 +160,12 @@ export const landingData: LandingPageData = { buildStage: { kicker: '02 BUILD', - title: 'Making local inference actually usable.', + title: 'Build the reusable AI layer once.', intro: - 'When Local or Hybrid makes sense, I build the runtime products need across desktop, mobile, and speech.', + 'When Local or Hybrid makes sense, I build reusable infrastructure for reasoning, mobile inference, repeated decisions and private speech.', proofLine: 'Same question across every device: what can realistically run locally?', - ecosystemImage: 'images/ecosystem.png', - ecosystemAlt: 'Connected Local AI architecture across desktop, Android, local server and cloud fallback', + ecosystemImage: 'images/ecosystem-map.png', + ecosystemAlt: 'Ecosystem map showing how reusable primitives (Korgis, Harnex, Decisio, Local ASR) connect to real products (ClosedRoom, RedactGuard, Aura Finance) across Local, Hybrid, and Cloud boundaries.', areas: [ { icon: '▱', @@ -188,48 +188,67 @@ export const landingData: LandingPageData = { id: 'korgis', title: 'Korgis', eyebrow: 'DESKTOP · SERVER', - summary: 'Your AI. Local. Ready to use. Runtime control plane for reliable multi-model local AI applications.', + layer: 'Local AI runtime control plane', + status: 'ACTIVE', + summary: 'Run and manage local AI models behind one stable application-facing API.', href: '/korgis', logoPath: 'images/local-llm-server/logo.png', logoAlt: 'Korgis logo', features: [ - { label: 'OpenAI API' }, { label: 'Multi-model runtime' }, - { label: 'Telemetry' }, + { label: 'OpenAI API' }, + { label: 'Resource control' }, ], - evidence: 'One local runtime that different apps can reuse.', - imagePath: 'images/local-llm-server/overview.png', - imageAlt: 'Korgis overview interface', + evidence: 'Makes local models reusable infrastructure instead of one-off app integrations.', }, { - id: 'android-harness', + id: 'harnex', title: 'Harnex', - eyebrow: 'ON-DEVICE · ANDROID', - summary: 'One shared on-device gateway and runtime across Android applications.', + eyebrow: 'ANDROID · ON-DEVICE', + layer: 'Shared Android local-AI control plane', + status: 'ACTIVE', + summary: 'Run local GGUF models once and expose governed on-device inference to multiple apps.', href: '/harnex', logoPath: 'images/harness/logo.png', logoAlt: 'Harnex logo', features: [ - { label: 'GGUF runtime' }, - { label: 'Thermal testing' }, - { label: 'Telemetry' }, + { label: 'Shared runtime' }, + { label: 'Governed access' }, + { label: 'Runtime evidence' }, + ], + evidence: 'Removes repeated model-store, JNI and lifecycle engineering from every Android app.', + }, + { + id: 'decisio', + title: 'Decisio', + eyebrow: 'DECISIONS · STATEFUL', + layer: 'Stateful local-LLM decision runtime', + status: 'EXPERIMENTAL', + summary: 'Prefill stable context once, send only changing state and return a typed action.', + href: '/decisio', + logoPath: 'images/decisio/decisio-mark-512.png', + logoAlt: 'Decisio logo', + features: [ + { label: 'Context reuse' }, + { label: 'Typed actions' }, + { label: 'Zero-token readout' }, ], - evidence: 'A real phone exposes limits a desktop demo can hide.', - imagePath: 'images/harness/harness-overview.png', - imageAlt: 'Harnex overview', + evidence: 'Cuts repeated prefill work when the same bounded decision is made over changing state.', }, { id: 'local-asr-server', - title: 'Local ASR Server', + title: 'Local ASR', eyebrow: 'SPEECH · AUDIO', - summary: 'Lets a product turn speech into text without sending audio to a cloud service.', + layer: 'Private speech primitive', + status: 'ACTIVE', + summary: 'Turn speech into text locally so audio does not have to leave the controlled workflow.', href: '/local-asr-server', features: [ { label: 'Whisper ASR' }, - { label: 'Real-time stream' }, - { label: 'Zero telemetry' }, + { label: 'Streaming' }, + { label: 'Private audio' }, ], - evidence: 'Speech-to-text that stays inside the local workflow.', + evidence: 'Provides the speech primitive needed by products such as ClosedRoom without cloud transcription.', }, ], closingMessage: @@ -250,6 +269,8 @@ export const landingData: LandingPageData = { applications: [ { name: 'RedactGuard', + valueHeadline: 'Sensitive documents. Local processing.', + infrastructureLabel: 'Korgis · Harnex', tag: 'WEB & DESKTOP · PII REDACTION', question: 'Can sensitive document processing stay local?', description: @@ -265,10 +286,12 @@ export const landingData: LandingPageData = { imagePath: 'images/redact-guard/redactguard-doc-preview.jpg', colorClass: 'blue', linkLabel: 'See the test →', - whatItTests: 'Local inference + configurable PII + human review inside a controlled boundary.', + whatItTests: 'Desktop uses Korgis; Android uses Harnex. The same privacy workflow tests two local execution layers.', }, { name: 'Aura Finance', + valueHeadline: 'Understand transactions on-device.', + infrastructureLabel: 'Harnex · Integration target', tag: 'MOBILE · ON-DEVICE PARSING', question: 'Can personal transactions be categorized on-device?', description: @@ -284,10 +307,12 @@ export const landingData: LandingPageData = { imagePath: 'images/aura-finance/aura-categories-preview.png', colorClass: 'green', linkLabel: 'See the test →', - whatItTests: 'Small local model handling recurring semantic tasks without cloud exposure.', + whatItTests: 'Designed as the mobile proving ground for Harnex-powered transaction extraction and categorisation.', }, { name: 'ClosedRoom', + valueHeadline: 'Meeting intelligence. Private by default.', + infrastructureLabel: 'Korgis + Local ASR', tag: 'MACOS · MEETING INTELLIGENCE', question: 'Can meeting intelligence stay inside the room?', description: @@ -303,7 +328,7 @@ export const landingData: LandingPageData = { imagePath: 'images/closedroom/recording-preview.jpg', colorClass: 'purple', linkLabel: 'See the test →', - whatItTests: 'Multi-primitive orchestration on macOS with external models strictly opt-in.', + whatItTests: 'Local ASR + Korgis orchestrated inside one private macOS meeting workflow.', }, ], nextCard: { @@ -321,71 +346,85 @@ export const landingData: LandingPageData = { measureStage: { kicker: '04 MEASURE', - title: 'Is it actually good enough?', + title: 'Test the assumptions, not just the demo.', intro: - "Running locally isn't the goal, performing well is. I measure real numbers to shape the next architecture decision.", + 'Reproducible benchmarks turn architecture choices into evidence: what works, how well, and at what cost.', systems: [ { - id: 'performance-lab', - title: 'Performance Lab', - tag: 'OPTIMIZATION & BENCHMARKS', - question: 'Which configuration is good enough for this workload on this device?', - metrics: [ - { label: 'TTFT', value: '820', unit: 'ms', note: 'Time to first token' }, - { label: 'Decode', value: '18.6', unit: 'tok/s', note: 'Generation speed' }, - { label: 'Prefill', value: '72.4', unit: 'tok/s', note: 'Prompt processing' }, - { label: 'Peak RAM', value: '3.4', unit: 'GB', note: 'Memory footprint' }, - ], - benchmarkProfiles: [ + id: 'experiments', + title: 'Experiments', + tag: 'REPRODUCIBLE EVALUATION', + question: 'Select a benchmark. Inspect the latest evidence.', + metrics: [], + tracks: [ { - id: 'samsung-a56', device: 'Samsung Galaxy A56', platform: 'Android', deviceKind: 'mobile', - model: 'Qwen 3.5 2B', quantization: 'Q4_K_M', context: '4K context', - metrics: [ - { label: 'TTFT', value: '820', unit: 'ms', note: 'Time to first token' }, - { label: 'Decode', value: '18.6', unit: 'tok/s', note: 'Generation speed' }, - { label: 'Prefill', value: '72.4', unit: 'tok/s', note: 'Prompt processing' }, - { label: 'Peak RAM', value: '3.4', unit: 'GB', note: 'Memory footprint' }, - ], + id: 'model-capability', + title: 'Model Capability Benchmark', + question: 'How do model capability and latency trade off across local and API runtimes?', + status: 'PUBLISHED', + resultUrl: + 'https://raw.githubusercontent.com/daniele21/experiments/main/experiments/model-capability-benchmark/results/published/models-overview.json', + sourceUrl: + 'https://github.com/daniele21/experiments/blob/main/experiments/model-capability-benchmark/results/published/models-overview.json', + resultKind: 'mcb-overview', }, { - id: 'mac-pro-m3', device: 'Mac Pro M3', platform: 'macOS', deviceKind: 'desktop', - model: 'Qwen 3.5 2B', quantization: 'Q4_K_M', context: '4K context', - metrics: [ - { label: 'TTFT', value: '240', unit: 'ms', note: 'Time to first token' }, - { label: 'Decode', value: '54.8', unit: 'tok/s', note: 'Generation speed' }, - { label: 'Prefill', value: '214', unit: 'tok/s', note: 'Prompt processing' }, - { label: 'Peak RAM', value: '3.1', unit: 'GB', note: 'Memory footprint' }, - ], + id: 'jev-vs-llm-routing', + title: 'JEV vs LLM · Decision Benchmarks', + question: 'What does the published evidence say about decision models versus general LLMs?', + status: 'PARTIAL PUBLIC EVIDENCE', + resultUrl: + 'https://raw.githubusercontent.com/daniele21/experiments/dev/experiments/jev-vs-llm/results/published/portfolio-summary.json', + sourceUrl: + 'https://github.com/daniele21/experiments/blob/feature/public-jev-benchmark-evidence/experiments/jev-vs-llm/results/published/portfolio-summary.json', + resultKind: 'jev-public-summary', + relatedResultUrl: + 'https://raw.githubusercontent.com/daniele21/experiments/dev/experiments/model-capability-benchmark/results/published/models-overview.json', + relatedSourceUrl: + 'https://github.com/daniele21/experiments/blob/dev/experiments/model-capability-benchmark/results/published/models-overview.json', + }, + { + id: 'redactguard-local-anonymization', + title: 'RedactGuard Local Anonymization', + question: 'How do we test private-data detection before deployment?', + status: 'MEASURED RESULTS PUBLISHED', + resultUrl: + 'https://raw.githubusercontent.com/daniele21/experiments/dev/experiments/redactguard-local-anonymization/results/published/portfolio-summary.json', + sourceUrl: + 'https://github.com/daniele21/experiments/blob/dev/experiments/redactguard-local-anonymization/results/published/portfolio-summary.json', + resultKind: 'redactguard-public-summary', + }, + { + id: 'vlm-capability', + title: 'VLM Capability Benchmark', + question: 'Which visual tasks can realistically move local?', + status: 'NO PUBLISHED SNAPSHOT', + }, + { + id: 'image-generation', + title: 'Image Generation Benchmark', + question: 'How should image models be compared beyond subjective demos?', + status: 'NO PUBLISHED SNAPSHOT', + }, + { + id: 'diarization', + title: 'Diarization Benchmark', + question: 'Which open diarization pipeline best balances speaker accuracy and runtime cost?', + status: 'NO PUBLISHED SNAPSHOT', }, ], - statusNote: 'Illustrative benchmark values for the current UI. Replace after the validated device runs.', - statusType: 'planned', - href: '/performance-lab', - ctaLabel: 'Open benchmark lab', - }, - { - id: 'traffic-monitoring', - title: 'Traffic Monitoring', - tag: 'PROCESS-LEVEL OBSERVABILITY', - question: 'Did local AI processes stay on-device without reaching the internet?', - metrics: [ - { label: 'Processes observed', value: '3', note: 'LLM · ASR · app' }, - { label: 'Local traffic', value: '100%', note: 'Loopback + LAN' }, - { label: 'Internet egress', value: '0 B', note: 'No outbound payload' }, - { label: 'External endpoints', value: '0', note: 'None contacted' }, - ], - statusNote: 'Process-level capture confirms that AI traffic remained on local interfaces during the observed run.', + statusNote: 'One repository, multiple reproducible research tracks.', statusType: 'verified', - href: '/traffic-monitoring', - ctaLabel: 'See network evidence', + href: 'https://github.com/daniele21/experiments', + ctaLabel: 'Open Experiments', }, ], closingMessage: - 'Working ≠ good enough. Good enough ≠ production ready. Numbers decide, not opinions.', + 'Evidence should change the next architecture decision. Otherwise it is just telemetry.', handoff: { output: 'Evidence changes the next decision', leadsTo: 'DECIDE AGAIN', - detailCta: { label: 'Open full benchmark lab', href: '/performance-lab' }, + detailCta: { label: 'Open Experiments', href: 'https://github.com/daniele21/experiments' }, }, }, @@ -430,14 +469,14 @@ export const landingData: LandingPageData = { badge: 'Lab Prototype', }, { - id: 'traffic-monitoring', - title: 'Traffic Monitoring Platform', - role: 'Observability & network boundary validation', + id: 'decisio', + title: 'Decisio', + role: 'Stateful decision runtime for repeated local-LLM decisions', description: - 'Non-intrusive network traffic analysis proving whether applications maintain declared local-first boundaries without inspecting private payloads.', - technologies: ['macOS Network Extension', 'Android VpnService', 'eBPF'], - href: '/traffic-monitoring', - badge: 'Evidence System', + 'Reuses stable model context, sends only changing state and returns typed actions for bounded decision loops.', + technologies: ['Python', 'llama.cpp', 'GGUF', 'State reuse'], + href: 'https://github.com/daniele21/decisio', + badge: 'Experimental System', }, ], }, diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro index aabab1ba..d44f18f3 100644 --- a/src/layouts/BaseLayout.astro +++ b/src/layouts/BaseLayout.astro @@ -127,7 +127,7 @@ const allSchemas = jsonLd - +