diff --git a/apps/surfaces-web/README.md b/apps/surfaces-web/README.md index e215bc4..0dc46d9 100644 --- a/apps/surfaces-web/README.md +++ b/apps/surfaces-web/README.md @@ -1,5 +1,11 @@ This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). +## Public website copy + +The [website family copy contract](../../docs/specs/website-family-copy.md) governs public text and metadata alongside the [layout contract](../../docs/specs/website-family-layout.md). The homepage is a testing-stage decision and guidance workflow; a vision or example does not establish a measured benefit or general availability. + +Edit `app/homepage-content.html` for the hero, workload, and workflow copy. `app/page.tsx` imports those sections and owns the footer and website schema; `app/layout.tsx` owns page and social metadata. Keep the same claim scope in each file. The existing `tests/home-page.test.tsx` checks the copy boundary, action destinations, and unchanged image/layout assets at CI/CD time. Review violations as copy corrections, without changing product behavior or granting release authority. + ## Getting Started First, run the development server: diff --git a/apps/surfaces-web/app/adaptive/[...slug]/page.tsx b/apps/surfaces-web/app/adaptive/[...slug]/page.tsx index a3b743e..095fbe6 100644 --- a/apps/surfaces-web/app/adaptive/[...slug]/page.tsx +++ b/apps/surfaces-web/app/adaptive/[...slug]/page.tsx @@ -1,5 +1,10 @@ +import type { Metadata } from "next"; +import { publicPageTitleMetadata } from "@/lib/brand-metadata"; + export const runtime = "edge"; +export const metadata: Metadata = publicPageTitleMetadata("Adaptive Contract Test | Surfaces"); + export default async function AdaptiveTestPage({ params }: { params: Promise<{ slug?: string[] }> }) { const { slug } = await params; const path = slug?.join("/") || "(root)"; diff --git a/apps/surfaces-web/app/demos/ci/page.tsx b/apps/surfaces-web/app/demos/ci/page.tsx index 039f424..8137204 100644 --- a/apps/surfaces-web/app/demos/ci/page.tsx +++ b/apps/surfaces-web/app/demos/ci/page.tsx @@ -1,5 +1,10 @@ +import type { Metadata } from "next"; +import { publicPageTitleMetadata } from "@/lib/brand-metadata"; + export const runtime = "edge"; +export const metadata: Metadata = publicPageTitleMetadata("CI/CD Validation Demo | Surfaces"); + export default function CiDemoPage() { return (
diff --git a/apps/surfaces-web/app/demos/generation/page.tsx b/apps/surfaces-web/app/demos/generation/page.tsx index 05db046..f6abfa1 100644 --- a/apps/surfaces-web/app/demos/generation/page.tsx +++ b/apps/surfaces-web/app/demos/generation/page.tsx @@ -1,5 +1,10 @@ +import type { Metadata } from "next"; +import { publicPageTitleMetadata } from "@/lib/brand-metadata"; + export const runtime = "edge"; +export const metadata: Metadata = publicPageTitleMetadata("Generation-Time Enforcement Demo | Surfaces"); + export default function GenerationDemoPage() { return (
diff --git a/apps/surfaces-web/app/design-system/[section]/page.tsx b/apps/surfaces-web/app/design-system/[section]/page.tsx index 53b4a2d..458106c 100644 --- a/apps/surfaces-web/app/design-system/[section]/page.tsx +++ b/apps/surfaces-web/app/design-system/[section]/page.tsx @@ -29,7 +29,9 @@ export async function generateMetadata({ params }: SectionPageProps): Promise - Make Every Release Decision Reusable | Surfaces + UI Review Decisions and Guidance | Surfaces @@ -34,6 +34,7 @@ + @@ -91,18 +92,18 @@ >
-

For teams turning UI review into reusable guidance

-

Make every release decision reusable.

+

For teams reviewing agent-produced UI changes

+

Turn UI review decisions into guidance.

- Resolve clear, repeated violations sooner, so senior judgment stays focused on - what’s genuinely new or contested. + We’re testing how prior decisions can guide later UI reviews, with an owner + confirming or correcting proposed guidance.

@@ -136,8 +137,8 @@

Make every release decision reusable.

data-contract-section="landing.workload" >
- -

The same violation should not require the same review twice.

+ +

Keep prior decisions available for the next review.

    The same violation should not require the same review tw data-contract-section="landing.guidance" >
    - -

    Each decision should make the next clear repeat easier to resolve.

    + +

    Review guidance before trying it on a later case.

      @@ -209,7 +210,7 @@

      Decide

Propose guidance

-

Every decision · including no change needed

+

A proposed change or no change needed

  • 04 @@ -231,17 +232,14 @@

    Confirm changes

    -

    Reuse

    -

    Accepted guidance checks the next candidate

    +

    Test reuse

    +

    Check accepted guidance against a later candidate

  • -
    - Clear repeats use guidance - New cases reach the owner +
    + Try guidance on matching cases + Owner review for new or contested cases
    -

    - Surfaces directs what happens next and delegates the work. Agents execute in the tools your team already uses. -

    Surfaces is an early product hypothesis. We’re testing it with recent interface release decisions.

    @@ -253,7 +251,7 @@

    Reuse

    -

    Release decisions that become reusable guidance.

    +

    UI review decisions and proposed guidance.

    © 2026 Surfaces

    diff --git a/apps/surfaces-web/app/layout.tsx b/apps/surfaces-web/app/layout.tsx index 9cad626..27cc0ca 100644 --- a/apps/surfaces-web/app/layout.tsx +++ b/apps/surfaces-web/app/layout.tsx @@ -1,6 +1,6 @@ import type { Metadata, Viewport } from "next"; import { Inter } from "next/font/google"; -import { brandAssetBase, brandIcons, brandManifest, homeSocialImage } from "@/lib/brand-metadata"; +import { brandAssetBase, brandIcons, brandManifest, publicPageTitleMetadata } from "@/lib/brand-metadata"; import "./globals.css"; const inter = Inter({ @@ -14,28 +14,10 @@ export const metadata: Metadata = { appleWebApp: { capable: true, title: "Surfaces", statusBarStyle: "black-translucent" }, icons: brandIcons, manifest: brandManifest, - title: "Make Every Release Decision Reusable | Surfaces", - description: - "Surfaces is exploring how decisions about agent-produced UI changes can become reusable guidance, so clear repeat violations can be resolved before senior review.", + ...publicPageTitleMetadata("UI Review Decisions and Guidance | Surfaces"), alternates: { canonical: "/", }, - openGraph: { - type: "website", - siteName: "Surfaces", - title: "Make every release decision reusable.", - description: - "Turn decisions about agent-produced UI changes into guidance that can resolve clear repeat violations before senior review.", - url: "/", - images: [homeSocialImage], - }, - twitter: { - card: "summary_large_image", - title: "Make every release decision reusable.", - description: - "Turn decisions about agent-produced UI changes into guidance that can resolve clear repeat violations before senior review.", - images: [homeSocialImage], - }, }; export const viewport: Viewport = { diff --git a/apps/surfaces-web/app/page.tsx b/apps/surfaces-web/app/page.tsx index af357f1..e0b968b 100644 --- a/apps/surfaces-web/app/page.tsx +++ b/apps/surfaces-web/app/page.tsx @@ -44,7 +44,7 @@ const websiteSchema = { name: "Surfaces", url: "https://surfaces.systems/", description: - "Surfaces is exploring how decisions about agent-produced UI changes can become reusable guidance, so clear repeat violations can be resolved before senior review.", + "Surfaces is testing how prior decisions can guide later UI reviews, with an owner confirming or correcting proposed guidance.", }; export default function Home() { @@ -91,7 +91,7 @@ export default function Home() { > -

    Release decisions that become reusable guidance.

    +

    UI review decisions and proposed guidance.

    © {new Date().getFullYear()} Surfaces

    diff --git a/apps/surfaces-web/lib/brand-metadata.ts b/apps/surfaces-web/lib/brand-metadata.ts index 199350d..a7105c8 100644 --- a/apps/surfaces-web/lib/brand-metadata.ts +++ b/apps/surfaces-web/lib/brand-metadata.ts @@ -20,3 +20,28 @@ export const homeSocialImage = { export const designSystemSocialImage = { ...homeSocialImage, }; + +// Keep inherited sharing fields when a public page overrides its title. +export function publicPageTitleMetadata(title: string): Metadata { + const description = + "Surfaces is testing how prior decisions can guide later UI reviews, with an owner confirming or correcting proposed guidance."; + + return { + title, + description, + openGraph: { + type: "website", + siteName: "Surfaces", + title, + description, + url: "/", + images: [homeSocialImage], + }, + twitter: { + card: "summary_large_image", + title, + description, + images: [homeSocialImage], + }, + }; +} diff --git a/apps/surfaces-web/tests/brand-entrypoints.test.tsx b/apps/surfaces-web/tests/brand-entrypoints.test.tsx index dc2cbda..2087da6 100644 --- a/apps/surfaces-web/tests/brand-entrypoints.test.tsx +++ b/apps/surfaces-web/tests/brand-entrypoints.test.tsx @@ -12,6 +12,9 @@ import RootLayout, { metadata as rootMetadata } from "@/app/layout"; import { metadata as referenceMetadata } from "@/app/design-system/page"; import { generateMetadata } from "@/app/design-system/[section]/page"; import { navItems } from "@/app/design-system/referenceContent"; +import { metadata as ciMetadata } from "@/app/demos/ci/page"; +import { metadata as generationMetadata } from "@/app/demos/generation/page"; +import { metadata as adaptiveMetadata } from "@/app/adaptive/[...slug]/page"; vi.mock("next/font/google", () => ({ Inter: () => ({ variable: "font-inter" }) })); @@ -234,6 +237,51 @@ describe("browser, install, and sharing brand entrypoints", () => { expect(layout).toContain('color="#0B0D0E"'); }); + it("uses the reviewed title on every public route and preserves inherited sharing fields", async () => { + const sectionTitles = { + identity: "Identity and Design System | Surfaces", + foundations: "Design System Foundations | Surfaces", + tokens: "Design System Tokens | Surfaces", + components: "Design System Components | Surfaces", + patterns: "Design System Patterns | Surfaces", + resources: "Design System Resources | Surfaces", + governance: "Design System Governance | Surfaces", + changelog: "Design System Changelog | Surfaces", + }; + const reviewedPages = [ + { metadata: rootMetadata, title: "UI Review Decisions and Guidance | Surfaces" }, + { metadata: referenceMetadata, title: "Design System | Surfaces" }, + { metadata: ciMetadata, title: "CI/CD Validation Demo | Surfaces" }, + { metadata: generationMetadata, title: "Generation-Time Enforcement Demo | Surfaces" }, + { metadata: adaptiveMetadata, title: "Adaptive Contract Test | Surfaces" }, + ...await Promise.all(Object.entries(sectionTitles).map(async ([section, title]) => ({ + metadata: await generateMetadata({ params: Promise.resolve({ section }) }), title, + }))), + ]; + + expect(reviewedPages).toHaveLength(13); + expect(new Set(reviewedPages.map(({ title }) => title)).size).toBe(13); + for (const { metadata, title } of reviewedPages) { + expect(metadata.title).toBe(title); + expect(metadata.openGraph).toMatchObject({ title }); + expect(metadata.twitter).toMatchObject({ title }); + } + for (const metadata of [ciMetadata, generationMetadata, adaptiveMetadata]) { + // Nested social metadata replaces parent fields in Next: keep the image, + // description, card and existing homepage URL when overriding just a title. + expect(metadata.openGraph).toEqual({ ...rootMetadata.openGraph, title: metadata.title }); + expect(metadata.twitter).toEqual({ ...rootMetadata.twitter, title: metadata.title }); + expect(metadata.description).toBe(rootMetadata.description); + expect(metadata.alternates).toBeUndefined(); + expect(metadata.robots).toBeUndefined(); + } + expect(rootMetadata.alternates).toEqual({ canonical: "/" }); + const standalone = new DOMParser().parseFromString(read("app/homepage-content.html").toString(), "text/html"); + expect(standalone.title).toBe(rootMetadata.title); + expect(standalone.querySelector('meta[property="og:title"]')?.getAttribute("content")).toBe(rootMetadata.title); + expect(standalone.querySelector('meta[name="twitter:title"]')?.getAttribute("content")).toBe(rootMetadata.title); + }); + it("preserves complete outlined logos in social sources and refreshes legacy/download aliases", () => { for (const path of [`public${brandAssetBase}/surfaces-social.svg`, "public/design-system/logo/surfaces-social-card.svg"]) { const svg = read(path).toString(); diff --git a/apps/surfaces-web/tests/home-page.test.tsx b/apps/surfaces-web/tests/home-page.test.tsx index 598c8f7..becc43b 100644 --- a/apps/surfaces-web/tests/home-page.test.tsx +++ b/apps/surfaces-web/tests/home-page.test.tsx @@ -42,7 +42,7 @@ function renderHomeDocument() { } describe("surfaces.systems home page", () => { - it("leads with the customer outcome and a compact decision-to-guidance story", () => { + it("leads with the proposed decision-to-guidance workflow and its testing stage", () => { const { page, html } = renderHomeDocument(); const sectionMarkers = Array.from( page.querySelectorAll("[data-contract-section]"), @@ -50,11 +50,11 @@ describe("surfaces.systems home page", () => { ); expect(page.querySelector("h1")?.textContent?.replace(/\s+/g, " ").trim()).toBe( - "Make every release decision reusable.", + "Turn UI review decisions into guidance.", ); expect( page.querySelector(".hero-copy > .eyebrow")?.textContent?.replace(/\s+/g, " ").trim(), - ).toBe("For teams turning UI review into reusable guidance"); + ).toBe("For teams reviewing agent-produced UI changes"); expect(page.querySelector(".hero-copy > .eyebrow + h1")?.id).toBe("hero-title"); expect(homepageCss).toMatch( /\.surfaces-homepage \.hero \.eyebrow\s*\{[^}]*text-transform:\s*uppercase;/, @@ -67,11 +67,11 @@ describe("surfaces.systems home page", () => { ); const heroLead = page.querySelector(".hero-lead")?.textContent?.replace(/\s+/g, " ").trim(); expect(heroLead).toBe( - "Resolve clear, repeated violations sooner, so senior judgment stays focused on what’s genuinely new or contested.", + "We’re testing how prior decisions can guide later UI reviews, with an owner confirming or correcting proposed guidance.", ); expect( page.querySelector("#workload-title")?.textContent?.replace(/\s+/g, " ").trim(), - ).toBe("The same violation should not require the same review twice."); + ).toBe("Keep prior decisions available for the next review."); expect( Array.from(page.querySelectorAll(".loop-fragment strong"), (heading) => heading.textContent?.trim(), @@ -82,18 +82,15 @@ describe("surfaces.systems home page", () => { "Chasing rework", ]); expect(page.querySelector(".loop-break")).toBeNull(); - expect(html).toContain("Each decision should make the next clear repeat easier to resolve."); + expect(html).toContain("Review guidance before trying it on a later case."); expect( Array.from(page.querySelectorAll(".cycle-card h3"), (heading) => heading.textContent?.trim()), - ).toEqual(["Assemble context", "Decide", "Propose guidance", "Confirm changes", "Reuse"]); - expect(html).toContain("Every decision · including no change needed"); + ).toEqual(["Assemble context", "Decide", "Propose guidance", "Confirm changes", "Test reuse"]); + expect(html).toContain("A proposed change or no change needed"); expect(html).toContain("The owner confirms or corrects proposed changes"); - expect(html).toContain("Accepted guidance checks the next candidate"); - expect(html).toContain("Clear repeats use guidance"); - expect(html).toContain("New cases reach the owner"); - expect(html).toContain( - "Surfaces directs what happens next and delegates the work. Agents execute in the tools your team already uses.", - ); + expect(html).toContain("Check accepted guidance against a later candidate"); + expect(html).toContain("Try guidance on matching cases"); + expect(html).toContain("Owner review for new or contested cases"); expect(html).toContain( "Surfaces is an early product hypothesis. We’re testing it with recent interface release decisions.", ); @@ -163,7 +160,7 @@ describe("surfaces.systems home page", () => { ]); expect( heroActions.map((link) => link.textContent?.replace(/\s+/g, " ").trim()), - ).toEqual(["See how it works", "Share a recent release decision"]); + ).toEqual(["See the workflow", "Email a release decision"]); const emailLinks = Array.from(page.querySelectorAll('a[href^="mailto:"]')); const emailLink = emailLinks[0]; @@ -179,10 +176,10 @@ describe("surfaces.systems home page", () => { expect(emailLinks).toHaveLength(1); expect(emailLink?.textContent?.replace(/\s+/g, " ").trim()).toBe( - "Share a recent release decision", + "Email a release decision", ); expect(emailLink?.getAttribute("aria-label")).toBe( - "Share a recent release decision by email with Mike", + "Email a release decision to Mike", ); expect(emailLink?.matches(".button.button-secondary")).toBe(true); expect(emailLink?.getAttribute("href")).toBe(emailHref); @@ -273,11 +270,11 @@ describe("surfaces.systems home page", () => { const publicSources = `${homepageContentSource}\n${layoutSource}`; expect(publicSources).toContain( - "Make Every Release Decision Reusable | Surfaces", + "UI Review Decisions and Guidance | Surfaces", ); - expect(publicSources).toContain("Make every release decision reusable."); + expect(publicSources).toContain("Turn UI review decisions into guidance."); expect(publicSources).toContain( - "Surfaces is exploring how decisions about agent-produced UI changes can become reusable guidance, so clear repeat violations can be resolved before senior review.", + "Surfaces is testing how prior decisions can guide later UI reviews, with an owner confirming or correcting proposed guidance.", ); expect(homepageContentSource).toContain('href="https://surfaces.systems/"'); expect(layoutSource).toContain('canonical: "/"'); @@ -287,6 +284,10 @@ describe("surfaces.systems home page", () => { "above the agent work loop", "release admission", "Request a workflow review", + "Make every release decision reusable", + "Make Every Release Decision Reusable", + "violations sooner", + "your team already uses", ]) { expect(publicSources).not.toContain(internalCopy); } diff --git a/docs/specs/website-family-copy.md b/docs/specs/website-family-copy.md new file mode 100644 index 0000000..8c4dda1 --- /dev/null +++ b/docs/specs/website-family-copy.md @@ -0,0 +1,51 @@ +# Website family copy contract + +## Reader and purpose + +The reader is deciding what Surfaces, JudgmentKit or kanban.cards can help them do and whether to try the product. Assume no knowledge of repository vocabulary. Describe an available action and its useful consequence without promising that the product will deliver an unmeasured outcome. + +This contract governs public pages, sign-in and access instructions, page titles, descriptions and social preview text alongside [the layout contract](website-family-layout.md). Copy changes do not authorize changes to product behavior, prices, release status or historical evidence. + +## Claims and evidence + +1. **Describe current behavior.** For each material capability claim, record the implementation or fixture, its limits and the public destination that demonstrates or explains it. A vision document establishes intended purpose; it does not establish availability. A test establishes only the behavior and conditions it checks. +2. **Name the actor.** Distinguish what the product does from what a person, an installed skill or a caller's agent must do. Do not imply automatic planning, execution, repair, approval or synchronization when those actions depend on a caller. +3. **Bound the result.** Name the object and scope of checks. Passing selected layout or evidence checks does not establish usability, accessibility compliance, data truth, security, fitness for purpose or product approval. Use broader terms only when the evidence supports that broader scope. +4. **Earn outcome claims.** Claims about speed, effort, quality, productivity, cost or error reduction require measurements for the named product and task, including the baseline, conditions and date. Otherwise describe the supported mechanism or intended use. Adding “helps,” “can” or “designed to” does not rescue an unsupported claim. +5. **Avoid universal promises.** Review “every,” “always,” “never,” “complete,” “automatic,” “right,” “best” and equivalent wording for a bounded, demonstrable meaning. Prefer the specific supported action over a slogan plus a disclaimer. +6. **State availability accurately.** Keep shipped behavior, examples, experiments, product hypotheses and roadmap items distinct. Historical evaluation results retain their original version and conditions. Do not present an example or recording as a live feature or current release proof. +7. **Make the action honest.** A CTA describes what happens at its destination. Pricing, signup, account recovery, credential storage and agent access statements must match the current flow. Put a prerequisite beside the action when omitting it would change the reader's decision. + +## Product boundaries + +1. **JudgmentKit:** describe guidance for activity and interaction choices, contracts, supported checks and repair instructions. The caller's agent builds, tests and repairs the interface; the builder retains product decisions. Do not promise a useful interface, the right design, successful repair or approval from a passing receipt alone. +2. **kanban.cards:** describe recording and updating project work through Cards, Columns, Comments and Activity. Name the person or authorized agent that changes the board; agent updates require the installed skill and read/write access. A recorded next action does not execute itself, and stored board state does not independently establish that the work happened. Recovery instructions must state that replacing access revokes old passkeys, sessions and agent keys before the person submits. +3. **Surfaces:** describe the supported decision and guidance workflow at its actual release stage. Reuse of a recorded decision is an intended use until execution evidence for that workflow establishes it. Do not infer reduced review effort or universal coverage from the existence of a workflow. + +## Copy and composition + +1. Keep the existing hero budget: one eyebrow up to 12 words, one h1 up to 10 words, one lead up to 32 words and exactly two hero actions. The complete homepage main content stays at or below 220 words, including its section headings, item labels and actions. There is no minimum. +2. Each section adds a different fact or action. Do not fill the three-item row or five-step workflow with invented benefits, repeated positioning or steps the product does not support. +3. Use concrete objects and familiar verbs. Explain product vocabulary through its action rather than defining internal architecture. Remove claims that could fit any AI tool or project board. +4. Preserve useful value. Narrow an unsupported promise to what someone can actually do; do not replace the homepage with caveats, implementation details or a risk checklist. Detailed limits belong on the linked destination page unless they are necessary to understand the headline or CTA. +5. Keep the same promise across visible copy, metadata, social descriptions and action destinations. Illustrations remain decorative and must not imply an application state or capability the product lacks. + +## Page and social titles + +1. Owned public HTML pages use `Specific page topic | Canonical brand`. Write the canonical brand once, at the end: `Surfaces`, `JudgmentKit` or `kanban.cards`. Keep each authored page's title distinct and descriptive; do not copy the homepage title onto unrelated subpages. +2. Describe the actual subject with familiar words. Avoid keyword lists, internal implementation labels and promises stronger than the page supports. Keep titles concise by removing unnecessary words, without imposing a search-display character limit or promising search ranking. +3. The approved homepage titles are `UI Review Decisions and Guidance | Surfaces`, `Interface Design Guidance for AI Agents | JudgmentKit` and `Shared Kanban Board for People and Agents | kanban.cards`. Visible hero headings and navigation labels retain their own jobs and wording. +4. Document, Open Graph and Twitter titles use the same branded title on public content pages. Kanban access pages use a fixed public homepage identity for social metadata while their document title names the resolved access task. +5. Kanban titles follow the validated, rendered public state, including sign-in, signup, recovery, setup and receipts. Recovery and setup cannot be distinguished by pathname alone. Use fixed labels; never include account details, recovery codes, tokens or private project content. Signed-in, local and transitional product views retain the product-only title. +6. Application names, installed-app names and site-name fields remain product-only. Preserve copied examples, immutable recordings and historical evaluation titles. Keep test/demo status explicit and preserve existing canonical and indexing policy during a title-only change. + +## Adversarial acceptance + +Before handoff, review the complete rendered copy through four passes: + +1. **Skeptical buyer:** write the strongest reasonable promise a visitor could infer. Check whether current evidence supports that promise. +2. **Capability and availability:** trace each material claim to behavior; name dependencies, actors and unsupported extrapolations. +3. **Clarity and necessity:** check undefined terms, repeated claims, filler and whether each CTA lands on what it promises. +4. **Consistency and rendered fit:** compare page copy, metadata and destinations, then inspect desktop/mobile wrapping in both appearances. + +Record concrete findings as High, Medium or Low with the original phrase, inference, evidence, replacement and disposition. Reviewers may retain a defensible claim; restraint does not require weakening accurate statements. Acceptance requires resolving misleading current claims, preserving necessary prerequisites and documenting any contract requirements not met and the reason. diff --git a/docs/specs/website-family-layout.md b/docs/specs/website-family-layout.md new file mode 100644 index 0000000..1fd9814 --- /dev/null +++ b/docs/specs/website-family-layout.md @@ -0,0 +1,52 @@ +# Surfaces website family layout contract + +## Reference and purpose + +This contract records the rendered composition of [surfaces.systems](https://www.surfaces.systems/) observed on 16 October 2026 at 1280×720 and 390×844. It governs the shared public homepage composition. Preserve Surfaces' existing hero artwork and layout as the visual reference, including its content density and mobile composition. Matching fonts and colors alone does not satisfy this contract. + +Public copy also follows [the website family copy contract](website-family-copy.md). Text may be narrowed without replacing the reference artwork or layout. + +## Page composition + +1. A 72px Ink header contains the complete reversed Nickel lockup at the left and no more than four primary navigation links at the right. Compact navigation becomes one Menu control. Main content aligns to the same 1120px container as the header and footer. +2. One full-width image hero sits behind one left-aligned text stack. At 1280px, the scene below the header is approximately 560px tall; the next section begins at y=632px. The image reaches every edge of the hero. A dark overlay protects the copy while retaining the artwork on the right. The image is not a separate column, rounded frame, bordered box, inset panel or card. +3. One problem section has a small eyebrow, one heading and a ruled row of three equal items. The section uses a quiet contrasting surface, 80px top padding and 56px bottom padding on desktop. Each item has a 12px ordinal, one short 20px/600 title and one brief 14px muted supporting phrase. Shared top/bottom rules, internal separators and small connector notches organize the row; there are no fully boxed items, rounded cards, shadows or floating tiles. +4. One workflow section follows with 96px vertical padding on desktop. A small eyebrow and one heading introduce one connected row of five steps. Each step has a 12px ordinal, a restrained outline icon in a 40–44px square frame with 4px corners, a short 20px/600 action title and a brief 14px muted explanation. Desktop ordinals sit above the icon frames. A small signal accent may identify the final step; it must not alter the logo. Additional branch labels appear only when the product has a documented branch to explain. +5. At most two short supporting sentences follow the workflow. The footer remains compact: reversed lockup, one short product description, copyright and any necessary access utility. Do not add feature-card grids, repeated CTAs, an FAQ, testimonials, installation instructions, evidence galleries or more marketing sections to the homepage. Detailed material remains on its existing destination pages. + +## Hero hierarchy and copy budget + +1. The hero text occupies roughly the left half of the desktop container, with a maximum width near 560px. The headline is the dominant text element; the image fills the environment around it. +2. Use one eyebrow of at most 12 words, one action- or purpose-led h1 of at most 10 words, and one supporting sentence of at most 32 words. An outcome claim needs the evidence required by the copy contract. The reference has no second explanatory paragraph, bullet list or extra bold positioning sentence in the hero. +3. Use exactly two hero actions. Primary is Ivory with Ink text; secondary is transparent with an Ivory rule. Both are at least 48px tall with 4px corners. At compact widths, actions stack and fill the available width. Recovery and other access utilities belong in the sign-in flow or footer rather than a third large hero action. +4. The homepage main-content budget is at most 220 words, including section headings, item labels and actions. The rendered Surfaces main content contains approximately 182 words. Shorter copy is acceptable when it explains the product. Shorten inherited copy that exceeds this budget rather than retaining every existing block. +5. Product capabilities and claims must match current behavior. A vision document establishes purpose, not shipped behavior or measured value. Copy may be shortened or recomposed without introducing capabilities, measured benefits, approval claims or invented proof. Keep a provenance map that distinguishes implemented behavior, illustrative examples and intended use. Each product retains its own purpose and actions. + +## Image contract + +1. Use a real raster hero asset with a wide composition, approximately 2.5:1. Match the full-width placement, left copy space and readable crop. Keep the cinematic 3D rendering, controlled lighting and tangible material language of the JudgmentKit and Surfaces images. Distinguish each product through its subject and composition rather than switching to flat illustration or stationery photography. +2. JudgmentKit's existing lens artwork becomes a wide environmental background. Preserve its recognizable subject and product metaphor, place the lens and ordered path primarily on the right, and extend the environment leftward for readable copy. It must no longer appear in a framed portrait panel beside the text. +3. Create a new Kanban image with a distinct card-and-flow concept in that same cinematic 3D language. Do not repeat Surfaces' three architectural slabs or JudgmentKit's lens. Show cards or work becoming organized, with the main composition toward the right and quiet space for the copy on the left. Present multiple rendered concepts before selecting the final artwork. The user rejected the flat cut-paper, graphic and stationery-photo directions. Do not copy the Surfaces mark or show a misleading application screenshot. +4. Do not bake text, controls, logos, labels or a gradient overlay into the image. Render the logo and copy as accessible page content. Apply the readability overlay in CSS so mobile cropping can be tuned independently. +5. At 390px, crop the image into a full-width background behind the entire hero stack. The artwork remains visible around the copy without creating a separate image section or pushing the next section down to accommodate an illustration card. + +## Typography, surfaces and responsive behavior + +1. Use Inter for website copy, with a variable 100–900 weight face, `font-display: swap` and the reference's antialiased/grayscale font smoothing on the public-page body. Desktop h1/h2 are 40/32px, weight 600, line height 1.15 and zero tracking. At 620px and below, use 32/24px. Hero supporting copy is regular 20px desktop and follows the reference's compact type at mobile widths. Body copy is regular 16px/1.45. Navigation and actions are 14px/500. Computed weights alone do not establish visual parity; inspect the rendered supporting text next to the reference. +2. Use Ink `#0B0D0E`, Ivory `#F7F6F2`, Nickel `#8B9096`, rule `#DAD7CF` and the reference's neutral surface roles. Hero and footer remain Ink in both appearances. Lower sections follow the reference's light/dark surface alternation. Lower-section eyebrows use quiet Nickel in dark appearance and `#555B60` in light appearance; footer supporting text uses Nickel. Preserve semantic form colors where they communicate real states. +3. Use a 1120px maximum container, 24px desktop gutters and 16px compact gutters. At 820px and below, navigation compacts, section padding becomes 52px vertically and the three-part row and five-step workflow become connected vertical rows. +4. On mobile, keep each workflow ordinal/icon in a narrow left column and its title/explanation in the right column. Align the title near the ordinal and place its explanation beneath. Use shared horizontal separators and downward connector notches. Do not turn the workflow into a loose stack of centered cards. The footer changes from a desktop row to a left-aligned stack of lockup, description and copyright. +5. Match the reference's restrained hierarchy, whitespace, image scale and section rhythm at full-page scale. The desktop homepage should be roughly the same overall length as Surfaces, with content length allowed to affect exact height. Do not force identical heights by adding empty sections or hiding content. + +## Sign-in and preservation + +1. Kanban's public homepage supplies its existing account-creation and Sign in actions; the signup label is Create account. Existing recovery, setup, passkey, verification, one-time recovery and sign-out behavior must remain available. +2. Sign-in forms use the family header, typography, neutral palette, controls and footer. Form grouping may use a compact panel; the prohibition on boxed artwork applies to homepage heroes. +3. Signed-in product screens, auth/session/data policies, native apps, canonical lockups, favicon geometry and social artwork stay outside this presentation change. JudgmentKit has no sign-in route in this repository; do not invent one. + +## Acceptance + +1. Inspect the Surfaces, JudgmentKit and kanban.cards public homepages visually at desktop and mobile widths in light and dark appearance. Save actual browser screenshots and compare full-page composition as well as the hero. Small thumbnails and computed-style tests alone are insufficient. +2. Verify that each hero has full-width background imagery, one text stack and two actions, with no separate image card. Confirm the three-item problem row, five-step workflow and compact footer. +3. Count the visible main-content words, check every claim against its provenance and confirm that detailed material remains reachable on existing pages. +4. Check image crop, text contrast, logo proportions, section spacing, mobile workflow alignment, focus and menu behavior, auth entry and signed-in style isolation. Run the affected build and regression checks after visual acceptance.