@hraness/design-kit 0.31.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARTICLE_COPY.md +208 -0
- package/HERO_FIELDS.md +55 -0
- package/ICONS.md +56 -0
- package/LANTERN_MATERIAL.md +60 -0
- package/LICENSE +21 -0
- package/MARKETING_COPY.md +49 -0
- package/MARKETING_PRESET.md +108 -0
- package/PALETTES.md +90 -0
- package/PAPER_THEME.md +128 -0
- package/README.md +745 -0
- package/STATUS_PAGES.md +67 -0
- package/STYLE.md +245 -0
- package/WRITING.md +72 -0
- package/dist/browser/index.js +2345 -0
- package/dist/chunk-44djxk16.js +6872 -0
- package/dist/chunk-5gtx3pza.js +9 -0
- package/dist/chunk-77391vmq.js +454 -0
- package/dist/chunk-7b1586eb.js +1209 -0
- package/dist/chunk-8834fh4n.js +6 -0
- package/dist/chunk-9t8xyqte.js +870 -0
- package/dist/chunk-cejpzyfh.js +446 -0
- package/dist/chunk-eh71jz57.js +429 -0
- package/dist/chunk-evb02bc1.js +516 -0
- package/dist/chunk-h4k7yv6x.js +368 -0
- package/dist/chunk-he8eznb1.js +760 -0
- package/dist/chunk-mr1vdcjq.js +404 -0
- package/dist/chunk-wzvdn8ey.js +116 -0
- package/dist/fonts/nebula-sans/social-fonts.generated.js +53 -0
- package/dist/icons.js +48 -0
- package/dist/index.js +237 -0
- package/dist/launch.js +41 -0
- package/dist/mockups/client.js +422 -0
- package/dist/mockups/index.js +1850 -0
- package/dist/portfolio.js +1549 -0
- package/dist/provider-marks.js +19 -0
- package/dist/react/charts.js +11 -0
- package/dist/react/hero-backdrop.js +8 -0
- package/dist/react/index.js +7252 -0
- package/dist/react/platform-install.js +10 -0
- package/dist/react/server.js +137 -0
- package/dist/stylex-manifest.json +1 -0
- package/dist/stylex.css +5392 -0
- package/dist/syntax-highlighting.js +15 -0
- package/dist/testing.js +250 -0
- package/package.json +377 -0
- package/portfolio-inventory.json +31 -0
- package/scripts/check-lantern-material-snapshot.d.mts +26 -0
- package/scripts/check-lantern-material-snapshot.mjs +126 -0
- package/scripts/check-marketing-snapshot.d.mts +12 -0
- package/scripts/check-marketing-snapshot.mjs +58 -0
- package/scripts/lantern-material-snapshot.ts +101 -0
- package/scripts/marketing-textures.ts +48 -0
- package/scripts/paper-theme-snapshot.ts +97 -0
- package/scripts/product-marketing-snapshot.ts +149 -0
- package/src/appearance-menu.css +266 -0
- package/src/appearance.ts +32 -0
- package/src/article-html.ts +269 -0
- package/src/article.ts +686 -0
- package/src/browser/appearance-menu.ts +604 -0
- package/src/browser/artifact-share.ts +164 -0
- package/src/browser/design-palette.ts +197 -0
- package/src/browser/foil.ts +124 -0
- package/src/browser/hero-light.ts +11 -0
- package/src/browser/index.ts +29 -0
- package/src/browser/status-field.ts +432 -0
- package/src/browser/status-page.ts +100 -0
- package/src/browser/sticky-offset.ts +82 -0
- package/src/browser/theme-color-sync.ts +213 -0
- package/src/charts.css +7 -0
- package/src/compiler-components.css +34 -0
- package/src/compiler-foundation.css +18 -0
- package/src/compiler-palettes.css +7 -0
- package/src/compiler-tokens.css +342 -0
- package/src/components.css +3 -0
- package/src/design-gallery.css +431 -0
- package/src/effects.css +14 -0
- package/src/fonts/geist-mono/GeistMono[wght].woff2 +0 -0
- package/src/fonts/geist-mono/OFL.txt +93 -0
- package/src/fonts/geist-mono/PROVENANCE.md +12 -0
- package/src/fonts/instrument-serif/OFL.txt +93 -0
- package/src/fonts/instrument-serif/UPSTREAM.md +14 -0
- package/src/fonts/instrument-serif/instrument-serif-latin-400.woff2 +0 -0
- package/src/fonts/nebula-sans/LICENSE.txt +96 -0
- package/src/fonts/nebula-sans/NebulaSans-Black.woff2 +0 -0
- package/src/fonts/nebula-sans/NebulaSans-BlackItalic.woff2 +0 -0
- package/src/fonts/nebula-sans/NebulaSans-Bold.otf +0 -0
- package/src/fonts/nebula-sans/NebulaSans-Bold.woff2 +0 -0
- package/src/fonts/nebula-sans/NebulaSans-BoldItalic.woff2 +0 -0
- package/src/fonts/nebula-sans/NebulaSans-Book.otf +0 -0
- package/src/fonts/nebula-sans/NebulaSans-Book.woff2 +0 -0
- package/src/fonts/nebula-sans/NebulaSans-BookItalic.woff2 +0 -0
- package/src/fonts/nebula-sans/NebulaSans-Light.woff2 +0 -0
- package/src/fonts/nebula-sans/NebulaSans-LightItalic.woff2 +0 -0
- package/src/fonts/nebula-sans/NebulaSans-Medium.woff2 +0 -0
- package/src/fonts/nebula-sans/NebulaSans-MediumItalic.woff2 +0 -0
- package/src/fonts/nebula-sans/NebulaSans-Semibold.woff2 +0 -0
- package/src/fonts/nebula-sans/NebulaSans-SemiboldItalic.woff2 +0 -0
- package/src/fonts/nebula-sans/PROVENANCE.md +14 -0
- package/src/fonts/nebula-sans/social-fonts.generated.ts +3873 -0
- package/src/fonts.css +106 -0
- package/src/icons/act60/act60.svg +5 -0
- package/src/icons/act60/application.svg +9 -0
- package/src/icons/act60/chapter-compare.svg +3 -0
- package/src/icons/act60/decree-residence.svg +6 -0
- package/src/icons/act60/estimate.svg +7 -0
- package/src/icons/act60/export-services.svg +4 -0
- package/src/icons/act60/investor.svg +13 -0
- package/src/icons/act60/presence.svg +7 -0
- package/src/icons/aicharts/aicharts.svg +6 -0
- package/src/icons/aicharts/cost-compare.svg +4 -0
- package/src/icons/aicharts/task-audio.svg +7 -0
- package/src/icons/aicharts/task-coding.svg +6 -0
- package/src/icons/aicharts/task-images.svg +3 -0
- package/src/icons/aicharts/task-reasoning.svg +9 -0
- package/src/icons/aicharts/task-research.svg +5 -0
- package/src/icons/aicharts/task-video.svg +4 -0
- package/src/icons/manifest.json +1371 -0
- package/src/icons/platonik/conversation.svg +3 -0
- package/src/icons/platonik/creature.svg +7 -0
- package/src/icons/platonik/economy.svg +4 -0
- package/src/icons/platonik/keep-light.svg +6 -0
- package/src/icons/platonik/platonik.svg +7 -0
- package/src/icons/platonik/spark-route.svg +15 -0
- package/src/icons/receipts/act60-retry.receipt.json +888 -0
- package/src/icons/receipts/act60.receipt.json +1366 -0
- package/src/icons/receipts/aicharts.receipt.json +1546 -0
- package/src/icons/receipts/platonik.receipt.json +1011 -0
- package/src/icons/receipts/roughday.receipt.json +770 -0
- package/src/icons/receipts/shared.receipt.json +1296 -0
- package/src/icons/receipts/slopcamera.receipt.json +1495 -0
- package/src/icons/receipts/soundfish-retry.receipt.json +460 -0
- package/src/icons/receipts/soundfish.receipt.json +1709 -0
- package/src/icons/receipts/sponge.receipt.json +1743 -0
- package/src/icons/receipts/stripe-history-retry.receipt.json +145 -0
- package/src/icons/receipts/stripe-history.receipt.json +1202 -0
- package/src/icons/receipts/wordcell.receipt.json +1924 -0
- package/src/icons/roughday/finance.svg +7 -0
- package/src/icons/roughday/roughday.svg +6 -0
- package/src/icons/roughday/technology.svg +4 -0
- package/src/icons/roughday/world.svg +4 -0
- package/src/icons/sets/act60.json +63 -0
- package/src/icons/sets/aicharts.json +63 -0
- package/src/icons/sets/platonik.json +39 -0
- package/src/icons/sets/roughday.json +25 -0
- package/src/icons/sets/shared.json +12 -0
- package/src/icons/sets/slopcamera.json +47 -0
- package/src/icons/sets/soundfish.json +43 -0
- package/src/icons/sets/sponge.json +51 -0
- package/src/icons/sets/stripe-history.json +39 -0
- package/src/icons/sets/wordcell.json +51 -0
- package/src/icons/shared/agent-skill.svg +4 -0
- package/src/icons/shared/cli.svg +5 -0
- package/src/icons/shared/privacy.svg +3 -0
- package/src/icons/shared/research.svg +7 -0
- package/src/icons/shared/sdk.svg +3 -0
- package/src/icons/slopcamera/diagram.svg +3 -0
- package/src/icons/slopcamera/direct-scene.svg +5 -0
- package/src/icons/slopcamera/edit-video.svg +17 -0
- package/src/icons/slopcamera/mcp.svg +3 -0
- package/src/icons/slopcamera/native-world.svg +3 -0
- package/src/icons/slopcamera/slopcamera.svg +11 -0
- package/src/icons/soundfish/agent-ui.svg +7 -0
- package/src/icons/soundfish/collaborate.svg +5 -0
- package/src/icons/soundfish/compose.svg +3 -0
- package/src/icons/soundfish/hear.svg +6 -0
- package/src/icons/soundfish/human-ui.svg +6 -0
- package/src/icons/soundfish/share.svg +3 -0
- package/src/icons/soundfish/soundfish.svg +6 -0
- package/src/icons/sponge/api.svg +3 -0
- package/src/icons/sponge/documents.svg +3 -0
- package/src/icons/sponge/library.svg +3 -0
- package/src/icons/sponge/one-question.svg +5 -0
- package/src/icons/sponge/people-direct.svg +5 -0
- package/src/icons/sponge/read.svg +3 -0
- package/src/icons/sponge/save.svg +4 -0
- package/src/icons/sponge/sponge.svg +7 -0
- package/src/icons/stripe-history/company-history.svg +3 -0
- package/src/icons/stripe-history/evidence.svg +4 -0
- package/src/icons/stripe-history/independence.svg +7 -0
- package/src/icons/stripe-history/publications.svg +11 -0
- package/src/icons/stripe-history/sources.svg +6 -0
- package/src/icons/stripe-history/stripe-history.svg +3 -0
- package/src/icons/wordcell/backlinks.svg +6 -0
- package/src/icons/wordcell/capture.svg +6 -0
- package/src/icons/wordcell/git-provenance.svg +4 -0
- package/src/icons/wordcell/kb.svg +4 -0
- package/src/icons/wordcell/markdown.svg +4 -0
- package/src/icons/wordcell/scopes.svg +3 -0
- package/src/icons/wordcell/search.svg +7 -0
- package/src/icons.generated.ts +734 -0
- package/src/icons.ts +51 -0
- package/src/index.ts +275 -0
- package/src/lantern-material.css +213 -0
- package/src/launch.ts +624 -0
- package/src/marketing-assets/UPSTREAM.md +18 -0
- package/src/marketing-assets/cells.svg +1 -0
- package/src/marketing-assets/grain.svg +1 -0
- package/src/marketing-forced-colors.css +12 -0
- package/src/mockups/client.tsx +415 -0
- package/src/mockups/core.tsx +318 -0
- package/src/mockups/frames.tsx +368 -0
- package/src/mockups/index.ts +14 -0
- package/src/mockups/surfaces.tsx +681 -0
- package/src/mockups.css +2607 -0
- package/src/palette-appearance.ts +45 -0
- package/src/palette-bridge.css +124 -0
- package/src/palette-color.ts +41 -0
- package/src/palette-system.css +721 -0
- package/src/palette-themes.ts +23 -0
- package/src/palette-tokens.stylex.ts +519 -0
- package/src/palettes.css +3 -0
- package/src/palettes.ts +161 -0
- package/src/paper-theme.css +244 -0
- package/src/plain-publication.css +1241 -0
- package/src/plain-site.css +397 -0
- package/src/platforms.ts +151 -0
- package/src/portfolio.generated.json +1595 -0
- package/src/portfolio.generated.ts +1596 -0
- package/src/portfolio.ts +226 -0
- package/src/product-marketing-foundation.css +433 -0
- package/src/product-marketing-preset.css +261 -0
- package/src/product-marketing.css +2186 -0
- package/src/provider-marks.generated.ts +112 -0
- package/src/provider-marks.ts +143 -0
- package/src/react/animated-rail-stage.stylex.ts +15 -0
- package/src/react/animated-rail-stage.tsx +51 -0
- package/src/react/app-shell.stylex.ts +104 -0
- package/src/react/app-shell.tsx +121 -0
- package/src/react/article.tsx +602 -0
- package/src/react/aurora-dots-background.tsx +34 -0
- package/src/react/charts.stylex.ts +563 -0
- package/src/react/charts.tsx +610 -0
- package/src/react/chat.stylex.ts +34 -0
- package/src/react/chat.tsx +155 -0
- package/src/react/design-gallery.tsx +943 -0
- package/src/react/design-palette.stylex.ts +124 -0
- package/src/react/design-palette.tsx +196 -0
- package/src/react/design-theme-context.tsx +41 -0
- package/src/react/effects.stylex.ts +325 -0
- package/src/react/fader.stylex.ts +71 -0
- package/src/react/fader.tsx +151 -0
- package/src/react/foil-card-math.ts +100 -0
- package/src/react/foil-card-surface.stylex.ts +326 -0
- package/src/react/foil-card-surface.tsx +742 -0
- package/src/react/foil-mark.tsx +35 -0
- package/src/react/foil.stylex.ts +163 -0
- package/src/react/haptics.ts +228 -0
- package/src/react/hero-backdrop.tsx +22 -0
- package/src/react/index.ts +38 -0
- package/src/react/keyboard-shortcuts.ts +228 -0
- package/src/react/lantern-material-gallery.tsx +166 -0
- package/src/react/lantern-material.stylex.ts +39 -0
- package/src/react/launch-beats.tsx +73 -0
- package/src/react/navigation-rail.stylex.ts +130 -0
- package/src/react/navigation-rail.tsx +228 -0
- package/src/react/particle-halo.tsx +115 -0
- package/src/react/phaser-dots.tsx +501 -0
- package/src/react/platform-icons.tsx +94 -0
- package/src/react/platform-install.stylex.ts +385 -0
- package/src/react/platform-install.tsx +389 -0
- package/src/react/playback-transport.stylex.ts +14 -0
- package/src/react/playback-transport.tsx +112 -0
- package/src/react/procedural-backdrop.tsx +192 -0
- package/src/react/procedural-recipe.ts +323 -0
- package/src/react/product-marketing.stylex.ts +4469 -0
- package/src/react/product-marketing.tsx +1611 -0
- package/src/react/production-data-preview-notice.stylex.ts +40 -0
- package/src/react/production-data-preview-notice.tsx +34 -0
- package/src/react/provider-mark.stylex.ts +132 -0
- package/src/react/provider-mark.tsx +120 -0
- package/src/react/relative-time.tsx +117 -0
- package/src/react/route-state.stylex.ts +39 -0
- package/src/react/route-state.tsx +350 -0
- package/src/react/server.ts +11 -0
- package/src/react/social-kit-panel.tsx +103 -0
- package/src/react/sticky-offset.tsx +33 -0
- package/src/react/surfaces.stylex.ts +179 -0
- package/src/react/surfaces.tsx +330 -0
- package/src/react/syntax-code.tsx +33 -0
- package/src/react/theme-color-sync.ts +1 -0
- package/src/react/theme-resolution.ts +11 -0
- package/src/react/theme.stylex.ts +276 -0
- package/src/react/theme.tsx +402 -0
- package/src/reading.css +72 -0
- package/src/relative-time.ts +185 -0
- package/src/reset.css +2 -0
- package/src/site-shell.css +16 -0
- package/src/status-page-html.ts +63 -0
- package/src/status-page.css +276 -0
- package/src/status-page.ts +323 -0
- package/src/styles.css +21 -0
- package/src/syntax-code-lexers.ts +303 -0
- package/src/syntax-highlighting.css +78 -0
- package/src/syntax-highlighting.ts +477 -0
- package/src/syntax-tokens.ts +33 -0
- package/src/testing.ts +307 -0
- package/src/tokens.css +3 -0
- package/src/typography.css +86 -0
- package/vendor/evilcharts/LICENSE +21 -0
- package/vendor/evilcharts/UPSTREAM.md +14 -0
- package/vendor/platform-marks/LICENSE +30 -0
- package/vendor/platform-marks/UPSTREAM.md +15 -0
- package/vendor/platform-marks/apple.svg +1 -0
- package/vendor/platform-marks/linux.svg +1 -0
- package/vendor/provider-marks/CRUSH-LICENSE.md +134 -0
- package/vendor/provider-marks/LICENSE +21 -0
- package/vendor/provider-marks/UPSTREAM.md +27 -0
- package/vendor/provider-marks/aider.svg +3 -0
- package/vendor/provider-marks/crush-heartbit.svg +8 -0
- package/vendor/provider-marks/lobehub/alibabacloud-color.svg +1 -0
- package/vendor/provider-marks/lobehub/alibabacloud.svg +1 -0
- package/vendor/provider-marks/lobehub/amp-color.svg +1 -0
- package/vendor/provider-marks/lobehub/amp.svg +1 -0
- package/vendor/provider-marks/lobehub/anthropic.svg +1 -0
- package/vendor/provider-marks/lobehub/claude-color.svg +1 -0
- package/vendor/provider-marks/lobehub/claude.svg +1 -0
- package/vendor/provider-marks/lobehub/claudecode-color.svg +1 -0
- package/vendor/provider-marks/lobehub/claudecode.svg +1 -0
- package/vendor/provider-marks/lobehub/codex-color.svg +1 -0
- package/vendor/provider-marks/lobehub/codex.svg +1 -0
- package/vendor/provider-marks/lobehub/cursor.svg +1 -0
- package/vendor/provider-marks/lobehub/deepseek-color.svg +1 -0
- package/vendor/provider-marks/lobehub/deepseek.svg +1 -0
- package/vendor/provider-marks/lobehub/devin-color.svg +1 -0
- package/vendor/provider-marks/lobehub/devin.svg +1 -0
- package/vendor/provider-marks/lobehub/gemini-color.svg +1 -0
- package/vendor/provider-marks/lobehub/gemini.svg +1 -0
- package/vendor/provider-marks/lobehub/geminicli-color.svg +1 -0
- package/vendor/provider-marks/lobehub/geminicli.svg +1 -0
- package/vendor/provider-marks/lobehub/goose.svg +1 -0
- package/vendor/provider-marks/lobehub/meta-color.svg +1 -0
- package/vendor/provider-marks/lobehub/meta.svg +1 -0
- package/vendor/provider-marks/lobehub/mistral-color.svg +1 -0
- package/vendor/provider-marks/lobehub/mistral.svg +1 -0
- package/vendor/provider-marks/lobehub/moonshot.svg +1 -0
- package/vendor/provider-marks/lobehub/nvidia-color.svg +1 -0
- package/vendor/provider-marks/lobehub/nvidia.svg +1 -0
- package/vendor/provider-marks/lobehub/openai.svg +1 -0
- package/vendor/provider-marks/lobehub/opencode.svg +1 -0
- package/vendor/provider-marks/lobehub/perplexity-color.svg +1 -0
- package/vendor/provider-marks/lobehub/perplexity.svg +1 -0
- package/vendor/provider-marks/lobehub/qwen-color.svg +1 -0
- package/vendor/provider-marks/lobehub/qwen.svg +1 -0
- package/vendor/provider-marks/lobehub/xai.svg +1 -0
- package/vendor/provider-marks/lobehub/zai.svg +1 -0
package/ARTICLE_COPY.md
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# Writing articles
|
|
2
|
+
|
|
3
|
+
The article components (`MarketingArticle`, `ArticleIndex`, and the static `renderArticleHtml` functions) give every Hraness host the same article shape. This guide says what goes in each part and which posts a host may publish. `STYLE.md` still owns voice and conventions, and `MARKETING_COPY.md` owns the product-page slots.
|
|
4
|
+
|
|
5
|
+
A reader arrives with one question. The title names it, the dek answers it in one sentence, and the body shows the evidence.
|
|
6
|
+
|
|
7
|
+
## Parts of an article
|
|
8
|
+
|
|
9
|
+
Limits are maximums. Count characters in the rendered text.
|
|
10
|
+
|
|
11
|
+
| Part | Write | Limit | Avoid |
|
|
12
|
+
| --- | --- | --- | --- |
|
|
13
|
+
| `heading` | The question or the result, in sentence case with no period. | 70 characters | Clickbait, a question the article does not answer, and a colon followed by a second title. |
|
|
14
|
+
| `dek` | One concrete claim, as a complete sentence, that a reader could check. | 200 characters | A summary of the article's sections, "In this post", and claims with no source in the body. |
|
|
15
|
+
| `eyebrow` | The series or post type: "Technique", "Release", "Integration". | Three words | A tagline. |
|
|
16
|
+
| `author` | The organization byline on every host: `{ kind: "organization", name: "Hraness" }`. | | A person's name on a post that person did not adopt. |
|
|
17
|
+
| `provenance` | The drafting kind and the review on record. | | Omitting it on an AI-drafted post. |
|
|
18
|
+
| `toc` | One entry per `h2`, only when the article has four or more. | Eight entries | Entries for `h3` headings. |
|
|
19
|
+
| Body headings | Sentence case, each naming what the section shows. | 70 characters | Signpost headings such as "Overview", "Conclusion", or "Key takeaways". |
|
|
20
|
+
| `ArticleSources` | Primary sources a reader can open, each with the date someone last checked the cited claim. | | Secondary summaries when the primary source is public. |
|
|
21
|
+
| `ArticleRelatedProducts` | Siblings from the registered relations for this article, each shown with its mark, name, and one-line description. | Three items | Every product in the portfolio. |
|
|
22
|
+
| `ArticleCallout` | A limit or warning the reader must see before acting. | One short paragraph | Decorative asides and restated body text. |
|
|
23
|
+
|
|
24
|
+
## Post shapes
|
|
25
|
+
|
|
26
|
+
### Introducing a product
|
|
27
|
+
|
|
28
|
+
Title: "Introducing <product>". It lives on the product's own host, and each active product gets one. The reader's job is to decide whether to try it, so the admission record says `readerJob: "decide whether to try it"`.
|
|
29
|
+
|
|
30
|
+
The post keeps five parts, written as beats:
|
|
31
|
+
|
|
32
|
+
1. **What it is.** One plain sentence, then the problem it solves in the reader's terms, with one concrete case.
|
|
33
|
+
2. **What it does today.** Only shipped behavior. One beat per surface or mode, and only when that beat has its own visual.
|
|
34
|
+
3. **How it works and who it is for.** How it works in the reader's terms, then the reader who has the problem and who should use something else.
|
|
35
|
+
4. **The vision.** Where the product is going and why, stated as intent, not as a shipped feature or a date.
|
|
36
|
+
5. **Limits and status.** What it does not do yet, then the status sentence and how to get it.
|
|
37
|
+
|
|
38
|
+
It explains the reason for the product. It does not repeat the feature grid on the home page. A paused product says so in the status sentence; a retired or legacy product gets no introduction.
|
|
39
|
+
|
|
40
|
+
#### Beats
|
|
41
|
+
|
|
42
|
+
A beat is one short section that makes one claim and shows one visual. Write 7 to 10 beats, about 40 to 60 words each, in this order:
|
|
43
|
+
|
|
44
|
+
| Part | Beats | Write |
|
|
45
|
+
| --- | --- | --- |
|
|
46
|
+
| `what` | 1 | What the product does, stated plainly. No question, teaser, or "here's why". |
|
|
47
|
+
| `does` | 1 or more | One surface or mode each, tied to one mockup state or clip. |
|
|
48
|
+
| `how` | 1 | How it works, in the reader's terms. |
|
|
49
|
+
| `who` | 1 | Who it is for, and who should use something else. |
|
|
50
|
+
| `vision` | 1 | Where it is going, as intent. |
|
|
51
|
+
| `limits` | 1 | What it does not do yet, once, at its true scope. |
|
|
52
|
+
| `status` | 1 | The status label and how to get it. |
|
|
53
|
+
|
|
54
|
+
- The hero holds the title, the registry `meta` as the dek, the status line from release data, and the film when there is one.
|
|
55
|
+
- Each beat has a headline of 70 characters or fewer, in sentence case with no period, and a post of 250 characters or fewer that works on its own as one social post.
|
|
56
|
+
- Social posts carry claims only. The `limits` beat, and any caveat, known gap, negative finding, audit or review line, or legally required disclaimer, stay in the launch post, the docs, and the site, and never reach a social post, the Show HN facts, or the Product Hunt fields. Every social post must still be true on its own: when a caveat is what keeps a beat's claim accurate, give the beat a `socialPost` that states a narrower claim accurately without it, or mark the beat `social: false`.
|
|
57
|
+
- Each beat has exactly one visual, and no two beats share one. Alt text is 125 characters or fewer. A mockup is labelled as an illustration in its alt text and caption.
|
|
58
|
+
- Render beats with `LaunchBeats` from `@hraness/design-kit/react`. Each beat gets a `#beat-<id>` anchor.
|
|
59
|
+
- After the beats: "Go deeper" links named for the reader's next task, then the social kit, then sources and the drafting and review note.
|
|
60
|
+
- Technical depth, comparisons with other products, and tables go in companion posts or `/compare` pages. Link them from the beat with `detailHref`.
|
|
61
|
+
|
|
62
|
+
#### Launch data
|
|
63
|
+
|
|
64
|
+
`@hraness/design-kit/launch` holds the shapes and checks. It has no React.
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
type LaunchBeat = {
|
|
68
|
+
id: string; // lowercase slug, rendered as #beat-<id>
|
|
69
|
+
part: "what" | "does" | "how" | "who" | "vision" | "limits" | "status";
|
|
70
|
+
headline: string; // may hold {fact} placeholders
|
|
71
|
+
post: string; // may hold {fact} placeholders
|
|
72
|
+
visual:
|
|
73
|
+
| { kind: "mockup"; id: string; state: Record<string, string> }
|
|
74
|
+
| { kind: "clip"; scene: string }
|
|
75
|
+
| { kind: "diagram"; src: string };
|
|
76
|
+
alt: string;
|
|
77
|
+
facts?: string[]; // every placeholder key the beat uses
|
|
78
|
+
detailHref?: string; // a companion post
|
|
79
|
+
socialPost?: string; // social wording when `post` keeps a caveat; accurate on its own
|
|
80
|
+
social?: boolean; // false keeps the beat in the launch post only
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
type LaunchFacts = Record<string, { value: string; source: string }>;
|
|
84
|
+
|
|
85
|
+
type SocialKit = {
|
|
86
|
+
x: string[];
|
|
87
|
+
bluesky: string[];
|
|
88
|
+
threads: string[];
|
|
89
|
+
linkedin: string;
|
|
90
|
+
productHunt: { tagline: string; description: string; tags: string[] };
|
|
91
|
+
showHnFacts: string[]; // facts for a person to write from
|
|
92
|
+
sources: Record<string, string>;
|
|
93
|
+
};
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
- **Facts.** Type every number once, in the product's `LaunchFacts` module, with its source: versions, sizes, rates, counts, and prices. The post, the social kit, film captions, and store listings all import it. `resolveLaunchBeats(beats, facts)` fills `{key}` placeholders and rejects a beat that types a digit by hand.
|
|
97
|
+
- **Checks.** `assertLaunchBeats(beats)` checks the order, the counts, the limits, and the wording rules. `buildSocialKit(beats, messaging, release, canonicalUrl)` builds each channel's posts from the social beats (every beat except `limits` and beats marked `social: false`, using `socialPost` when a beat has one), the Product Hunt fields from portfolio messaging, and the status from the release record. `assertLaunchKit(beats, kit, options)` checks each channel's length. It also checks that only the last post links, that the last post carries the status and the canonical URL without tracking parameters, that nothing asks the reader to install before a public install exists, that no other product is named, and that no post or fact carries the text of a `limits` beat or a `social: false` beat.
|
|
98
|
+
- **Social kit.** Build it only from an `indexable` post. A quarantined post has no social kit. Render it with `SocialKitPanel`, a closed `<details>` with a copy button and a character count per post. A person writes the Show HN post and the first Product Hunt comment; the kit gives them the facts.
|
|
99
|
+
- **Words.** Social posts follow the rules below, plus: no hashtags, no emoji, and no thread markers such as "1/n".
|
|
100
|
+
- **Admission.** A launch post passes the same admission gate as every other article. The launch helpers do not change it.
|
|
101
|
+
|
|
102
|
+
### How one product uses another
|
|
103
|
+
|
|
104
|
+
Title: "How <consumer> uses <provider>". It lives on the consumer's host.
|
|
105
|
+
|
|
106
|
+
1. **The reader's problem.** The job the consumer's user is trying to get done.
|
|
107
|
+
2. **What the provider does.** In plain words, for a reader who has never used it.
|
|
108
|
+
3. **How the consumer uses it.** The code path, configuration, or data flow, from the consumer's side.
|
|
109
|
+
4. **What that buys the user.** The concrete result a user of the consumer sees.
|
|
110
|
+
5. **Limits.** What the integration does not cover, once, at its true scope.
|
|
111
|
+
6. **Links.** The provider's home page and hub page, and the consumer's introduction.
|
|
112
|
+
|
|
113
|
+
- Write one only for a relation registered in `@hraness/design-kit/portfolio` that has a reviewed `detail` sentence. The post expands that sentence; it does not claim more.
|
|
114
|
+
- Outside its hub page, the provider's host adds at most one descriptive link to the post, and only after both pages are live.
|
|
115
|
+
- When the relation changes or is removed, update or archive the post in the same change.
|
|
116
|
+
|
|
117
|
+
### Provider hub page
|
|
118
|
+
|
|
119
|
+
A provider may keep one page, such as "Built on <provider>", that lists the products using it. Each entry is the registered relation's own sentence and a link to the consumer's "How <consumer> uses <provider>" post when one exists. The hub is an index. It does not restate each post.
|
|
120
|
+
|
|
121
|
+
### Technique posts in two halves
|
|
122
|
+
|
|
123
|
+
A technique post teaches one method, such as property tests, model checking, or proofs. It lives on hraness.com, and a product host may carry a product-specific version that shows the technique inside that product.
|
|
124
|
+
|
|
125
|
+
- **First half, for any reader.** Ground it in problems people already know, such as vibe-coded slop (software a model wrote quickly that looks finished and breaks on the second use) and fragile foundations (a product built on code nobody checked). Use one familiar failure ("It only breaks if you save, lose connection, then reopen"). Then describe the brighter alternative: what it is like when a whole class of that failure cannot happen. No code or notation in this half.
|
|
126
|
+
- **Second half, how Hraness does it.** Show the method with durable examples: laws, invariants, and small code excerpts that still read correctly after a refactor. Cite the repository file that holds the real example. Avoid line numbers and file names that change often, and keep internal jargon to the one term the technique needs.
|
|
127
|
+
- End on the last supported fact. State what the technique does not prove, once, at its true scope.
|
|
128
|
+
|
|
129
|
+
## Titles and formulas
|
|
130
|
+
|
|
131
|
+
- Keep titles to 70 characters or fewer. Most search results cut longer ones.
|
|
132
|
+
- The fixed formulas "Introducing <product>" and "How <consumer> uses <provider>" are allowed. Every other title in a series needs its own wording.
|
|
133
|
+
- Vary openings, closing sections, and headings across a series. Check the other posts before publishing: no repeated first sentence pattern, closing heading, closing checklist, or disclaimer paragraph.
|
|
134
|
+
|
|
135
|
+
## Words and numbers
|
|
136
|
+
|
|
137
|
+
- Follow `STYLE.md`. Do not use em dashes anywhere in the article, including the title, dek, captions, and alt text.
|
|
138
|
+
- Do not use these words: delve, tapestry, testament, landscape (for a market), realm, seamless, seamlessly, robust (without the specific property), leverage (as a verb), unlock, empower, elevate, supercharge, game-changer, cutting-edge, revolutionize, harness (as a verb), journey (for a process), and "in today's world".
|
|
139
|
+
- Keep internal vocabulary out of articles: admission, admitted, qualification, qualified, custody, settlement, receipt, attest, bounded, boundary, gate, lane, surface, projection, manifest, lease, and the other words `STYLE.md` lists under "Write for the reader, not the build". A technique post may define one of these once when it is the subject.
|
|
140
|
+
- Do not invent numbers, benchmarks, quotes, customers, or dates. Every number has a source or a date in the text. Every quote has a named person who agreed to it.
|
|
141
|
+
- Type each number once. Versions, sizes, rates, counts, and prices in a launch post come from the product's launch facts, not from the article text.
|
|
142
|
+
- State product status once with one of the `STYLE.md` labels: In development, Preview, Beta, Latest release: vX.Y.Z, Paused, or Retired.
|
|
143
|
+
|
|
144
|
+
## Dual-use products
|
|
145
|
+
|
|
146
|
+
Some products can be pointed at other people's accounts or data: authenticated browser adapters, automated messaging, exposure scans, clipping tools, and media import. Articles about them:
|
|
147
|
+
|
|
148
|
+
- describe the owner's own accounts and data, used with the owner's authorization;
|
|
149
|
+
- show previews, confirmation steps, and consent before anything is sent or changed;
|
|
150
|
+
- say what the product will not do;
|
|
151
|
+
- never frame the product as scraping, impersonation, or outreach at scale.
|
|
152
|
+
|
|
153
|
+
## Drafting and review note
|
|
154
|
+
|
|
155
|
+
Every article states who drafted it and who reviewed it, visibly, on every host. The owner decided this on 2026-09-23, as relayed to the task that added this guide. It applies to articles only; other pages follow `STYLE.md`.
|
|
156
|
+
|
|
157
|
+
- Render `ArticleProvenance`, or the `provenance` prop of `MarketingArticle`, from the admission record with `articleProvenanceFromAdmission()`. Do not type the sentence by hand.
|
|
158
|
+
- An AI-drafted post from repository sources reads: "Drafted with AI from the source code and reviewed by <reviewer>."
|
|
159
|
+
- Name the reviewer as it is. An AI reviewer has `reviewerType: "ai"` and a name that says it is AI, for example "Claude Opus 5.5 (claude-opus-5-5) editorial review". The sentence says "human" only when `reviewerType` is `human-editor`.
|
|
160
|
+
- Keep `humanReview` null unless a person reviewed the article. Never describe AI review as human review.
|
|
161
|
+
- The byline is "Hraness" on every host. Do not credit a person, including the owner, for an AI-drafted post. A person who later adopts a post may switch the byline to their own name; the drafting and review note stays.
|
|
162
|
+
- A post with no review on record renders "It has not been reviewed yet." and stays quarantined.
|
|
163
|
+
|
|
164
|
+
## Admission and indexing
|
|
165
|
+
|
|
166
|
+
Every article URL has an `ArticleAdmission` record in its host's registry, checked by `assertArticleAdmissions()` in tests.
|
|
167
|
+
|
|
168
|
+
- A new post starts `quarantined`: readable, `noindex`, and absent from the sitemap, feeds, `llms.txt`, and index lists.
|
|
169
|
+
- It becomes `indexable` only when the record passes: six scores of 0 to 2 totalling at least 9 with no zero, a review with reviewer and `reviewerType`, at least one source with a check date, two observations that are not paraphrases of the sources, and at least one refresh trigger.
|
|
170
|
+
- An independent, disclosed AI review can admit a post. Record it as `reviewerType: "ai"`. The validator rejects an AI reviewer name that does not say it is AI (for example by naming the model), and it rejects `reviewerType: "author"` for an indexable post, because the author cannot admit their own post.
|
|
171
|
+
- `archived` keeps an old URL working without listing it.
|
|
172
|
+
|
|
173
|
+
## Freshness
|
|
174
|
+
|
|
175
|
+
- Every record has `reassessOn`, 28 to 56 days after `review.reviewedOn`. `articleAdmissionsDue(registry, today)` lists the records to look at again.
|
|
176
|
+
- Render versions, release dates, and status labels from release data (`package.json`, a published release file, or the portfolio facts). Never type a version into an article body.
|
|
177
|
+
- Each record names its refresh triggers: a release tag bump, a change to a relation's `detail`, a rename, or a change to the feature the post describes.
|
|
178
|
+
- When a product is renamed, update article bodies, titles, and slugs (with redirects) in the same change as the rename.
|
|
179
|
+
- When a feature an article describes changes, refresh the article or archive it. Update the `updated` date only when the content changed.
|
|
180
|
+
|
|
181
|
+
## Interlinking
|
|
182
|
+
|
|
183
|
+
- Add a link only when it is the reader's next useful action. Use a descriptive anchor.
|
|
184
|
+
- Follow registered relations. Do not link every product to every other product.
|
|
185
|
+
- No minimum link counts, reciprocal link swaps, or footer link farms.
|
|
186
|
+
- Every article stands on its own. A reader who follows no link still gets the answer.
|
|
187
|
+
- Link to another host only after both URLs are live.
|
|
188
|
+
|
|
189
|
+
## Check before publishing
|
|
190
|
+
|
|
191
|
+
Read the title, dek, and first paragraph aloud. Open every source and confirm the cited claim on the check date. Render the article at 1280px and 375px wide; code blocks scroll sideways and tables stay readable on a phone. Run `assertArticleAdmissions()` on the registry.
|
|
192
|
+
|
|
193
|
+
## Block for product repositories
|
|
194
|
+
|
|
195
|
+
Paste this block into the `AGENTS.md` of every product repository that publishes articles. Keep the markers so the block can be synced.
|
|
196
|
+
|
|
197
|
+
```md
|
|
198
|
+
<!-- hraness-articles:start -->
|
|
199
|
+
## Articles
|
|
200
|
+
|
|
201
|
+
- Take product names, one-liners, URLs, status, and relations from `@hraness/design-kit/portfolio`. Do not copy them into article text by hand.
|
|
202
|
+
- Write "How <consumer> uses <provider>" only for a relation registered there with a `detail` sentence. Change the relation and the post in the same change.
|
|
203
|
+
- Give every post an `ArticleAdmission` record with a `reassessOn` date 28 to 56 days after its review, and keep `assertArticleAdmissions()` in the tests.
|
|
204
|
+
- Render versions, release dates, and status from release data. Never type a version into an article.
|
|
205
|
+
- When a product is renamed, update article bodies, titles, and slugs in the same change. When a feature an article describes changes, refresh or archive the article.
|
|
206
|
+
- Follow `ARTICLE_COPY.md` in `@hraness/design-kit` for article shapes, the drafting and review note, and interlinking.
|
|
207
|
+
<!-- hraness-articles:end -->
|
|
208
|
+
```
|
package/HERO_FIELDS.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Hero fields
|
|
2
|
+
|
|
3
|
+
Hero backdrops are retired. Since 0.23.0, marketing heroes follow the Quiet
|
|
4
|
+
direction: a flat palette background, a sans heading, a short summary, one next
|
|
5
|
+
step, and one real proof of the product right after the copy. Nothing moves,
|
|
6
|
+
glows, or blurs behind the text.
|
|
7
|
+
|
|
8
|
+
```tsx
|
|
9
|
+
<ProductHero
|
|
10
|
+
eyebrow="Job runner"
|
|
11
|
+
heading="Run a job from your terminal, your code, or your agent"
|
|
12
|
+
headingId="title"
|
|
13
|
+
name="Relay"
|
|
14
|
+
summary="Relay runs the same job wherever you start it and writes a log you can read afterward."
|
|
15
|
+
actions={[{ href: "#install", label: "Install Relay" }]}
|
|
16
|
+
boundary="MIT license · macOS and Linux"
|
|
17
|
+
frame={<MarketingProofFrame title="relay run job-01">{terminalOutput}</MarketingProofFrame>}
|
|
18
|
+
/>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Put real proof in `frame`: a terminal or code block, a static screenshot with
|
|
22
|
+
`alt` text, or compact real output. It stays available to readers and
|
|
23
|
+
assistive technology. Do not put a large interactive copy of the application in
|
|
24
|
+
the hero.
|
|
25
|
+
|
|
26
|
+
When the product has no single screen to capture, `frame` may hold one static
|
|
27
|
+
mockup from `@hraness/design-kit/mockups`, or a `ModeShowcase` from
|
|
28
|
+
`@hraness/design-kit/mockups/client` that lets the reader switch between a few
|
|
29
|
+
states of the same illustration. Both need a caption that says the picture is
|
|
30
|
+
an illustration; `ModeShowcase` throws without one. Mockups render as a single
|
|
31
|
+
image with a text description, carry no headings, and are skipped by search
|
|
32
|
+
snippets. Load `@hraness/design-kit/mockups.css` with them.
|
|
33
|
+
|
|
34
|
+
## Retired features
|
|
35
|
+
|
|
36
|
+
These names stay exported so existing code compiles. They do nothing visible,
|
|
37
|
+
and their types are marked deprecated. Remove them when convenient.
|
|
38
|
+
|
|
39
|
+
| Feature | Behavior since 0.23.0 |
|
|
40
|
+
| --- | --- |
|
|
41
|
+
| `ProductHero` `backdrop` prop | Ignored. `backdrop={false}`, artwork, and omission all render the same hero with no decorative layer. |
|
|
42
|
+
| `HeroBackdrop` from `@hraness/design-kit/react` or `@hraness/design-kit/react/hero-backdrop` | Renders nothing, including its children. |
|
|
43
|
+
| `attachHeroLight` from `@hraness/design-kit/browser` | Attaches no listeners, writes no styles, schedules no frames, and returns a disposer you can call any number of times. |
|
|
44
|
+
| `--hraness-hero-light-x`, `--hraness-hero-light-y`, `--hraness-hero-drift-x`, `--hraness-hero-drift-y`, `--hraness-hero-proximity` | No longer written or read by any shared style. |
|
|
45
|
+
| `data-hraness-hero-item` | No proximity effect. Product artwork marked with it no longer renders inside the hero. |
|
|
46
|
+
| `.hraness-marketing-hero-backdrop` and its `__atmosphere` and `__light` children in hand-written HTML | Hidden with `display: none` by `product-marketing.css` and the compiler foundation, so old markup paints nothing and takes no space. |
|
|
47
|
+
| `data-hraness-pattern="cells"`, `"weave"`, `"contour"`, `"mesh"` | Still valid; each renders exactly like `"none"`. See [Marketing preset](MARKETING_PRESET.md). |
|
|
48
|
+
|
|
49
|
+
A product that still passes decorative artwork should delete it rather than
|
|
50
|
+
recreate it in product CSS. Floating or blurred elements behind the heading make
|
|
51
|
+
it harder to read, especially on phones.
|
|
52
|
+
|
|
53
|
+
`ProductHero` stays available from the server entry and no longer crosses a
|
|
54
|
+
client boundary. The `@hraness/design-kit/react/hero-backdrop` export remains a
|
|
55
|
+
client module for compatibility.
|
package/ICONS.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Icons
|
|
2
|
+
|
|
3
|
+
`@hraness/design-kit/icons` is the shared vetted illustration and mark library
|
|
4
|
+
for hraness.com surfaces. Every icon is generated as a member of a measured
|
|
5
|
+
family — never alone — by Slopcamera, then admitted to the package by hash and
|
|
6
|
+
metric bounds.
|
|
7
|
+
|
|
8
|
+
## Using icons
|
|
9
|
+
|
|
10
|
+
Two distributions ship from the same canonical artwork:
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { hranessIconMarkup, hranessIcons } from "@hraness/design-kit/icons";
|
|
14
|
+
|
|
15
|
+
const html = hranessIconMarkup("shared/research"); // complete inline <svg>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
or the raw file, for `<img>` sources and static builds:
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import researchUrl from "@hraness/design-kit/icons/shared/research.svg";
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Each asset carries `viewBox`, sanitized inner `body`, `ink`, `context`
|
|
25
|
+
(`card` | `hero` | `inline`), `purpose` (`illustration` | `mark`), `set`, and
|
|
26
|
+
`subject`.
|
|
27
|
+
|
|
28
|
+
## Adding or regenerating a set
|
|
29
|
+
|
|
30
|
+
Sets are authored in `src/icons/sets/<name>.json` and are also the exact input
|
|
31
|
+
Slopcamera consumes:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
slopcamera image icon --set src/icons/sets/sponge.json \
|
|
35
|
+
--output-dir artifacts/icons/sponge --candidates 2 --set-rounds 3
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Copy the reviewed SVGs into `src/icons/<set>/` and the set receipt into
|
|
39
|
+
`src/icons/receipts/`, then run:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
bun run generate:icons # measures artwork and rewrites manifest + module
|
|
43
|
+
bun run check:icons # CI form: verifies instead of writing
|
|
44
|
+
bun test src/icons.test.ts
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Admission is fail-closed: single ink, bounded bytes and vector detail,
|
|
48
|
+
context-shaped density and stroke bands, and per-set family coherence
|
|
49
|
+
(each illustration within ±45% coverage and ±50% stroke of its set median;
|
|
50
|
+
marks are excluded from family coherence since they are product-authored).
|
|
51
|
+
A manifest member without artwork is listed under `pending`, never silently
|
|
52
|
+
omitted.
|
|
53
|
+
|
|
54
|
+
Product marks are declared on a set's `marks` field: they are vetted and
|
|
55
|
+
distributed through the same registry but are product-authored content that
|
|
56
|
+
a `--set` regeneration run never rewrites.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Lantern material
|
|
2
|
+
|
|
3
|
+
Lantern adds luminous edges, palette-tinted selection states and restrained depth to the shared component system. It is independent of the selected palette, the Paper theme and editorial typography. The default application treatment is quiet. Glazing belongs at key transitions and on chrome with real content behind it.
|
|
4
|
+
|
|
5
|
+
Opacity-only color mixes use sRGB so translucent surfaces keep the source color's hue. Blends between two colored paints use OKLCH. The native browser checks compare translucent paint with an independent RGB and alpha reference in both appearances.
|
|
6
|
+
|
|
7
|
+
The complete `styles.css` and `compiler-foundation.css` entries include the material. For a selective import, load `@hraness/design-kit/lantern-material.css` after the existing foundation and palette styles, then set `data-hraness-material="lantern"` on the document or an explicit theme island. The stylesheet has no JavaScript, font, remote URL or inline-style dependency. Existing controls continue to own their semantics, dimensions, state and accessible names.
|
|
8
|
+
|
|
9
|
+
| Hook | Purpose |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| `.hraness-material-pane` | Opaque, readable content plane. Optional `data-depth="raised"` or `"inset"` describes actual depth. |
|
|
12
|
+
| `.hraness-material-chrome` | Diffused header or floating enclosure. It becomes opaque without backdrop support or when reduced transparency is requested. |
|
|
13
|
+
| `.hraness-material-wall` | A flat palette plane behind an area. Its shaded faces, grain, patterns, and light washes are retired and no longer paint. It never filters or overlays descendants. |
|
|
14
|
+
| `.hraness-material-control` | Plain HTML action adapter. A narrow edge leaves the established foreground/background pair and focus shadow intact. |
|
|
15
|
+
| `.hraness-material-input` | Plain HTML field adapter: inset edge paint and a semantic caret. |
|
|
16
|
+
| `.hraness-material-choice` | Plain HTML selection with a matched fill/text pair, driven by `aria-pressed`, `aria-selected` or React Aria's `data-selected`. |
|
|
17
|
+
| `.hraness-material-terminal` | A quiet window enclosure; its `__bar` is decorative title chrome. |
|
|
18
|
+
| `.hraness-material-code` | A theme-aware `pre` with intact source lines and horizontal overflow. Use `SyntaxCode` or framework-neutral `highlightCode` for syntax; give an overflowing example a keyboard focus target. |
|
|
19
|
+
| `.hraness-material-rows` | An unruled readable collection. The product owns grouping, separators, and responsive structure. |
|
|
20
|
+
| `.hraness-material-disclosure` | Native disclosure with a touch-sized summary and visible keyboard focus. |
|
|
21
|
+
|
|
22
|
+
The wall and marketing fields accept `data-hraness-pattern="cells"`, `"weave"`, `"contour"`, `"mesh"` or `"none"` on their boundary for compatibility. Patterns are retired: every value paints the uninterrupted palette background, and the material-only snapshot needs no assets.
|
|
23
|
+
|
|
24
|
+
Panes use a resting shadow by default. Raised panes use a broader lift, while inset panes use an inner shadow and a quieter fill. Their transparent borders preserve geometry; forced colors replaces those edges with `CanvasText`. Chrome uses a diffused shadow rather than a drawn divider. `--hraness-material-rest`, `--hraness-material-raised`, `--hraness-material-lift`, `--hraness-material-inset` and `--hraness-material-outline` are the shared depth roles. `--hraness-material-raised` is the small-control counterpart of the resting shadow. The UI adapters leave error borders and keyboard focus under the control's ownership.
|
|
25
|
+
|
|
26
|
+
Material tokens resolve inside each marked island. Mark a nested theme island separately. A portalled overlay must carry the complete palette class/attributes, resolved light/dark mode and `data-hraness-material="lantern"` on its actual host (or inherit them from a corresponding wrapper). The marker and material hook can live on the same element. A material class alone cannot transport context across a portal.
|
|
27
|
+
|
|
28
|
+
For `@hraness/ui` primitives, use the compiled `lanternControlStyles` exported from `@hraness/design-kit/react` through their existing `controlXstyle` prop. Use `edge` on actions, `inset` on a text field's control, and conditionally append `selected` for a real selected state. Do not apply the plain HTML classes to primitive wrappers or controls. The compiled recipes leave the primitive's focus outline, focus shadow, dimensions, error and disabled behavior intact. They work through either the standalone package stylesheet or the final compiler union.
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
<Button
|
|
32
|
+
aria-pressed={selected}
|
|
33
|
+
controlXstyle={[lanternControlStyles.edge, selected && lanternControlStyles.selected]}
|
|
34
|
+
onPress={() => setSelected(!selected)}
|
|
35
|
+
>
|
|
36
|
+
Personal only
|
|
37
|
+
</Button>
|
|
38
|
+
<TextField label="Find a note" controlXstyle={lanternControlStyles.inset} />
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`ThemeMenuButton` and `ThemeToggle`'s menu presentation need no adapter. Inside a Lantern island the trigger rests on the material plane with a seam border and the raised shadow, warms on hover, sets into the inset shadow while pressed, and keeps its focus ring and outline. Its menu uses the material plane and lift, and the selected appearance uses the warm plane. The palette appearance menu that `ThemeMenuButton` renders inside `DesignPaletteProvider` follows the same rules. Outside Lantern every value falls back to the palette, so other products see no change. Forced colors removes the shadows and uses system colors; reduced motion removes the transition. The menu is portalled, so put `data-hraness-material="lantern"` on the document element (or the portal host) when the header is Lantern. Remove site CSS that restyles `.hraness-design-theme-toggle__trigger`, `.hraness-design-theme-toggle__popover`, or `.hraness-design-theme-toggle__item` for Lantern.
|
|
42
|
+
|
|
43
|
+
`TopBar` accepts the chrome class with `surface="glass"`; the material binds its public background/backdrop tokens. Its `--hraness-design-top-bar-edge` token resolves to the material outline, including an explicit system-color edge in forced colors. The same class also supports a native header. Use sticky chrome only where content actually scrolls behind it.
|
|
44
|
+
|
|
45
|
+
Older applications can vendor a five-file snapshot without upgrading their primitive peer graph:
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
bun scripts/lantern-material-snapshot.ts --write DEST --source-commit FULL_SHA
|
|
49
|
+
node DEST/check.mjs DEST
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Run the installer from an immutable design-kit checkout and supply its full commit. The snapshot contains CSS, license, a Node checker and declaration, and provenance. It has no fonts or bitmap dependencies. Keep its checker in the consuming application's normal validation gate.
|
|
53
|
+
|
|
54
|
+
Keep body text on opaque planes. Use square modules for alignment and proportion without forcing content into square cards. Collections need a bounded readable measure, a clear grouping rule and progressive disclosure of secondary details. Availability, errors and consequential limits remain visible before a user acts. One section should have one clear primary next step.
|
|
55
|
+
|
|
56
|
+
Warm material states do not replace success, warning, danger or keyboard-focus meaning. Reduced motion removes material transitions. Reduced transparency removes glazing. Forced colors replaces material paint with system colors and keeps selected states explicit.
|
|
57
|
+
|
|
58
|
+
The architectural reference is Maison Hermès in Tokyo. RPBW describes textured translucent glass, curved corner blocks and a façade that shifts from cool daylight to warm interior illumination. Hermès describes a repeated square module extending into the flooring and shelving. These are principles of material and organization, not a license to cover every control with a façade image.
|
|
59
|
+
|
|
60
|
+
Sources: [RPBW project](https://www.rpbw.com/project/maison-hermes), [Hermès: The magic of the squares](https://lanterne.hermes.com/en/secrets/secret2412/).
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 hraness/design-kit contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Writing for the marketing components
|
|
2
|
+
|
|
3
|
+
The product-marketing components give every Hraness site the same page shape. This guide says what each slot is for, so the shape does not also produce the same prose. `STYLE.md` still owns voice and conventions; this file covers the slots.
|
|
4
|
+
|
|
5
|
+
A reader arrives asking "What is this, and is it for me?" Answer that in the hero, show evidence next, and keep build details for the documentation.
|
|
6
|
+
|
|
7
|
+
## Slots
|
|
8
|
+
|
|
9
|
+
Limits are maximums. Count characters in the rendered text.
|
|
10
|
+
|
|
11
|
+
| Slot | Write | Limit | Avoid |
|
|
12
|
+
| --- | --- | --- | --- |
|
|
13
|
+
| `ProductHero` `heading` | One sentence that says what the reader can do with the product. | 60 characters | Verbless slogans ("All your subscriptions. One router."), lists of three verbs, and claims a competitor could publish unchanged. |
|
|
14
|
+
| `ProductHero` `summary` | One or two sentences: what it is, who it is for, and the mechanism in the reader's words. | 240 characters | Stacking four mechanisms into one clause; "the first" or "the only" without a source. |
|
|
15
|
+
| `eyebrow` | The product's category from the portfolio registry, or nothing. | Four words | A tagline or a second headline. |
|
|
16
|
+
| `boundary` | Price or license, requirements, and release state, once and plainly: "Free and open source · Needs Rust 1.85 or newer · Latest release: v0.2.1". | 110 characters | Governance terms such as "verified release", "admitted", "qualified", or "source pilot". |
|
|
17
|
+
| `notice` | Status only when it changes what the reader can do today. | One sentence | A second copy of the boundary, or caveats a reader cannot act on. |
|
|
18
|
+
| `MarketingProofFrame` | Output a reader can reproduce with the current release. Put the capture date in `credit` and label a historical record as historical. Set `chrome` to `window`, `browser` (with the page's `url`), or `terminal` to match what was captured. | | Commands or flags the current release rejects; "Live qualification" as a label; a browser address the product does not serve. |
|
|
19
|
+
| Mockups from `@hraness/design-kit/mockups` | An illustration of one state of the product, with a `describe` sentence that starts "Illustration of" and a caption that says it is an illustration. Invented names, handles, and addresses on reserved example domains. | `describe`: one sentence | Real people's names or handles, headings inside the picture, and a mockup presented as a screenshot. |
|
|
20
|
+
| `MarketingDataTable` `caption` | The measurement's name: what was run, on what. | 70 characters | A verdict disguised as a caption. |
|
|
21
|
+
| `MarketingDataTable` `meta`, `note` | The date, source, or sample size in `meta`; the caveat that bounds the claim in `note`. | `note`: 160 characters | Data that outlives its caveat, or a table presented as a guarantee. |
|
|
22
|
+
| `MarketingDataTable` columns | Short unit-bearing labels. Numbers go in `numeric` columns; the first column names each row. | | Wide dumps that belong in documentation. |
|
|
23
|
+
| `MarketingCodeBlock` | Commands and output a reader can copy and run on the current release. Keep lines inside about 32 characters on a phone. | | Prompts, placeholders, or output the release cannot produce. |
|
|
24
|
+
| `MarketingPillars`, `MarketingPrimitives` | Two to four items. Labels name a thing or state a fact ("A log for every run"); each summary is one sentence. | Label: five words. Summary: 120 characters | Adjective triads ("Fast / Legible / Yours") and abstract nouns ("durable objects"). |
|
|
25
|
+
| Section `heading` | Sentence case. A full sentence in the editorial preset may end with a period; other headings do not. | 70 characters | Slogans and headings that promise more than the section shows. |
|
|
26
|
+
| `MarketingTrustBoundary` | What stays on the reader's machine, what is shared, and what the product will not do, each stated once on the page. | Four items | Authority, custody, admission, or lease vocabulary. |
|
|
27
|
+
| `MarketingRelated` `role` | The sibling product's own one-line description from the portfolio registry. It is the only text the card shows, under the product's mark and name. | One line | A new description invented for this page. |
|
|
28
|
+
| `MarketingRelated` `mark` | The sibling product's portfolio mark, such as a `relatedFor()` item's `mark`. | | Artwork the sibling does not use. |
|
|
29
|
+
| `MarketingQuestionList` | Questions readers actually ask. The first sentence of each answer answers it. Generate any FAQ JSON-LD from the same array. | Eight questions | Questions written to deliver a talking point. |
|
|
30
|
+
| `MarketingCallToAction` | The next concrete step. | Section heading limit | Decorative closers ("Give every job the same room to run in."). |
|
|
31
|
+
|
|
32
|
+
## Plain-string props
|
|
33
|
+
|
|
34
|
+
Most text props (`summary`, `detail`, `caption`, `boundary`, `role`) are plain strings and render as text. Backticks show up literally on the page. Name a command in prose there, or put code in a slot that accepts elements (`example`, `children`, `answer`).
|
|
35
|
+
|
|
36
|
+
## Before and after
|
|
37
|
+
|
|
38
|
+
These come from live Hraness pages.
|
|
39
|
+
|
|
40
|
+
- "every turn lands on one eligible account, bounded, with custody proven at settlement." Better: "Each task runs on one of your accounts that is signed in, idle, and not at a known quota limit, and xcb keeps that account locked until the provider process exits."
|
|
41
|
+
- "A policy over your transcripts, not a new editor." Better: "You decide when to compact and how."
|
|
42
|
+
- "Current verified release v0.2.1." Better: "Latest release: v0.2.1."
|
|
43
|
+
- "Automatic replies remain a separate choice, requiring a qualified agent, an enabled contact and global resume." Better: "Automatic replies stay off until you connect an AI account, turn them on for this contact, and resume the butler."
|
|
44
|
+
|
|
45
|
+
## Check before publishing
|
|
46
|
+
|
|
47
|
+
A launch post and its social posts are articles, not page slots. Write them as beats with `ARTICLE_COPY.md`.
|
|
48
|
+
|
|
49
|
+
Read the hero and the first two sections aloud. Every command must run on the current release. Render the page at 1280px and 375px wide; monospace proof frames need to fit about 32 characters per line on a phone.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Marketing preset contract 1 (Quiet)
|
|
2
|
+
|
|
3
|
+
The separately imported `@hraness/design-kit/product-marketing-preset.css` export adds the Quiet marketing treatment to the existing product-marketing grammar: a sans display heading, a flat palette background, hairline rules, and a thin header. It shares Paper's `light-dark()` browser baseline: Chrome 123+, Firefox 120+, and Safari 17.5+. It does not replace Paper contract 1, reset the document, or change default component presentation. Import it after `styles.css` for standalone components, or beside `compiler-foundation.css` in a registered compiler graph. The published manifest binds the stylesheet. Bundle the font its CSS URL references along with it; the browser downloads it only for a product that opts in to it.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import "@hraness/design-kit/product-marketing-preset.css";
|
|
7
|
+
import { MarketingPage, MarketingField, ProductHero, MarketingSection } from "@hraness/design-kit/react/server";
|
|
8
|
+
|
|
9
|
+
<MarketingPage preset="editorial">
|
|
10
|
+
<MarketingField>
|
|
11
|
+
<ProductHero name="Relay" heading="Run a job from your terminal, your code, or your agent" headingId="title"
|
|
12
|
+
summary="Relay runs the same job wherever you start it and writes a log you can read afterward." />
|
|
13
|
+
</MarketingField>
|
|
14
|
+
<MarketingSection heading="Install Relay and run your first job." headingId="next">
|
|
15
|
+
<p>Run <code>relay init</code>, then <code>relay run job-01</code> to write your first log.</p>
|
|
16
|
+
</MarketingSection>
|
|
17
|
+
</MarketingPage>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Raw HTML and older components use `data-hraness-marketing-preset="editorial"` on their marketing ancestor and `.hraness-marketing-field` on the opening container. Existing semantic `hraness-marketing-*` hooks receive the same typography, spacing, and action tokens. A field may also be the hero itself. The field paints the flat palette background in document flow, with no texture, gradient, overlay, or filter, whatever the retired field tokens say. Product imagery and logos stay clean and full color; `.hraness-marketing-brand-mark` is an optional image hook. Existing product accent variables remain authoritative.
|
|
21
|
+
|
|
22
|
+
`editorial` sets the display role to the `--font-text` face (normally Nebula Sans) at weight 550 with tight tracking and a fluid heading that opens at 2.375rem on phones. `minimal` uses the same face at weight 500, a compact heading scale, and a 40px header token. Neither preset uses a serif. The snapshot still vendors Instrument Serif 400 for a product that opts in explicitly with `--hraness-marketing-display-font: "Instrument Serif", Georgia, serif`; the browser downloads it only then. Nested scopes reset their own display and field tokens. Prefer independent header and main scopes when their presets differ.
|
|
23
|
+
|
|
24
|
+
For a custom or older component header, add `class="hraness-marketing-header-surface"` to the header itself. This class explicitly opts in to paint without requiring a preset ancestor. Add `data-hraness-marketing-preset="minimal"` on the same element only when you also need its compact role tokens. This paint-only hook adds supported backdrop blur and opaque accessibility fallbacks without setting position, dimensions, or navigation layout. No wrapper is required around a sticky header. Unsupported filtering, reduced transparency, and forced colors retain opaque surfaces. Blur is allowed only here, on a sticky header with content scrolling behind it; cards and content never use blur or glass. Coarse pointers retain 48px action targets.
|
|
25
|
+
|
|
26
|
+
## Phone header
|
|
27
|
+
|
|
28
|
+
At 48rem and narrower, `MarketingSiteHeader` and the raw `.hraness-marketing-header__*` hooks use two rows. The first row holds the brand, the primary action, and the `trailing` appearance menu. The navigation takes the whole second row, reaches the screen edges, and scrolls sideways without a visible scrollbar. The brand, links, and header actions keep 44px targets (48px for coarse pointers in the preset). The navigation row never widens the header's intrinsic size, so the page does not overflow at 360px. Keep three to six short link labels; the header wraps to its measured height, so use `StickyOffsetSync` or `syncStickyOffset` when the header is sticky.
|
|
29
|
+
|
|
30
|
+
## Palette and patterns
|
|
31
|
+
|
|
32
|
+
The opening field and terminal proof inherit the active palette's foreground, muted, primary, background and surface colors. Existing product accent overrides remain available for actions.
|
|
33
|
+
|
|
34
|
+
Patterns are retired. `MarketingPage` and `MarketingField` still accept the `pattern` prop, and `data-hraness-pattern` still accepts the same finite values in React markup, raw HTML, and immutable CSS snapshots, so existing markup keeps working. Every value paints the flat palette background:
|
|
35
|
+
|
|
36
|
+
| Value | Since 0.23.0 |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| `cells`, `weave`, `contour`, `mesh` | Retired; renders exactly like `none` |
|
|
39
|
+
| `none` and omission | The flat palette background |
|
|
40
|
+
|
|
41
|
+
The grain and cell textures, the field gradient, the accent band's grid, and the terminal chrome gradient no longer paint. `--hraness-marketing-field-images` and `--hraness-marketing-field-size` remain defined for compatibility, but the field ignores them. The hero light inputs are no longer read.
|
|
42
|
+
|
|
43
|
+
Editorial marketing uses the Nebula Sans display role at weight 550 with a fluid 2.375rem to 4.5rem opening heading and a 52-character summary measure. Minimal marketing uses the same face at weight 500 and a smaller fluid hierarchy. Application and embedded preview headings retain their own roles. Marketing cards, proof frames, and header chrome use hairline edges (`0 0 0 1px` of `--hraness-marketing-line`) instead of lifted shadows. Forced colors restores explicit system-color edges.
|
|
44
|
+
|
|
45
|
+
## Sticky clearance and equal-height card rows
|
|
46
|
+
|
|
47
|
+
A sticky `.hraness-marketing-header` or `.hraness-marketing-header-surface` publishes `--hraness-sticky-offset` on `html` and on `.hraness-marketing-page`. The token height is the fallback; `StickyOffsetSync` from `@hraness/design-kit/react` or `syncStickyOffset` from `@hraness/design-kit/browser` replaces it with the measured header border box when the chrome wraps. Attach the site header as a direct child of the page, then put the main landmark immediately after it:
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
import { MarketingMain, MarketingPage, MarketingSiteHeader, StickyOffsetSync } from "@hraness/design-kit/react";
|
|
51
|
+
|
|
52
|
+
<MarketingPage>
|
|
53
|
+
<MarketingSiteHeader brand="Relay" brandMark="/marks/relay.svg" links={[{ href: "#work", label: "Work" }]} />
|
|
54
|
+
<StickyOffsetSync />
|
|
55
|
+
<MarketingMain>
|
|
56
|
+
<div className="hraness-sticky-below-chrome" data-hraness-sticky>Index</div>
|
|
57
|
+
{children}
|
|
58
|
+
</MarketingMain>
|
|
59
|
+
</MarketingPage>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Skip links should target `#main-content`. Hash targets and the main landmark use the offset as `scroll-margin`. Do not add a second padding gap while the header stays in flow. Use `clearance="pad"` or `data-hraness-clearance="pad"` only when the header is `data-position="fixed"`. The next sticky sibling must use `.hraness-sticky-below-chrome` or `data-hraness-sticky` (`inset-block-start: var(--hraness-sticky-offset)`), not `top: 0`. This is the same publish-on-ancestor pattern as `@hraness/ui` next-adopter sticky-offset sync, under the kit-owned `--hraness-sticky-offset` name.
|
|
63
|
+
|
|
64
|
+
`.hraness-marketing-card-row` / `MarketingCardRow` stretches every direct child to the tallest item in the row. Meta sits in a reserved two-line block (`--hraness-marketing-card-meta-lines`, default 2). Titles wrap and are not clamped. Product-owned flex rows that `align-items: start` should switch to this hook so a longer blurb cannot stagger the row.
|
|
65
|
+
|
|
66
|
+
`.hraness-marketing-card__art` / `MarketingCardArt` is the per-card media well. It clips overflow, isolates paint, and keeps backgrounds attached and originated on the well so a logo or art surface cannot run through the gutter as one bar. Pass `art` on `MarketingCard` or `MarketingCardItem`, or put the class on a well inside a `.hraness-marketing-card`. Each card is a containing block (`position: relative; isolation: isolate`). Do not place the well as a row sibling or give it `background-attachment: fixed`.
|
|
67
|
+
|
|
68
|
+
## Tokens for custom compositions
|
|
69
|
+
|
|
70
|
+
| Role | Public tokens |
|
|
71
|
+
| --- | --- |
|
|
72
|
+
| Display | `--hraness-marketing-display-font`, `--hraness-marketing-display-weight` |
|
|
73
|
+
| Hero heading | `--hraness-marketing-h1-size`, `--hraness-marketing-h1-leading`, `--hraness-marketing-h1-tracking` |
|
|
74
|
+
| Section heading | `--hraness-marketing-h2-size`, `--hraness-marketing-h2-leading`, `--hraness-marketing-h2-tracking` |
|
|
75
|
+
| Tier and proof heading | `--hraness-marketing-h3-size`, `--hraness-marketing-h3-leading` |
|
|
76
|
+
| Layout | `--hraness-marketing-content-measure`, `--hraness-marketing-header-measure`, `--hraness-marketing-header-height`, `--hraness-marketing-gutter`, `--hraness-marketing-hero-space`, `--hraness-marketing-story-space` |
|
|
77
|
+
| Field | `--hraness-marketing-field-ink`, `--hraness-marketing-field-muted`, `--hraness-marketing-link`. `--hraness-marketing-field-images` and `--hraness-marketing-field-size` are retired and ignored. |
|
|
78
|
+
| Terminal proof | `--hraness-marketing-terminal-background`, `--hraness-marketing-terminal-ink`, `--hraness-marketing-terminal-muted`, `--hraness-marketing-terminal-command`, `--hraness-marketing-terminal-chrome`, `--hraness-marketing-terminal-shadow` |
|
|
79
|
+
|
|
80
|
+
Map only owned marketing headings to these tokens. Do not restyle every descendant h2 inside product previews or authentication forms. Remove obsolete unlayered font, size, and role-token overrides when adopting the preset; unlayered author rules otherwise outrank the legacy component layer. Custom compiled recipes must read these variables rather than pin old values. The preset uses the existing marketing grammar for layout; importing only this preset does not supply every base component recipe.
|
|
81
|
+
|
|
82
|
+
`MarketingSection.label` and collection labels are optional in current components. For older versions that require strings, pass an empty string only when the slot is redundant. The preset removes spacing for empty label/name/eyebrow elements; nonempty facts remain visible. Preserve factual positioning in an appropriate product-owned body or example slot.
|
|
83
|
+
|
|
84
|
+
## Mockups
|
|
85
|
+
|
|
86
|
+
`@hraness/design-kit/mockups` draws illustrations of chat, feeds, inboxes,
|
|
87
|
+
articles, terminals, browsers, desktop windows, and phones in plain React with
|
|
88
|
+
no StyleX. Its stylesheet, `@hraness/design-kit/mockups.css`, uses `hkm-`
|
|
89
|
+
classes, reads the marketing tokens with fallbacks, sizes itself with container
|
|
90
|
+
queries, and switches between light and dark on `[data-hkm-theme]`. Transitions
|
|
91
|
+
start only after the reader first changes a state, and stop under reduced
|
|
92
|
+
motion. `MarketingProofFrame` takes `chrome="window"`, `"browser"` with a `url`,
|
|
93
|
+
or `"terminal"` for real captures.
|
|
94
|
+
|
|
95
|
+
## Immutable snapshots for existing sites
|
|
96
|
+
|
|
97
|
+
Keep the current component/UI versions when upgrading them would create unrelated migration work. Use the installer from a reviewed design-kit checkout at an exact full commit containing the preset:
|
|
98
|
+
|
|
99
|
+
```sh
|
|
100
|
+
bun scripts/product-marketing-snapshot.ts --write /path/to/site/vendor/hraness-marketing --source-commit FULL_40_CHARACTER_COMMIT
|
|
101
|
+
node /path/to/site/vendor/hraness-marketing/check.mjs
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Import `vendor/hraness-marketing/product-marketing-preset.css`. Keep the entire directory. The CSS references only `fonts/instrument-serif/instrument-serif-latin-400.woff2`, which loads only when a product opts in to that face. `marketing-assets/grain.svg` and `marketing-assets/cells.svg` stay in the inventory so an earlier snapshot upgrades in place, but the Quiet preset no longer references them. Reinstall from a 0.23.0 or later commit to get the Quiet preset; the same installer replaces a previously verified snapshot. The finite bundle also includes their licenses/provenance, a dependency-free Node/Bun `check.mjs` validator, and `provenance.json`, which binds every file to SHA-256 and a source commit. Check that manifest in the consumer gate. Installation reads Git object bytes, never a dirty working copy, and refuses changed, unowned, or symlinked destinations. An upgrade replaces only a previously verified snapshot. Run from a released immutable commit for production delivery; local candidate commits are for pre-release verification.
|
|
105
|
+
|
|
106
|
+
The retained texture files come from `scripts/marketing-textures.ts`; its check verifies byte-identical deterministic SVGs. Instrument Serif retains its OFL license and upstream provenance. The existing Paper CSS snapshot remains separate and unchanged. Neither snapshot executes code at runtime or upgrades UI/compiler dependencies.
|
|
107
|
+
|
|
108
|
+
For a build graph, import `checkMarketingSnapshot` from the vendored `check.mjs` and await it with the snapshot directory (default: the checker's directory). It returns the validated source identity and finite `files` record; each entry contains its source path and SHA-256. The included `check.d.mts` provides TypeScript declarations. Include those checked CSS, font, and SVG files in your build; validation does not copy or modify files.
|