From df3cdf976c1a182e753631ca72007a61f046b336 Mon Sep 17 00:00:00 2001 From: 0thernet Date: Tue, 1 Sep 2026 12:00:55 -0400 Subject: [PATCH 1/3] feat: add product marketing compositions --- README.md | 63 ++- package.json | 5 +- portfolio-inventory.json | 2 +- src/design-gallery.css | 18 + src/product-marketing.css | 567 +++++++++++++++++++++++++++ src/product-marketing.test.ts | 33 ++ src/public-boundary.test.ts | 7 + src/react/design-gallery.test.tsx | 8 + src/react/design-gallery.tsx | 55 +++ src/react/index.ts | 1 + src/react/product-marketing.test.tsx | 149 +++++++ src/react/product-marketing.tsx | 511 ++++++++++++++++++++++++ src/react/server.ts | 1 + src/styles.css | 1 + 14 files changed, 1417 insertions(+), 4 deletions(-) create mode 100644 src/product-marketing.css create mode 100644 src/product-marketing.test.ts create mode 100644 src/react/product-marketing.test.tsx create mode 100644 src/react/product-marketing.tsx diff --git a/README.md b/README.md index 69ef918..c3ff93a 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Pin the immutable GitHub release: ```json { "dependencies": { - "@hraness/design-kit": "github:hraness/design-kit#v0.2.1", + "@hraness/design-kit": "github:hraness/design-kit#v0.3.0", "@hraness/ui": "github:hraness/ui#v0.4.7" } } @@ -45,6 +45,65 @@ Import narrower layers when the application does not need the full presentation `plain-site.css` provides a compact site shell. `plain-publication.css` adds sourced article, citation, table, callout, and related-reading structure. +## Explain a technical product + +`product-marketing.css` is an opt-in narrative grammar for technical product +sites. It supplies outcome-led hero, ordered workflow, fact strip, install, +proof frame, narrative section, interface, trust-boundary, native question, and +call-to-action roles. The classes own responsive structure and semantics-facing +presentation. Products bind the `--hraness-marketing-*` roles to their own +palette, type, content, evidence, and action. + +Static sites may render the documented classes directly. React sites can use +the server-safe compositions from either React entry: + +```tsx +import { + MarketingFlow, + ProductHero, +} from "@hraness/design-kit/react/server"; + + + ), + }} + summary="The same owned job can be initialized, run, and inspected from a human or agent surface." +/> +``` + +Import only the grammar when a site owns its reset and tokens: + +```css +@import "@hraness/design-kit/product-marketing.css"; +``` + +The components render complete server HTML and add no clipboard, animation, or +analytics runtime. A product may enhance a command with its own accessible copy +control while keeping selectable text as the fallback. + ## Use application compositions ```tsx @@ -253,7 +312,7 @@ bun install --frozen-lockfile bun run check ``` -The stable dependency pair for this release is `@hraness/ui` `v0.4.7` with `@hraness/design-kit` `v0.2.1`. The previous rollback pair remains `@hraness/ui` `v0.4.6` with `@hraness/design-kit` `v0.1.9`. +The stable dependency pair for this release is `@hraness/ui` `v0.4.7` with `@hraness/design-kit` `v0.3.0`. The previous rollback pair remains `@hraness/ui` `v0.4.7` with `@hraness/design-kit` `v0.2.1`. The complete check runs linting, typechecking, production builds, an installed-package smoke test, deterministic examples, property tests, server rendering, vendor-integrity checks, and headless Chromium regressions. The browser gate verifies responsive shell ownership, extracted AnimatedRailStage, Fader, layout-surface, and playback-transport delivery, reduced-motion stage fallback, Fader keyboard and focus behavior, forced-color behavior, keyboard-operable appearance, browser-chrome synchronization across opposing device and saved preferences, global-error static metadata and runtime lifecycle, accessible title and copy, deterministic procedural layers, viewport containment, and the absence of the excluded canvas effect. Set `CHROMIUM_EXECUTABLE_PATH` when Chromium or Chrome is installed outside the standard macOS and Linux paths. diff --git a/package.json b/package.json index f208a1b..c7a1a5d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hraness/design-kit", - "version": "0.2.1", + "version": "0.3.0", "description": "Opinionated application compositions, charts, effects, and syntax presentation built on @hraness/ui.", "license": "MIT", "type": "module", @@ -57,6 +57,7 @@ "./jelly.css": "./src/jelly.css", "./plain-publication.css": "./src/plain-publication.css", "./plain-site.css": "./src/plain-site.css", + "./product-marketing.css": "./src/product-marketing.css", "./reset.css": "./src/reset.css", "./styles.css": "./src/styles.css", "./stylex.css": "./dist/stylex.css", @@ -94,6 +95,7 @@ "src/jelly.css", "src/plain-publication.css", "src/plain-site.css", + "src/product-marketing.css", "src/react/animated-rail-stage.stylex.ts", "src/react/animated-rail-stage.tsx", "src/react/app-shell.tsx", @@ -122,6 +124,7 @@ "src/react/procedural-recipe.ts", "src/react/production-data-preview-notice.tsx", "src/react/production-data-preview-notice.stylex.ts", + "src/react/product-marketing.tsx", "src/react/route-state.tsx", "src/react/server.ts", "src/react/surfaces.stylex.ts", diff --git a/portfolio-inventory.json b/portfolio-inventory.json index b5c6fb4..0ae5f48 100644 --- a/portfolio-inventory.json +++ b/portfolio-inventory.json @@ -6,7 +6,7 @@ "name": "@hraness/design-kit", "path": ".", "visibility": "public", - "version": "0.2.1" + "version": "0.3.0" } ], "contract": "hraness.portfolio-inventory/v1", diff --git a/src/design-gallery.css b/src/design-gallery.css index b6cf1ac..c83603a 100644 --- a/src/design-gallery.css +++ b/src/design-gallery.css @@ -84,6 +84,23 @@ padding: var(--space-4); } +.design-gallery__marketing { + display: grid; + gap: var(--space-8); + padding: clamp(var(--space-4), 3vw, var(--space-8)); + border: 1px solid var(--line); + background: var(--background); +} + +.design-gallery__marketing-command { + max-inline-size: 100%; + margin: 0; + padding: var(--space-4); + overflow: auto; + background: var(--inverse); + color: var(--inverse-foreground); +} + .design-gallery__shell-preview { block-size: min(42rem, 72vh); min-block-size: 26rem; @@ -224,6 +241,7 @@ .design-gallery__instrument, .design-gallery__chat, .design-gallery__effect, + .design-gallery__marketing, .design-gallery__syntax { border-color: CanvasText; } diff --git a/src/product-marketing.css b/src/product-marketing.css new file mode 100644 index 0000000..2b1b69e --- /dev/null +++ b/src/product-marketing.css @@ -0,0 +1,567 @@ +@layer components.hraness-design-kit.legacy { + +:where( + .hraness-marketing-hero, + .hraness-marketing-install, + .hraness-marketing-section, + .hraness-marketing-interfaces, + .hraness-marketing-trust, + .hraness-marketing-questions, + .hraness-marketing-cta +) { + --hraness-marketing-ink: var(--foreground, CanvasText); + --hraness-marketing-muted: var(--muted, GrayText); + --hraness-marketing-line: var(--line, currentColor); + --hraness-marketing-surface: var(--surface, Canvas); + --hraness-marketing-inverse: var(--inverse, CanvasText); + --hraness-marketing-inverse-ink: var(--inverse-foreground, Canvas); + --hraness-marketing-accent: var(--primary, currentColor); + --hraness-marketing-text-font: var(--font-text, ui-sans-serif, system-ui, sans-serif); + --hraness-marketing-heading-font: var(--font-heading, var(--hraness-marketing-text-font)); + --hraness-marketing-mono-font: var(--font-mono, ui-monospace, monospace); + --hraness-marketing-measure: 74rem; + --hraness-marketing-copy-measure: 68ch; + --hraness-marketing-rule: 1px solid var(--hraness-marketing-line); + color: var(--hraness-marketing-ink); +} + +.hraness-marketing-hero { + display: grid; + inline-size: min(100%, var(--hraness-marketing-measure)); + min-inline-size: 0; + margin-inline: auto; + gap: clamp(2rem, 5vw, 4.5rem); + grid-template-columns: minmax(0, 1.04fr) minmax(min(100%, 22rem), 0.96fr); +} + +.hraness-marketing-hero__copy { + min-inline-size: 0; + align-self: center; + padding-block: clamp(1rem, 5vw, 4rem); +} + +:where( + .hraness-marketing-hero__eyebrow, + .hraness-marketing-hero__name, + .hraness-marketing-hero__boundary, + .hraness-marketing-proof__kicker, + .hraness-marketing-install__eyebrow, + .hraness-marketing-facts dt, + .hraness-marketing-section__label, + .hraness-marketing-interfaces__label, + .hraness-marketing-trust__label, + .hraness-marketing-questions__label, + .hraness-marketing-cta__eyebrow +) { + font-family: var(--hraness-marketing-mono-font); + font-size: 0.72rem; + font-weight: 700; + letter-spacing: 0.1em; + line-height: 1.4; + text-transform: uppercase; +} + +.hraness-marketing-hero__eyebrow { + margin: 0 0 clamp(2rem, 5vw, 4rem); + color: var(--hraness-marketing-accent); +} + +.hraness-marketing-hero__name { + margin: 0 0 0.65rem; +} + +.hraness-marketing-hero__heading { + max-inline-size: 11ch; + margin: 0; + font-family: var(--hraness-marketing-heading-font); + font-size: clamp(3.25rem, 7.6vw, 6.6rem); + font-weight: 700; + letter-spacing: -0.065em; + line-height: 0.92; + text-wrap: balance; +} + +.hraness-marketing-hero__summary { + max-inline-size: 38rem; + margin: 1.75rem 0 0; + color: var(--hraness-marketing-muted); + font-family: var(--hraness-marketing-text-font); + font-size: clamp(1.1rem, 2.25vw, 1.55rem); + line-height: 1.4; + text-wrap: pretty; +} + +.hraness-marketing-hero__actions, +.hraness-marketing-cta__actions { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.65rem; +} + +.hraness-marketing-hero__actions { + margin-block-start: clamp(1.5rem, 3vw, 2.25rem); +} + +.hraness-marketing-action { + display: inline-flex; + min-block-size: 2.75rem; + align-items: center; + justify-content: center; + padding: 0.7rem 1rem; + border: var(--hraness-marketing-rule); + border-radius: var(--hraness-marketing-action-radius, 0); + color: var(--hraness-marketing-ink); + font-family: var(--hraness-marketing-text-font); + font-size: 0.9rem; + font-weight: 700; + text-decoration: none; +} + +.hraness-marketing-action:hover { + background: color-mix(in oklch, var(--hraness-marketing-ink) 8%, transparent); + text-decoration: none; +} + +.hraness-marketing-action[data-emphasis="primary"] { + background: var(--hraness-marketing-ink); + color: var(--hraness-marketing-surface); +} + +.hraness-marketing-action[data-emphasis="primary"]:hover { + background: color-mix(in oklch, var(--hraness-marketing-ink) 88%, var(--hraness-marketing-surface)); +} + +.hraness-marketing-hero__boundary { + max-inline-size: 48rem; + margin: 1.1rem 0 0; + color: var(--hraness-marketing-muted); + line-height: 1.6; +} + +.hraness-marketing-proof { + min-inline-size: 0; + align-self: stretch; + padding: clamp(1.25rem, 3.5vw, 2.4rem); + border: 1px solid color-mix(in oklch, var(--hraness-marketing-inverse-ink) 24%, transparent); + background: + linear-gradient(color-mix(in oklch, var(--hraness-marketing-inverse-ink) 6%, transparent) 1px, transparent 1px), + linear-gradient(90deg, color-mix(in oklch, var(--hraness-marketing-inverse-ink) 6%, transparent) 1px, transparent 1px), + var(--hraness-marketing-inverse); + background-size: 2.5rem 2.5rem; + color: var(--hraness-marketing-inverse-ink); +} + +.hraness-marketing-proof__kicker { + margin: 0 0 0.6rem; + color: color-mix(in oklch, var(--hraness-marketing-inverse-ink) 64%, transparent); +} + +.hraness-marketing-proof__heading { + max-inline-size: 16ch; + margin: 0 0 clamp(1.75rem, 4vw, 3.5rem); + font-family: var(--hraness-marketing-heading-font); + font-size: clamp(1.55rem, 3vw, 2.2rem); + line-height: 1.1; + text-wrap: balance; +} + +.hraness-marketing-flow { + display: grid; + min-inline-size: 0; + margin: 0; + padding: 0; + list-style: none; +} + +.hraness-marketing-flow__step { + display: grid; + min-inline-size: 0; + margin: 0; + padding-block: 1rem; + border-block-start: 1px solid color-mix(in oklch, currentColor 24%, transparent); + gap: 0.85rem; + grid-template-columns: 2.25rem minmax(0, 1fr); +} + +.hraness-marketing-flow__number { + padding-block-start: 0.16rem; + color: color-mix(in oklch, currentColor 58%, transparent); + font-family: var(--hraness-marketing-mono-font); + font-size: 0.72rem; +} + +.hraness-marketing-flow__body { + min-inline-size: 0; +} + +.hraness-marketing-flow__label, +.hraness-marketing-flow__code, +.hraness-marketing-flow__detail { + display: block; +} + +.hraness-marketing-flow__label { + margin-block-end: 0.35rem; + font-family: var(--hraness-marketing-text-font); + font-size: 0.84rem; +} + +.hraness-marketing-flow__code { + padding: 0; + background: transparent; + color: inherit; + font-family: var(--hraness-marketing-mono-font); + font-size: 0.76rem; + overflow-wrap: anywhere; +} + +.hraness-marketing-flow__detail { + margin: 0.45rem 0 0; + color: color-mix(in oklch, currentColor 72%, transparent); + font-family: var(--hraness-marketing-text-font); + font-size: 0.78rem; + line-height: 1.45; +} + +.hraness-marketing-facts { + display: grid; + margin: 0; + border-block: var(--hraness-marketing-rule); + grid-column: 1 / -1; + grid-template-columns: repeat(var(--hraness-marketing-fact-columns, 4), minmax(0, 1fr)); +} + +.hraness-marketing-facts > div { + min-inline-size: 0; + padding: 1.25rem clamp(0.8rem, 2vw, 1.5rem); +} + +.hraness-marketing-facts > div + div { + border-inline-start: var(--hraness-marketing-rule); +} + +.hraness-marketing-facts dt { + margin-block-end: 0.55rem; + color: var(--hraness-marketing-muted); +} + +.hraness-marketing-facts dd { + margin: 0; +} + +.hraness-marketing-facts :where(strong, span) { + display: block; +} + +.hraness-marketing-facts strong { + font-family: var(--hraness-marketing-text-font); + font-size: 1rem; +} + +.hraness-marketing-facts span { + margin-block-start: 0.25rem; + color: var(--hraness-marketing-muted); + font-family: var(--hraness-marketing-text-font); + font-size: 0.76rem; + line-height: 1.45; +} + +.hraness-marketing-install, +.hraness-marketing-section, +.hraness-marketing-interfaces, +.hraness-marketing-trust, +.hraness-marketing-questions, +.hraness-marketing-cta { + inline-size: min(100%, var(--hraness-marketing-measure)); + min-inline-size: 0; + margin-inline: auto; +} + +.hraness-marketing-install { + display: grid; + padding: clamp(1.25rem, 4vw, 2.5rem); + border: var(--hraness-marketing-rule); + gap: clamp(1.5rem, 4vw, 4rem); + grid-template-columns: minmax(11rem, 0.55fr) minmax(0, 1.45fr); + scroll-margin-block-start: 1rem; +} + +.hraness-marketing-install__eyebrow { + margin: 0 0 0.6rem; + color: var(--hraness-marketing-muted); +} + +.hraness-marketing-install__heading, +.hraness-marketing-section__heading, +.hraness-marketing-interfaces__heading, +.hraness-marketing-trust__heading, +.hraness-marketing-questions__heading, +.hraness-marketing-cta__heading { + margin: 0; + font-family: var(--hraness-marketing-heading-font); + line-height: 1.08; + text-wrap: balance; +} + +.hraness-marketing-install__heading { + max-inline-size: 10ch; + font-size: clamp(1.65rem, 3.4vw, 2.65rem); +} + +.hraness-marketing-install__commands, +.hraness-marketing-section__body { + min-inline-size: 0; +} + +.hraness-marketing-install__commands :where(pre) { + max-inline-size: 100%; + overflow: auto; +} + +.hraness-marketing-proof-frame { + min-inline-size: 0; + margin: 0; + border: var(--hraness-marketing-rule); + background: var(--hraness-marketing-surface); +} + +.hraness-marketing-proof-frame__content { + min-inline-size: 0; + overflow: auto; +} + +.hraness-marketing-proof-frame__caption { + display: grid; + padding: 0.75rem 0.9rem; + border-block-start: var(--hraness-marketing-rule); + color: var(--hraness-marketing-muted); + font-family: var(--hraness-marketing-text-font); + font-size: 0.78rem; + gap: 0.2rem; + line-height: 1.45; +} + +.hraness-marketing-proof-frame__caption small { + font-family: var(--hraness-marketing-mono-font); + font-size: 0.68rem; +} + +.hraness-marketing-section { + display: grid; + padding-block: clamp(2.5rem, 7vw, 6rem); + border-block-start: var(--hraness-marketing-rule); + gap: clamp(2rem, 5vw, 6rem); + grid-template-columns: minmax(10rem, 0.55fr) minmax(0, 1.45fr); +} + +.hraness-marketing-section__label, +.hraness-marketing-interfaces__label, +.hraness-marketing-trust__label, +.hraness-marketing-questions__label { + margin: 0; + color: var(--hraness-marketing-muted); +} + +.hraness-marketing-section__heading, +.hraness-marketing-interfaces__heading, +.hraness-marketing-trust__heading, +.hraness-marketing-questions__heading { + font-size: clamp(1.75rem, 4vw, 3rem); +} + +.hraness-marketing-section__body { + display: grid; + max-inline-size: var(--hraness-marketing-copy-measure); + gap: 1rem; +} + +.hraness-marketing-section__body > :first-child { + margin-block-start: 0; +} + +.hraness-marketing-section__body > :last-child { + margin-block-end: 0; +} + +.hraness-marketing-interfaces, +.hraness-marketing-trust, +.hraness-marketing-questions { + padding-block: clamp(2.5rem, 7vw, 6rem); + border-block-start: var(--hraness-marketing-rule); +} + +.hraness-marketing-interfaces__header, +.hraness-marketing-trust__header, +.hraness-marketing-questions__header { + display: grid; + max-inline-size: var(--hraness-marketing-copy-measure); + margin-block-end: clamp(1.5rem, 4vw, 3rem); + gap: 0.65rem; +} + +.hraness-marketing-interfaces__header > p:last-child, +.hraness-marketing-trust__header > p:last-child, +.hraness-marketing-questions__header > p:last-child { + margin: 0; + color: var(--hraness-marketing-muted); + font-family: var(--hraness-marketing-text-font); +} + +.hraness-marketing-interface-grid, +.hraness-marketing-trust-grid { + display: grid; + gap: 1px; + border: var(--hraness-marketing-rule); + background: var(--hraness-marketing-line); + grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr)); +} + +.hraness-marketing-interface, +.hraness-marketing-trust-item { + min-inline-size: 0; + padding: clamp(1rem, 2.5vw, 1.5rem); + background: var(--hraness-marketing-surface); +} + +.hraness-marketing-interface :where(.hraness-marketing-interface__heading, p), +.hraness-marketing-trust-item :where(dt, dd) { + margin: 0; +} + +.hraness-marketing-interface__heading, +.hraness-marketing-trust-item dt { + font-family: var(--hraness-marketing-text-font); + font-size: 0.95rem; + font-weight: 700; +} + +.hraness-marketing-interface p, +.hraness-marketing-trust-item dd { + margin-block-start: 0.5rem; + color: var(--hraness-marketing-muted); + font-family: var(--hraness-marketing-text-font); + font-size: 0.84rem; + line-height: 1.5; +} + +.hraness-marketing-interface pre { + max-inline-size: 100%; + margin-block: 1rem 0; + overflow: auto; +} + +.hraness-marketing-question { + border-block-start: var(--hraness-marketing-rule); +} + +.hraness-marketing-question:last-child { + border-block-end: var(--hraness-marketing-rule); +} + +.hraness-marketing-question summary { + min-block-size: 3rem; + padding-block: 1rem; + color: var(--hraness-marketing-ink); + cursor: pointer; + font-family: var(--hraness-marketing-text-font); + font-weight: 700; +} + +.hraness-marketing-question__answer { + max-inline-size: var(--hraness-marketing-copy-measure); + padding-block: 0 1.25rem; + color: var(--hraness-marketing-muted); + font-family: var(--hraness-marketing-text-font); +} + +.hraness-marketing-question__answer > :first-child { + margin-block-start: 0; +} + +.hraness-marketing-cta { + display: grid; + padding: clamp(2rem, 6vw, 5rem); + background: var(--hraness-marketing-inverse); + color: var(--hraness-marketing-inverse-ink); + gap: 1rem; +} + +.hraness-marketing-cta__eyebrow, +.hraness-marketing-cta__summary { + margin: 0; + color: color-mix(in oklch, currentColor 72%, transparent); +} + +.hraness-marketing-cta__heading { + max-inline-size: 18ch; + font-size: clamp(2rem, 5vw, 4.5rem); +} + +.hraness-marketing-cta__summary { + max-inline-size: var(--hraness-marketing-copy-measure); + font-family: var(--hraness-marketing-text-font); + text-wrap: pretty; +} + +.hraness-marketing-cta .hraness-marketing-action { + border-color: currentColor; + color: inherit; +} + +.hraness-marketing-cta .hraness-marketing-action[data-emphasis="primary"] { + background: var(--hraness-marketing-inverse-ink); + color: var(--hraness-marketing-inverse); +} + +@media (max-width: 48rem) { + .hraness-marketing-hero, + .hraness-marketing-install, + .hraness-marketing-section { + grid-template-columns: minmax(0, 1fr); + } + + .hraness-marketing-hero__copy { + padding-block-end: 0; + } + + .hraness-marketing-hero__heading { + max-inline-size: 12ch; + } + + .hraness-marketing-facts { + grid-template-columns: minmax(0, 1fr); + } + + .hraness-marketing-facts > div + div { + border-block-start: var(--hraness-marketing-rule); + border-inline-start: 0; + } + + .hraness-marketing-install__heading { + max-inline-size: none; + } +} + +@media (pointer: coarse) { + .hraness-marketing-action, + .hraness-marketing-question summary { + min-block-size: 3rem; + } +} + +@media (forced-colors: active) { + .hraness-marketing-proof, + .hraness-marketing-cta { + border: 1px solid CanvasText; + background: Canvas; + color: CanvasText; + } + + .hraness-marketing-interface-grid, + .hraness-marketing-trust-grid { + background: CanvasText; + } +} + +} diff --git a/src/product-marketing.test.ts b/src/product-marketing.test.ts new file mode 100644 index 0000000..205b8a5 --- /dev/null +++ b/src/product-marketing.test.ts @@ -0,0 +1,33 @@ +import { expect, test } from "bun:test"; + +const css = await Bun.file(new URL("./product-marketing.css", import.meta.url)).text(); +const styles = await Bun.file(new URL("./styles.css", import.meta.url)).text(); + +test("the product-marketing entry is product-neutral and independently importable", () => { + expect(css).toStartWith("@layer components.hraness-design-kit.legacy {"); + expect(styles).toContain('@import "./product-marketing.css";'); + expect(css).toContain(".hraness-marketing-hero"); + expect(css).toContain(".hraness-marketing-flow"); + expect(css).toContain(".hraness-marketing-facts"); + expect(css).toContain(".hraness-marketing-install"); + expect(css).toContain(".hraness-marketing-proof-frame"); + expect(css).toContain(".hraness-marketing-section"); + expect(css).toContain(".hraness-marketing-interface-grid"); + expect(css).toContain(".hraness-marketing-trust-grid"); + expect(css).toContain(".hraness-marketing-question"); + expect(css).toContain(".hraness-marketing-cta"); + expect(css).not.toMatch(/soloterm|atet|wrench|message like me|peopleblade|\bhra\b/iu); + expect(css).not.toMatch(/#[0-9a-f]{3,8}\b/iu); +}); + +test("the marketing grammar keeps compact, coarse-pointer, and forced-color contracts", () => { + expect(css).toContain("@media (max-width: 48rem)"); + expect(css).toContain("@media (pointer: coarse)"); + expect(css).toContain("@media (forced-colors: active)"); + expect(css).toContain("grid-template-columns: minmax(0, 1fr)"); + expect(css).toContain("min-block-size: 3rem"); + expect(css).toContain("background: Canvas;"); + expect(css).toContain("color: CanvasText;"); + expect(css).not.toContain("transition:"); + expect(css).not.toContain("animation:"); +}); diff --git a/src/public-boundary.test.ts b/src/public-boundary.test.ts index 2b2f02e..63a7ca8 100644 --- a/src/public-boundary.test.ts +++ b/src/public-boundary.test.ts @@ -77,6 +77,13 @@ test("the package exposes compositions without a second primitive barrel", async types: "./src/react/server.ts", import: "./dist/react/server.js", }); + expect(packageJson.exports["./product-marketing.css"]).toBe( + "./src/product-marketing.css", + ); + expect(reactBarrel).toContain('./product-marketing.js'); + expect( + await Bun.file(new URL("./react/server.ts", import.meta.url)).text(), + ).toContain('./product-marketing.js'); for (const module of excludedPrimitiveModules) { expect(reactBarrel).not.toContain(`./${module}`); } diff --git a/src/react/design-gallery.test.tsx b/src/react/design-gallery.test.tsx index fc3fec8..e79cf25 100644 --- a/src/react/design-gallery.test.tsx +++ b/src/react/design-gallery.test.tsx @@ -12,6 +12,7 @@ import { test("the public gallery covers the composition boundary", () => { expect(designGallerySections.map(({ id }) => id)).toEqual([ "foundation", + "marketing", "shells", "data", "effects", @@ -27,6 +28,7 @@ test("the public gallery covers the composition boundary", () => { expect(designGalleryRecipeCoverage).toContain("layout surfaces"); expect(designGalleryRecipeCoverage).toContain("playback transport"); expect(designGalleryRecipeCoverage).toContain("plain site and publication grammar"); + expect(designGalleryRecipeCoverage).toContain("product-marketing grammar"); expect(designGalleryRecipeCoverage).toContain("Nebula Sans typography"); expect(designGalleryRecipeCoverage).toContain("production preview notice"); expect(designGalleryTouchKinds).toEqual(["button", "link", "radio", "range"]); @@ -106,6 +108,12 @@ test("the gallery is product-neutral and server renderable", () => { expect(html).toContain('data-gallery-font="proportional"'); expect(html).toContain('data-gallery-font="mono"'); expect(html).toContain("More shape, less noise."); + expect(html).toContain('data-hraness-marketing="hero"'); + expect(html).toContain('data-hraness-marketing="flow"'); + expect(html).toContain('data-hraness-marketing="facts"'); + expect(html).toContain('data-hraness-marketing="install"'); + expect(html).toContain("Move one exact job across every interface."); + expect(html).toContain("bun add --global relay@1.2.3"); }); test("a nested gallery identifies itself and defers appearance to the product header", () => { diff --git a/src/react/design-gallery.tsx b/src/react/design-gallery.tsx index d151c15..dc6ebbc 100644 --- a/src/react/design-gallery.tsx +++ b/src/react/design-gallery.tsx @@ -31,6 +31,11 @@ import { NavigationRail, RailItem, RailSection } from "./navigation-rail.js"; import { PlaybackTransport, type PlaybackTransportStatus } from "./playback-transport.js"; import { ProceduralBackdrop } from "./procedural-backdrop.js"; import { ProductionDataPreviewNotice } from "./production-data-preview-notice.js"; +import { + MarketingFlow, + MarketingInstallPanel, + ProductHero, +} from "./product-marketing.js"; import { BottomBar, DitherSurface, @@ -43,6 +48,7 @@ import { type ConcreteDesignTheme, type DesignTheme } from "./theme.js"; export const designGallerySections = [ { id: "foundation", label: "Foundation" }, + { id: "marketing", label: "Marketing" }, { id: "shells", label: "Shells" }, { id: "data", label: "Data" }, { id: "effects", label: "Effects" }, @@ -70,6 +76,7 @@ export const designGalleryRecipeCoverage = [ "Jelly presentation", "playback transport", "plain site and publication grammar", + "product-marketing grammar", "Nebula Sans typography", "procedural effects", "production preview notice", @@ -199,6 +206,54 @@ export function DesignSystemGallery({ +
+

Product-marketing grammar

+
+ + ), + heading: "One job, three observable transitions", + kicker: "Working model", + }} + summary="The same owned job can be initialized, run, and inspected from a human or agent surface." + /> + +
bun add --global relay@1.2.3
+
+
+
+

Application shells

diff --git a/src/react/index.ts b/src/react/index.ts index 8b8cd3a..371efd0 100644 --- a/src/react/index.ts +++ b/src/react/index.ts @@ -18,6 +18,7 @@ export * from "./playback-transport.js"; export * from "./procedural-backdrop.js"; export * from "./procedural-recipe.js"; export * from "./production-data-preview-notice.js"; +export * from "./product-marketing.js"; export * from "./route-state.js"; export * from "./surfaces.js"; export * from "./syntax-code.js"; diff --git a/src/react/product-marketing.test.tsx b/src/react/product-marketing.test.tsx new file mode 100644 index 0000000..e564f63 --- /dev/null +++ b/src/react/product-marketing.test.tsx @@ -0,0 +1,149 @@ +import { expect, test } from "bun:test"; +import { renderToStaticMarkup } from "react-dom/server"; + +import { + MarketingCallToAction, + MarketingFlow, + MarketingInstallPanel, + MarketingInterfaceGrid, + MarketingProofFrame, + MarketingQuestionList, + MarketingSection, + MarketingTrustBoundary, + ProductHero, +} from "./product-marketing"; + +const steps = [ + { code: "tool init", detail: "Create one exact workspace.", label: "Initialize" }, + { code: "tool run job-01", detail: "Run the named job.", label: "Execute" }, +] as const; + +test("the product hero renders a complete semantic narrative without client behavior", () => { + const html = renderToStaticMarkup( + , + heading: "One job, two observable transitions", + kicker: "Working model", + }} + summary="The same owned job can be initialized, run, and inspected from a human or agent surface." + />, + ); + + expect(html.match(/'); + expect(html).toContain('

'); + expect(html).toContain('
    { + const html = renderToStaticMarkup( + <> + +
    bun add --global relay@1.2.3
    +
    + +
    {'{"status":"complete"}'}
    +
    + +

    The object keeps its identity while interfaces change.

    +
    + relay run, label: "CLI", summary: "For terminals and scripts." }, + { label: "SDK", summary: "For typed application code." }, + { label: "Skill", summary: "For coding-agent instruction." }, + ]} + label="Interfaces" + /> + + No. The local workflow works without one.

    , question: "Does it require an account?" }, + ]} + /> + + , + ); + + expect(html).toContain('data-hraness-marketing="install"'); + expect(html).toContain('data-hraness-marketing="proof-frame"'); + expect(html).toContain('data-hraness-marketing="section"'); + expect(html).toContain('data-hraness-marketing="interfaces"'); + expect(html).toContain('data-hraness-marketing="trust"'); + expect(html).toContain('data-hraness-marketing="questions"'); + expect(html).toContain('data-hraness-marketing="cta"'); + expect(html).toContain("bun add --global relay@1.2.3"); + expect(html).toContain('
    '); + expect(html).toContain("Does it require an account?"); + expect(html).toContain("For typed application code."); + expect(html).toContain("Source files and credentials."); + expect(html).toContain('

    CLI

    '); +}); + +test("an embedded hero advances its proof heading without adding another h1", () => { + const html = renderToStaticMarkup( + , + heading: "Inspect the flow", + }} + summary="A compact product-neutral example." + />, + ); + + expect(html).not.toContain("'); + expect(html).toContain('

    '); +}); diff --git a/src/react/product-marketing.tsx b/src/react/product-marketing.tsx new file mode 100644 index 0000000..3cebf3b --- /dev/null +++ b/src/react/product-marketing.tsx @@ -0,0 +1,511 @@ +import type { ReactNode } from "react"; + +export type MarketingHeadingLevel = 1 | 2 | 3 | 4 | 5 | 6; + +export interface MarketingAction { + readonly emphasis?: "primary" | "secondary"; + readonly href: string; + readonly label: string; +} + +export interface MarketingFact { + readonly detail?: string; + readonly label: string; + readonly value: string; +} + +export interface MarketingStep { + readonly code?: string; + readonly detail?: string; + readonly label: string; +} + +function classNames(...values: readonly (string | undefined)[]): string { + return values.filter((value): value is string => value !== undefined && value.length > 0).join(" "); +} + +function Heading({ + children, + className, + id, + level, +}: Readonly<{ + children: ReactNode; + className?: string; + id?: string; + level: MarketingHeadingLevel; +}>) { + const properties = { children, className, id }; + switch (level) { + case 1: return

    ; + case 2: return

    ; + case 3: return

    ; + case 4: return

    ; + case 5: return

    ; + case 6: return
    ; + } + throw new TypeError("Marketing heading level must be between one and six."); +} + +function childHeadingLevel(level: MarketingHeadingLevel): MarketingHeadingLevel { + return Math.min(level + 1, 6) as MarketingHeadingLevel; +} + +function MarketingActions({ actions }: Readonly<{ actions: readonly MarketingAction[] }>) { + if (actions.length === 0) return null; + return ( +
    + {actions.map((action, index) => ( + + {action.label} + + ))} +
    + ); +} + +export function MarketingFlow({ + ariaLabel, + className, + steps, +}: Readonly<{ + ariaLabel: string; + className?: string; + steps: readonly MarketingStep[]; +}>) { + return ( +
      + {steps.map((step, index) => ( +
    1. + +
      + {step.label} + {step.code === undefined + ? null + : {step.code}} + {step.detail === undefined + ? null + :

      {step.detail}

      } +
      +
    2. + ))} +
    + ); +} + +export function MarketingFacts({ + className, + facts, +}: Readonly<{ + className?: string; + facts: readonly MarketingFact[]; +}>) { + if (facts.length === 0) return null; + return ( +
    + {facts.map((fact) => ( +
    +
    {fact.label}
    +
    + {fact.value} + {fact.detail === undefined ? null : {fact.detail}} +
    +
    + ))} +
    + ); +} + +export interface ProductHeroProps { + readonly actions?: readonly MarketingAction[]; + readonly boundary?: string; + readonly className?: string; + readonly eyebrow: string; + readonly facts?: readonly MarketingFact[]; + readonly heading: string; + readonly headingId: string; + readonly headingLevel?: MarketingHeadingLevel; + readonly name: string; + readonly proof?: Readonly<{ + readonly content: ReactNode; + readonly heading: string; + readonly kicker?: string; + }>; + readonly summary: string; +} + +export function ProductHero({ + actions = [], + boundary, + className, + eyebrow, + facts = [], + heading, + headingId, + headingLevel = 1, + name, + proof, + summary, +}: Readonly) { + return ( +
    +
    +

    {eyebrow}

    +

    {name}

    + + {heading} + +

    {summary}

    + + {boundary === undefined + ? null + :

    {boundary}

    } +
    + {proof === undefined + ? null + : ( + + )} + +
    + ); +} + +export function MarketingInstallPanel({ + children, + className, + eyebrow, + heading, + headingId, + headingLevel = 2, + id, +}: Readonly<{ + children: ReactNode; + className?: string; + eyebrow: string; + heading: string; + headingId: string; + headingLevel?: MarketingHeadingLevel; + id?: string; +}>) { + return ( +
    +
    +

    {eyebrow}

    + + {heading} + +
    +
    {children}
    +
    + ); +} + +export function MarketingProofFrame({ + caption, + children, + className, + credit, +}: Readonly<{ + caption: string; + children: ReactNode; + className?: string; + credit?: string; +}>) { + return ( +
    +
    {children}
    +
    + {caption} + {credit === undefined ? null : {credit}} +
    +
    + ); +} + +export function MarketingSection({ + children, + className, + heading, + headingId, + headingLevel = 2, + id, + label, +}: Readonly<{ + children: ReactNode; + className?: string; + heading: string; + headingId: string; + headingLevel?: MarketingHeadingLevel; + id?: string; + label: string; +}>) { + return ( +
    +
    +

    {label}

    + + {heading} + +
    +
    {children}
    +
    + ); +} + +interface MarketingCollectionHeaderProps { + readonly heading: string; + readonly headingId: string; + readonly headingLevel: MarketingHeadingLevel; + readonly label: string; + readonly prefix: "interfaces" | "questions" | "trust"; + readonly summary: string | undefined; +} + +function MarketingCollectionHeader({ + heading, + headingId, + headingLevel, + label, + prefix, + summary, +}: Readonly) { + return ( +
    +

    {label}

    + + {heading} + + {summary === undefined ? null :

    {summary}

    } +
    + ); +} + +export interface MarketingInterface { + readonly example?: ReactNode; + readonly label: string; + readonly summary: string; +} + +export function MarketingInterfaceGrid({ + className, + heading, + headingId, + headingLevel = 2, + id, + interfaces, + label, + summary, +}: Readonly<{ + className?: string; + heading: string; + headingId: string; + headingLevel?: MarketingHeadingLevel; + id?: string; + interfaces: readonly MarketingInterface[]; + label: string; + summary?: string; +}>) { + return ( +
    + +
    + {interfaces.map((entry) => ( +
    + + {entry.label} + +

    {entry.summary}

    + {entry.example} +
    + ))} +
    +
    + ); +} + +export interface MarketingTrustItem { + readonly detail: string; + readonly label: string; +} + +export function MarketingTrustBoundary({ + className, + heading, + headingId, + headingLevel = 2, + id, + items, + label, + summary, +}: Readonly<{ + className?: string; + heading: string; + headingId: string; + headingLevel?: MarketingHeadingLevel; + id?: string; + items: readonly MarketingTrustItem[]; + label: string; + summary?: string; +}>) { + return ( +
    + +
    + {items.map((item) => ( +
    +
    {item.label}
    +
    {item.detail}
    +
    + ))} +
    +
    + ); +} + +export interface MarketingQuestion { + readonly answer: ReactNode; + readonly question: string; +} + +export function MarketingQuestionList({ + className, + heading, + headingId, + headingLevel = 2, + id, + label, + questions, + summary, +}: Readonly<{ + className?: string; + heading: string; + headingId: string; + headingLevel?: MarketingHeadingLevel; + id?: string; + label: string; + questions: readonly MarketingQuestion[]; + summary?: string; +}>) { + return ( +
    + +
    + {questions.map((question) => ( +
    + {question.question} +
    {question.answer}
    +
    + ))} +
    +
    + ); +} + +export function MarketingCallToAction({ + actions, + className, + eyebrow, + heading, + headingId, + headingLevel = 2, + id, + summary, +}: Readonly<{ + actions: readonly MarketingAction[]; + className?: string; + eyebrow: string; + heading: string; + headingId: string; + headingLevel?: MarketingHeadingLevel; + id?: string; + summary?: string; +}>) { + return ( +
    +

    {eyebrow}

    + + {heading} + + {summary === undefined ? null :

    {summary}

    } +
    + {actions.map((action, index) => ( + + {action.label} + + ))} +
    +
    + ); +} diff --git a/src/react/server.ts b/src/react/server.ts index 3e3ab38..fa90ec0 100644 --- a/src/react/server.ts +++ b/src/react/server.ts @@ -1,5 +1,6 @@ export * from "./particle-halo.js"; export * from "./procedural-backdrop.js"; export * from "./procedural-recipe.js"; +export * from "./product-marketing.js"; export * from "./surfaces.js"; export * from "./syntax-code.js"; diff --git a/src/styles.css b/src/styles.css index ee4f4f1..080f8a5 100644 --- a/src/styles.css +++ b/src/styles.css @@ -14,4 +14,5 @@ @import "./jelly.css"; @import "./plain-site.css"; @import "./plain-publication.css"; +@import "./product-marketing.css"; @import "./design-gallery.css"; From fa149efb16cd55705878df71c9ec468509adffd0 Mon Sep 17 00:00:00 2001 From: 0thernet Date: Tue, 1 Sep 2026 12:15:46 -0400 Subject: [PATCH 2/3] test: admit product marketing stylesheet --- scripts/check-stylex-artifacts.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/check-stylex-artifacts.ts b/scripts/check-stylex-artifacts.ts index baedde1..3075cd4 100644 --- a/scripts/check-stylex-artifacts.ts +++ b/scripts/check-stylex-artifacts.ts @@ -183,6 +183,7 @@ function requireAggregateContract(source: string): void { '@import "./jelly.css";', '@import "./plain-site.css";', '@import "./plain-publication.css";', + '@import "./product-marketing.css";', '@import "./design-gallery.css";', ]; const statements = topLevelStatements(source, "src/styles.css"); From 8cbc433631288046a081152596ad0bec2f2b57e5 Mon Sep 17 00:00:00 2001 From: 0thernet Date: Tue, 1 Sep 2026 12:19:31 -0400 Subject: [PATCH 3/3] build: refresh published bundles --- dist/{chunk-cdje4gw1.js => chunk-hh1k4jwx.js} | 524 +++++++++++++++++- dist/react/index.js | 82 ++- dist/react/server.js | 22 +- 3 files changed, 604 insertions(+), 24 deletions(-) rename dist/{chunk-cdje4gw1.js => chunk-hh1k4jwx.js} (59%) diff --git a/dist/chunk-cdje4gw1.js b/dist/chunk-hh1k4jwx.js similarity index 59% rename from dist/chunk-cdje4gw1.js rename to dist/chunk-hh1k4jwx.js index 8564551..d9e2859 100644 --- a/dist/chunk-cdje4gw1.js +++ b/dist/chunk-hh1k4jwx.js @@ -284,6 +284,486 @@ function ProceduralBackdrop({ }); } +// src/react/product-marketing.tsx +import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime"; +function classNames(...values) { + return values.filter((value) => value !== undefined && value.length > 0).join(" "); +} +function Heading({ + children, + className, + id, + level +}) { + const properties = { children, className, id }; + switch (level) { + case 1: + return /* @__PURE__ */ jsx2("h1", { + ...properties + }); + case 2: + return /* @__PURE__ */ jsx2("h2", { + ...properties + }); + case 3: + return /* @__PURE__ */ jsx2("h3", { + ...properties + }); + case 4: + return /* @__PURE__ */ jsx2("h4", { + ...properties + }); + case 5: + return /* @__PURE__ */ jsx2("h5", { + ...properties + }); + case 6: + return /* @__PURE__ */ jsx2("h6", { + ...properties + }); + } + throw new TypeError("Marketing heading level must be between one and six."); +} +function childHeadingLevel(level) { + return Math.min(level + 1, 6); +} +function MarketingActions({ actions }) { + if (actions.length === 0) + return null; + return /* @__PURE__ */ jsx2("div", { + className: "hraness-marketing-hero__actions", + children: actions.map((action, index) => /* @__PURE__ */ jsx2("a", { + className: "hraness-marketing-action", + "data-emphasis": action.emphasis ?? (index === 0 ? "primary" : "secondary"), + href: action.href, + children: action.label + }, `${action.href}-${action.label}`)) + }); +} +function MarketingFlow({ + ariaLabel, + className, + steps +}) { + return /* @__PURE__ */ jsx2("ol", { + "aria-label": ariaLabel, + className: classNames("hraness-marketing-flow", className), + "data-hraness-marketing": "flow", + children: steps.map((step, index) => /* @__PURE__ */ jsxs2("li", { + className: "hraness-marketing-flow__step", + children: [ + /* @__PURE__ */ jsx2("span", { + "aria-hidden": "true", + className: "hraness-marketing-flow__number", + children: String(index + 1).padStart(2, "0") + }), + /* @__PURE__ */ jsxs2("div", { + className: "hraness-marketing-flow__body", + children: [ + /* @__PURE__ */ jsx2("strong", { + className: "hraness-marketing-flow__label", + children: step.label + }), + step.code === undefined ? null : /* @__PURE__ */ jsx2("code", { + className: "hraness-marketing-flow__code", + children: step.code + }), + step.detail === undefined ? null : /* @__PURE__ */ jsx2("p", { + className: "hraness-marketing-flow__detail", + children: step.detail + }) + ] + }) + ] + }, `${String(index)}-${step.label}`)) + }); +} +function MarketingFacts({ + className, + facts +}) { + if (facts.length === 0) + return null; + return /* @__PURE__ */ jsx2("dl", { + className: classNames("hraness-marketing-facts", className), + "data-hraness-marketing": "facts", + children: facts.map((fact) => /* @__PURE__ */ jsxs2("div", { + children: [ + /* @__PURE__ */ jsx2("dt", { + children: fact.label + }), + /* @__PURE__ */ jsxs2("dd", { + children: [ + /* @__PURE__ */ jsx2("strong", { + children: fact.value + }), + fact.detail === undefined ? null : /* @__PURE__ */ jsx2("span", { + children: fact.detail + }) + ] + }) + ] + }, `${fact.label}-${fact.value}`)) + }); +} +function ProductHero({ + actions = [], + boundary, + className, + eyebrow, + facts = [], + heading, + headingId, + headingLevel = 1, + name, + proof, + summary +}) { + return /* @__PURE__ */ jsxs2("header", { + "aria-labelledby": headingId, + className: classNames("hraness-marketing-hero", className), + "data-hraness-marketing": "hero", + children: [ + /* @__PURE__ */ jsxs2("div", { + className: "hraness-marketing-hero__copy", + children: [ + /* @__PURE__ */ jsx2("p", { + className: "hraness-marketing-hero__eyebrow", + children: eyebrow + }), + /* @__PURE__ */ jsx2("p", { + className: "hraness-marketing-hero__name", + children: name + }), + /* @__PURE__ */ jsx2(Heading, { + className: "hraness-marketing-hero__heading", + id: headingId, + level: headingLevel, + children: heading + }), + /* @__PURE__ */ jsx2("p", { + className: "hraness-marketing-hero__summary", + children: summary + }), + /* @__PURE__ */ jsx2(MarketingActions, { + actions + }), + boundary === undefined ? null : /* @__PURE__ */ jsx2("p", { + className: "hraness-marketing-hero__boundary", + children: boundary + }) + ] + }), + proof === undefined ? null : /* @__PURE__ */ jsxs2("aside", { + className: "hraness-marketing-proof", + "aria-labelledby": `${headingId}-proof`, + children: [ + proof.kicker === undefined ? null : /* @__PURE__ */ jsx2("p", { + className: "hraness-marketing-proof__kicker", + children: proof.kicker + }), + /* @__PURE__ */ jsx2(Heading, { + className: "hraness-marketing-proof__heading", + id: `${headingId}-proof`, + level: childHeadingLevel(headingLevel), + children: proof.heading + }), + proof.content + ] + }), + /* @__PURE__ */ jsx2(MarketingFacts, { + facts + }) + ] + }); +} +function MarketingInstallPanel({ + children, + className, + eyebrow, + heading, + headingId, + headingLevel = 2, + id +}) { + return /* @__PURE__ */ jsxs2("section", { + "aria-labelledby": headingId, + className: classNames("hraness-marketing-install", className), + "data-hraness-marketing": "install", + id, + children: [ + /* @__PURE__ */ jsxs2("div", { + className: "hraness-marketing-install__heading-group", + children: [ + /* @__PURE__ */ jsx2("p", { + className: "hraness-marketing-install__eyebrow", + children: eyebrow + }), + /* @__PURE__ */ jsx2(Heading, { + className: "hraness-marketing-install__heading", + id: headingId, + level: headingLevel, + children: heading + }) + ] + }), + /* @__PURE__ */ jsx2("div", { + className: "hraness-marketing-install__commands", + children + }) + ] + }); +} +function MarketingProofFrame({ + caption, + children, + className, + credit +}) { + return /* @__PURE__ */ jsxs2("figure", { + className: classNames("hraness-marketing-proof-frame", className), + "data-hraness-marketing": "proof-frame", + children: [ + /* @__PURE__ */ jsx2("div", { + className: "hraness-marketing-proof-frame__content", + children + }), + /* @__PURE__ */ jsxs2("figcaption", { + className: "hraness-marketing-proof-frame__caption", + children: [ + /* @__PURE__ */ jsx2("span", { + children: caption + }), + credit === undefined ? null : /* @__PURE__ */ jsx2("small", { + children: credit + }) + ] + }) + ] + }); +} +function MarketingSection({ + children, + className, + heading, + headingId, + headingLevel = 2, + id, + label +}) { + return /* @__PURE__ */ jsxs2("section", { + "aria-labelledby": headingId, + className: classNames("hraness-marketing-section", className), + "data-hraness-marketing": "section", + id, + children: [ + /* @__PURE__ */ jsxs2("div", { + className: "hraness-marketing-section__heading-group", + children: [ + /* @__PURE__ */ jsx2("p", { + className: "hraness-marketing-section__label", + children: label + }), + /* @__PURE__ */ jsx2(Heading, { + className: "hraness-marketing-section__heading", + id: headingId, + level: headingLevel, + children: heading + }) + ] + }), + /* @__PURE__ */ jsx2("div", { + className: "hraness-marketing-section__body", + children + }) + ] + }); +} +function MarketingCollectionHeader({ + heading, + headingId, + headingLevel, + label, + prefix, + summary +}) { + return /* @__PURE__ */ jsxs2("header", { + className: `hraness-marketing-${prefix}__header`, + children: [ + /* @__PURE__ */ jsx2("p", { + className: `hraness-marketing-${prefix}__label`, + children: label + }), + /* @__PURE__ */ jsx2(Heading, { + className: `hraness-marketing-${prefix}__heading`, + id: headingId, + level: headingLevel, + children: heading + }), + summary === undefined ? null : /* @__PURE__ */ jsx2("p", { + children: summary + }) + ] + }); +} +function MarketingInterfaceGrid({ + className, + heading, + headingId, + headingLevel = 2, + id, + interfaces, + label, + summary +}) { + return /* @__PURE__ */ jsxs2("section", { + "aria-labelledby": headingId, + className: classNames("hraness-marketing-interfaces", className), + "data-hraness-marketing": "interfaces", + id, + children: [ + /* @__PURE__ */ jsx2(MarketingCollectionHeader, { + ...{ heading, headingId, headingLevel, label, summary }, + prefix: "interfaces" + }), + /* @__PURE__ */ jsx2("div", { + className: "hraness-marketing-interface-grid", + children: interfaces.map((entry) => /* @__PURE__ */ jsxs2("article", { + className: "hraness-marketing-interface", + children: [ + /* @__PURE__ */ jsx2(Heading, { + className: "hraness-marketing-interface__heading", + level: childHeadingLevel(headingLevel), + children: entry.label + }), + /* @__PURE__ */ jsx2("p", { + children: entry.summary + }), + entry.example + ] + }, entry.label)) + }) + ] + }); +} +function MarketingTrustBoundary({ + className, + heading, + headingId, + headingLevel = 2, + id, + items, + label, + summary +}) { + return /* @__PURE__ */ jsxs2("section", { + "aria-labelledby": headingId, + className: classNames("hraness-marketing-trust", className), + "data-hraness-marketing": "trust", + id, + children: [ + /* @__PURE__ */ jsx2(MarketingCollectionHeader, { + ...{ heading, headingId, headingLevel, label, summary }, + prefix: "trust" + }), + /* @__PURE__ */ jsx2("dl", { + className: "hraness-marketing-trust-grid", + children: items.map((item) => /* @__PURE__ */ jsxs2("div", { + className: "hraness-marketing-trust-item", + children: [ + /* @__PURE__ */ jsx2("dt", { + children: item.label + }), + /* @__PURE__ */ jsx2("dd", { + children: item.detail + }) + ] + }, item.label)) + }) + ] + }); +} +function MarketingQuestionList({ + className, + heading, + headingId, + headingLevel = 2, + id, + label, + questions, + summary +}) { + return /* @__PURE__ */ jsxs2("section", { + "aria-labelledby": headingId, + className: classNames("hraness-marketing-questions", className), + "data-hraness-marketing": "questions", + id, + children: [ + /* @__PURE__ */ jsx2(MarketingCollectionHeader, { + ...{ heading, headingId, headingLevel, label, summary }, + prefix: "questions" + }), + /* @__PURE__ */ jsx2("div", { + className: "hraness-marketing-question-list", + children: questions.map((question) => /* @__PURE__ */ jsxs2("details", { + className: "hraness-marketing-question", + children: [ + /* @__PURE__ */ jsx2("summary", { + children: question.question + }), + /* @__PURE__ */ jsx2("div", { + className: "hraness-marketing-question__answer", + children: question.answer + }) + ] + }, question.question)) + }) + ] + }); +} +function MarketingCallToAction({ + actions, + className, + eyebrow, + heading, + headingId, + headingLevel = 2, + id, + summary +}) { + return /* @__PURE__ */ jsxs2("section", { + "aria-labelledby": headingId, + className: classNames("hraness-marketing-cta", className), + "data-hraness-marketing": "cta", + id, + children: [ + /* @__PURE__ */ jsx2("p", { + className: "hraness-marketing-cta__eyebrow", + children: eyebrow + }), + /* @__PURE__ */ jsx2(Heading, { + className: "hraness-marketing-cta__heading", + id: headingId, + level: headingLevel, + children: heading + }), + summary === undefined ? null : /* @__PURE__ */ jsx2("p", { + className: "hraness-marketing-cta__summary", + children: summary + }), + /* @__PURE__ */ jsx2("div", { + className: "hraness-marketing-cta__actions", + children: actions.map((action, index) => /* @__PURE__ */ jsx2("a", { + className: "hraness-marketing-action", + "data-emphasis": action.emphasis ?? (index === 0 ? "primary" : "secondary"), + href: action.href, + children: action.label + }, `${action.href}-${action.label}`)) + }) + ] + }); +} + // src/react/surfaces.tsx import { forwardRef } from "react"; import * as stylex from "@stylexjs/stylex"; @@ -454,7 +934,7 @@ var layoutSurfaceStyles = { }; // src/react/surfaces.tsx -import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime"; +import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime"; var ditherSurfaceDensityStyles = { coarse: ditherSurfaceStyles.coarse, fine: ditherSurfaceStyles.fine, @@ -466,7 +946,7 @@ function DitherSurface({ xstyle, ...props2 }) { - return /* @__PURE__ */ jsx2(ThemedSurface, { + return /* @__PURE__ */ jsx3(ThemedSurface, { ...props2, className: cn2("hraness-design-dither-surface", className), "data-density": density, @@ -492,31 +972,31 @@ function TopBar({ const titlePresentation = stylex.props(layoutSurfaceStyles.topBarTitle); const contentPresentation = stylex.props(layoutSurfaceStyles.barPart, layoutSurfaceStyles.barContent); const actionsPresentation = stylex.props(layoutSurfaceStyles.barPart, layoutSurfaceStyles.topBarActions); - return /* @__PURE__ */ jsxs2("header", { + return /* @__PURE__ */ jsxs3("header", { ...rootPresentation, ...props2, className: cn2("hraness-design-top-bar", rootPresentation.className, className), "data-position": position, "data-surface": surface, children: [ - /* @__PURE__ */ jsxs2("div", { + /* @__PURE__ */ jsxs3("div", { ...leadingPresentation, className: cn2("hraness-design-top-bar__leading", leadingPresentation.className), children: [ leading, - title === undefined ? null : /* @__PURE__ */ jsx2("div", { + title === undefined ? null : /* @__PURE__ */ jsx3("div", { ...titlePresentation, className: cn2("hraness-design-top-bar__title", titlePresentation.className), children: title }) ] }), - children === undefined ? null : /* @__PURE__ */ jsx2("div", { + children === undefined ? null : /* @__PURE__ */ jsx3("div", { ...contentPresentation, className: cn2("hraness-design-top-bar__content", contentPresentation.className), children }), - actions === undefined ? null : /* @__PURE__ */ jsx2("div", { + actions === undefined ? null : /* @__PURE__ */ jsx3("div", { ...actionsPresentation, className: cn2("hraness-design-top-bar__actions", actionsPresentation.className), children: actions @@ -535,22 +1015,22 @@ function BottomBar({ const leadingPresentation = stylex.props(layoutSurfaceStyles.barPart); const contentPresentation = stylex.props(layoutSurfaceStyles.barPart, layoutSurfaceStyles.barContent); const actionsPresentation = stylex.props(layoutSurfaceStyles.barPart); - return /* @__PURE__ */ jsxs2("footer", { + return /* @__PURE__ */ jsxs3("footer", { ...rootPresentation, ...props2, className: cn2("hraness-design-bottom-bar", rootPresentation.className, className), children: [ - leading === undefined ? null : /* @__PURE__ */ jsx2("div", { + leading === undefined ? null : /* @__PURE__ */ jsx3("div", { ...leadingPresentation, className: cn2("hraness-design-bottom-bar__leading", leadingPresentation.className), children: leading }), - /* @__PURE__ */ jsx2("div", { + /* @__PURE__ */ jsx3("div", { ...contentPresentation, className: cn2("hraness-design-bottom-bar__content", contentPresentation.className), children }), - actions === undefined ? null : /* @__PURE__ */ jsx2("div", { + actions === undefined ? null : /* @__PURE__ */ jsx3("div", { ...actionsPresentation, className: cn2("hraness-design-bottom-bar__actions", actionsPresentation.className), children: actions @@ -567,7 +1047,7 @@ function PageCanvas({ }) { const Element = as; const presentation = stylex.props(layoutSurfaceStyles.pageCanvas, inset === "content" ? layoutSurfaceStyles.pageContentInset : layoutSurfaceStyles.pageNoInset, size === "wide" && layoutSurfaceStyles.wideSize, size === "full" && layoutSurfaceStyles.fullSize); - return /* @__PURE__ */ jsx2(Element, { + return /* @__PURE__ */ jsx3(Element, { ...presentation, ...props2, className: cn2("hraness-design-page-canvas", presentation.className, className), @@ -588,14 +1068,14 @@ var DockedFooter = forwardRef(function DockedFooter2({ }, ref) { const rootPresentation = stylex.props(layoutSurfaceStyles.surface, layoutSurfaceStyles.dockedFooter, position === "absolute" ? layoutSurfaceStyles.dockedAbsolute : position === "sticky" ? layoutSurfaceStyles.dockedSticky : layoutSurfaceStyles.dockedFixed); const contentPresentation = stylex.props(layoutSurfaceStyles.dockedContent, density === "compact" ? inset === "content" ? layoutSurfaceStyles.dockedContentCompactInset : layoutSurfaceStyles.dockedContentCompactNoInset : inset === "content" ? layoutSurfaceStyles.dockedContentDefaultInset : layoutSurfaceStyles.dockedContentDefaultNoInset, size === "wide" && layoutSurfaceStyles.wideSize, size === "full" && layoutSurfaceStyles.fullSize); - return /* @__PURE__ */ jsx2("footer", { + return /* @__PURE__ */ jsx3("footer", { ...rootPresentation, ...props2, className: cn2("hraness-design-docked-footer", rootPresentation.className, className), "data-position": position, "data-surface": surface, ref, - children: /* @__PURE__ */ jsx2("div", { + children: /* @__PURE__ */ jsx3("div", { ...contentPresentation, className: cn2("hraness-design-docked-footer__content", contentPresentation.className, contentClassName), "data-density": density, @@ -607,7 +1087,7 @@ var DockedFooter = forwardRef(function DockedFooter2({ }); // src/react/syntax-code.tsx -import { jsx as jsx3 } from "react/jsx-runtime"; +import { jsx as jsx4 } from "react/jsx-runtime"; function SyntaxCode({ className, code, @@ -615,7 +1095,7 @@ function SyntaxCode({ }) { const highlighted = highlightCode(code, language); const classes = className === undefined ? highlighted.className : `${highlighted.className} ${className}`; - return /* @__PURE__ */ jsx3("code", { + return /* @__PURE__ */ jsx4("code", { className: classes, "data-language": highlighted.language, dangerouslySetInnerHTML: { __html: highlighted.html } @@ -624,7 +1104,7 @@ function SyntaxCode({ // src/react/particle-halo.tsx import { cn as cn3 } from "@hraness/ui"; -import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime"; +import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime"; var colorVariables2 = { highlight: "var(--hraness-design-procedural-highlight)", key: "var(--hraness-design-procedural-key)", @@ -652,14 +1132,14 @@ function ParticleHalo({ "--hraness-design-procedural-shadow": recipe.palette.shadow, "--hraness-design-procedural-support": recipe.palette.support }; - return /* @__PURE__ */ jsxs3("div", { + return /* @__PURE__ */ jsxs4("div", { ...props2, className: cn3("hraness-design-particle-halo", className), "data-recipe-version": recipe.version, "data-variation": recipe.variation, style: rootStyle, children: [ - /* @__PURE__ */ jsx4("span", { + /* @__PURE__ */ jsx5("span", { "aria-hidden": "true", className: "hraness-design-particle-halo__particles", role: "presentation", @@ -675,13 +1155,13 @@ function ParticleHalo({ "--hraness-design-particle-x": `${particle.x}%`, "--hraness-design-particle-y": `${particle.y}%` }; - return /* @__PURE__ */ jsx4("i", { + return /* @__PURE__ */ jsx5("i", { className: "hraness-design-particle-halo__particle", style: particleStyle }, index); }) }), - /* @__PURE__ */ jsx4("div", { + /* @__PURE__ */ jsx5("div", { className: "hraness-design-particle-halo__content", children }) @@ -689,4 +1169,4 @@ function ParticleHalo({ }); } -export { proceduralBackdropVariants, proceduralRecipeVersion, createProceduralBackdropRecipe, createParticleHaloRecipe, ProceduralBackdrop, DitherSurface, TopBar, BottomBar, PageCanvas, DockedFooter, SyntaxCode, ParticleHalo }; +export { proceduralBackdropVariants, proceduralRecipeVersion, createProceduralBackdropRecipe, createParticleHaloRecipe, ProceduralBackdrop, MarketingFlow, MarketingFacts, ProductHero, MarketingInstallPanel, MarketingProofFrame, MarketingSection, MarketingInterfaceGrid, MarketingTrustBoundary, MarketingQuestionList, MarketingCallToAction, DitherSurface, TopBar, BottomBar, PageCanvas, DockedFooter, SyntaxCode, ParticleHalo }; diff --git a/dist/react/index.js b/dist/react/index.js index 0fc5579..cfad027 100644 --- a/dist/react/index.js +++ b/dist/react/index.js @@ -13,16 +13,26 @@ import { BottomBar, DitherSurface, DockedFooter, + MarketingCallToAction, + MarketingFacts, + MarketingFlow, + MarketingInstallPanel, + MarketingInterfaceGrid, + MarketingProofFrame, + MarketingQuestionList, + MarketingSection, + MarketingTrustBoundary, PageCanvas, ParticleHalo, ProceduralBackdrop, + ProductHero, SyntaxCode, TopBar, createParticleHaloRecipe, createProceduralBackdropRecipe, proceduralBackdropVariants, proceduralRecipeVersion -} from "../chunk-cdje4gw1.js"; +} from "../chunk-hh1k4jwx.js"; import"../chunk-djxa5bgc.js"; import { __require @@ -2740,6 +2750,7 @@ function ProductionDataPreviewNotice({ import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime"; var designGallerySections = [ { id: "foundation", label: "Foundation" }, + { id: "marketing", label: "Marketing" }, { id: "shells", label: "Shells" }, { id: "data", label: "Data" }, { id: "effects", label: "Effects" }, @@ -2764,6 +2775,7 @@ var designGalleryRecipeCoverage = [ "Jelly presentation", "playback transport", "plain site and publication grammar", + "product-marketing grammar", "Nebula Sans typography", "procedural effects", "production preview notice", @@ -2967,6 +2979,64 @@ function DesignSystemGallery({ }) ] }), + /* @__PURE__ */ jsxs11("section", { + className: "design-gallery__section", + id: "marketing", + children: [ + /* @__PURE__ */ jsx13("h2", { + children: "Product-marketing grammar" + }), + /* @__PURE__ */ jsxs11("div", { + className: "design-gallery__marketing", + children: [ + /* @__PURE__ */ jsx13(ProductHero, { + actions: [ + { href: "#gallery-install", label: "Install Relay" }, + { href: "#shells", label: "See the workspace" } + ], + boundary: "Local CLI · version 1.2.3 · sync optional", + className: "design-gallery__marketing-hero", + eyebrow: "Reference developer tool", + facts: [ + { detail: "One exact source.", label: "Input", value: "Repository" }, + { detail: "One inspectable result.", label: "Output", value: "Receipt" }, + { detail: "Terminal and typed code.", label: "Interfaces", value: "CLI + SDK" } + ], + heading: "Move one exact job across every interface.", + headingId: "design-gallery-marketing-title", + headingLevel: 3, + name: "Relay", + proof: { + content: /* @__PURE__ */ jsx13(MarketingFlow, { + ariaLabel: "First Relay job", + steps: [ + { code: "relay init", detail: "Create one exact workspace.", label: "Initialize" }, + { code: "relay run job-01", detail: "Run the named job.", label: "Execute" }, + { code: "relay inspect job-01", detail: "Read the resulting receipt.", label: "Inspect" } + ] + }), + heading: "One job, three observable transitions", + kicker: "Working model" + }, + summary: "The same owned job can be initialized, run, and inspected from a human or agent surface." + }), + /* @__PURE__ */ jsx13(MarketingInstallPanel, { + eyebrow: "Local release", + heading: "Install the verified tool.", + headingId: "design-gallery-install-title", + headingLevel: 3, + id: "gallery-install", + children: /* @__PURE__ */ jsx13("pre", { + className: "design-gallery__marketing-command", + children: /* @__PURE__ */ jsx13("code", { + children: "bun add --global relay@1.2.3" + }) + }) + }) + ] + }) + ] + }), /* @__PURE__ */ jsxs11("section", { className: "design-gallery__section", id: "shells", @@ -4184,12 +4254,22 @@ export { RailItem, RadarProfileChart, ProductionDataPreviewNotice, + ProductHero, ProceduralBackdrop, PlaybackTransport, PhaserDots, ParticleHalo, PageCanvas, NavigationRail, + MarketingTrustBoundary, + MarketingSection, + MarketingQuestionList, + MarketingProofFrame, + MarketingInterfaceGrid, + MarketingInstallPanel, + MarketingFlow, + MarketingFacts, + MarketingCallToAction, JellySurface, HAPTIC_FEEDBACK_EVENT_NAME, GlobalErrorDocument, diff --git a/dist/react/server.js b/dist/react/server.js index 5be1267..9623f0d 100644 --- a/dist/react/server.js +++ b/dist/react/server.js @@ -2,16 +2,26 @@ import { BottomBar, DitherSurface, DockedFooter, + MarketingCallToAction, + MarketingFacts, + MarketingFlow, + MarketingInstallPanel, + MarketingInterfaceGrid, + MarketingProofFrame, + MarketingQuestionList, + MarketingSection, + MarketingTrustBoundary, PageCanvas, ParticleHalo, ProceduralBackdrop, + ProductHero, SyntaxCode, TopBar, createParticleHaloRecipe, createProceduralBackdropRecipe, proceduralBackdropVariants, proceduralRecipeVersion -} from "../chunk-cdje4gw1.js"; +} from "../chunk-hh1k4jwx.js"; import"../chunk-djxa5bgc.js"; import"../chunk-5gtx3pza.js"; export { @@ -21,9 +31,19 @@ export { createParticleHaloRecipe, TopBar, SyntaxCode, + ProductHero, ProceduralBackdrop, ParticleHalo, PageCanvas, + MarketingTrustBoundary, + MarketingSection, + MarketingQuestionList, + MarketingProofFrame, + MarketingInterfaceGrid, + MarketingInstallPanel, + MarketingFlow, + MarketingFacts, + MarketingCallToAction, DockedFooter, DitherSurface, BottomBar