@hraness/design-kit 0.40.2 → 0.41.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hraness/design-kit",
3
- "version": "0.40.2",
3
+ "version": "0.41.0",
4
4
  "description": "React components and CSS for Hraness apps and product sites: application shells, marketing sections, charts, themes, effects, and syntax highlighting. Built on @hraness/ui.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -129,7 +129,8 @@
129
129
  },
130
130
  "./marketing-comparison.css": "./src/marketing-comparison.css",
131
131
  "./marketing-diagram.css": "./src/marketing-diagram.css",
132
- "./marketing-marquee.css": "./src/marketing-marquee.css"
132
+ "./marketing-marquee.css": "./src/marketing-marquee.css",
133
+ "./product-landscape.css": "./src/product-landscape.css"
133
134
  },
134
135
  "main": "./dist/index.js",
135
136
  "types": "./src/index.ts",
@@ -314,7 +315,9 @@
314
315
  "src/marketing-diagram.css",
315
316
  "src/marketing-marquee.ts",
316
317
  "src/marketing-marquee.css",
317
- "src/react/marketing-marquee.tsx"
318
+ "src/react/marketing-marquee.tsx",
319
+ "src/product-landscape.css",
320
+ "LANDSCAPE.md"
318
321
  ],
319
322
  "scripts": {
320
323
  "build": "bun run ./scripts/build.ts",
@@ -328,7 +331,7 @@
328
331
  "browser:install": "bun ./node_modules/playwright-core/cli.js install chromium",
329
332
  "test:browser": "bun run test:browser:marketing && bun run test:browser:rest",
330
333
  "test:browser:marketing": "bun run ./scripts/product-marketing-browser.ts",
331
- "test:browser:rest": "bun run ./scripts/marketing-header-browser.ts && bun run ./scripts/site-shell-browser.ts && bun run ./scripts/palette-browser.ts && bun run ./scripts/gallery-browser.ts && bun run ./scripts/global-error-browser.ts && bun run ./scripts/security-delivery-browser.ts && bun run ./scripts/paper-theme-browser.ts && bun run ./scripts/product-marketing-preset-browser.ts && bun run ./scripts/lantern-material-browser.ts && bun run ./scripts/foil-browser.ts && bun run ./scripts/hero-light-browser.ts && bun run ./scripts/status-page-browser.ts && bun run ./scripts/marketing-marquee-browser.ts && bun run ./scripts/platform-install-browser.ts && bun run ./scripts/mockups-browser.ts && bun run ./scripts/agent-setup-browser.ts",
334
+ "test:browser:rest": "bun run ./scripts/marketing-header-browser.ts && bun run ./scripts/site-shell-browser.ts && bun run ./scripts/palette-browser.ts && bun run ./scripts/gallery-browser.ts && bun run ./scripts/global-error-browser.ts && bun run ./scripts/security-delivery-browser.ts && bun run ./scripts/paper-theme-browser.ts && bun run ./scripts/product-marketing-preset-browser.ts && bun run ./scripts/lantern-material-browser.ts && bun run ./scripts/foil-browser.ts && bun run ./scripts/hero-light-browser.ts && bun run ./scripts/status-page-browser.ts && bun run ./scripts/marketing-marquee-browser.ts && bun run ./scripts/product-landscape-browser.ts && bun run ./scripts/platform-install-browser.ts && bun run ./scripts/mockups-browser.ts && bun run ./scripts/agent-setup-browser.ts",
332
335
  "test:package": "bun run ./scripts/package-smoke.ts && bun run ./scripts/mockups-import.ts",
333
336
  "kb:refresh": "bunx --bun --package https://github.com/hraness/wordcell/releases/download/v0.24.0/hraness-wordcell-0.24.0.tgz wordcell refresh --root kb",
334
337
  "kb:check:lane": "bunx --bun --package https://github.com/hraness/wordcell/releases/download/v0.24.0/hraness-wordcell-0.24.0.tgz wordcell check --root kb --no-catalog",
@@ -6,7 +6,7 @@
6
6
  "name": "@hraness/design-kit",
7
7
  "path": ".",
8
8
  "visibility": "public",
9
- "version": "0.40.2"
9
+ "version": "0.41.0"
10
10
  }
11
11
  ],
12
12
  "contract": "hraness.portfolio-inventory/v1",
@@ -15,6 +15,7 @@
15
15
  @import "./status-page.css";
16
16
  @import "./marketing-marquee.css";
17
17
  @import "./product-marketing-foundation.css";
18
+ @import "./product-landscape.css";
18
19
  @import "./design-gallery.css";
19
20
  @import "./mockups.css";
20
21
  @import "./lantern-material.css";
@@ -347,6 +347,41 @@
347
347
  background: var(--background);
348
348
  }
349
349
 
350
+ .design-gallery__landscape {
351
+ display: grid;
352
+ min-block-size: 22rem;
353
+ align-content: start;
354
+ gap: var(--space-4);
355
+ padding: var(--space-8);
356
+ overflow: clip;
357
+ border: 1px solid var(--line);
358
+ background: var(--background);
359
+ }
360
+
361
+ .design-gallery__landscape-copy {
362
+ display: grid;
363
+ max-inline-size: 38rem;
364
+ gap: var(--space-2);
365
+ }
366
+
367
+ .design-gallery__landscape-copy > :where(h3, p),
368
+ .design-gallery__landscape-code {
369
+ margin: 0;
370
+ }
371
+
372
+ .design-gallery__landscape-code {
373
+ justify-self: start;
374
+ padding: var(--space-3) var(--space-4);
375
+ font-family: var(--font-mono, ui-monospace, monospace);
376
+ }
377
+
378
+ .design-gallery__landscape-note {
379
+ justify-self: start;
380
+ margin: 0;
381
+ padding: var(--space-3) var(--space-4);
382
+ border: 1px solid var(--line);
383
+ }
384
+
350
385
  .design-gallery__foil-deck {
351
386
  display: grid;
352
387
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr));
@@ -3,14 +3,14 @@
3
3
  "formatVersion": 1,
4
4
  "provenance": {
5
5
  "registry": "https://hraness.com/portfolio.json",
6
- "commit": "26b167cd5701caf2aec8ff15a0fd046d940bc6b5",
6
+ "commit": "64d51361007d5d38b3a4f0911c3bae58ca20de63",
7
7
  "committedOn": "2026-10-05",
8
8
  "upstreamContract": "hraness.portfolio-public/v1",
9
- "upstreamDigest": "sha256:0d3dd93744fc4adf183d81a4b62e4ba5bbca6dc417b6236fa99de7d19e6d7ecf",
9
+ "upstreamDigest": "sha256:a540c73eab4c3e7a363b8d2ebf9f1b5860e9ec89f486cc8a8d7ed737fb8a860b",
10
10
  "files": [
11
11
  {
12
12
  "path": "portfolio.public.generated.json",
13
- "sha256": "665c543651734bc2778c0bc50033cbb40e9604defbafbab83832e37af2a28e19"
13
+ "sha256": "862131d360164a8cb5a1dcf688993459d6f8077876ac5f6354117f497d318f98"
14
14
  },
15
15
  {
16
16
  "path": "packages/brand-catalog/brands.yaml",
@@ -921,7 +921,7 @@
921
921
  "hero": {
922
922
  "heading": "Give your agent a multimedia studio.",
923
923
  "summary": "Make images, animation, 3D scenes, and video with your coding agent. Direct the details. Keep the source.",
924
- "primaryAction": "Install Slopcamera",
924
+ "primaryAction": "Install SlopCamera",
925
925
  "secondaryAction": "Watch the films"
926
926
  },
927
927
  "headings": {
@@ -933,7 +933,7 @@
933
933
  "home-start-animation": "Make an animated film",
934
934
  "home-start-world": "Build a little world",
935
935
  "home-start-footage": "Give footage a new life",
936
- "home-install": "Install Slopcamera for your agent.",
936
+ "home-install": "Install SlopCamera for your agent.",
937
937
  "home-design": "Your tools. Your files.",
938
938
  "home-questions": "Common questions",
939
939
  "home-cta": "Make something worth replaying.",
@@ -941,7 +941,7 @@
941
941
  "agent-home-examples": "See what you can make",
942
942
  "agent-home-revision": "Direct it again",
943
943
  "agent-home-workflow": "From an idea to a finished piece",
944
- "agent-home-install": "Install Slopcamera",
944
+ "agent-home-install": "Install SlopCamera",
945
945
  "agent-home-tools": "Choose your tools",
946
946
  "agent-home-privacy": "Cost and privacy",
947
947
  "agent-home-explore": "Explore"
@@ -2197,5 +2197,5 @@
2197
2197
  "detail": "The icon.place drawing lab runs each experiment as an ALGAL program in the browser and records the proposal, candidates, and selection as receipts it can replay; the live site does not use ALGAL yet."
2198
2198
  }
2199
2199
  ],
2200
- "digest": "sha256:4cc6210bb3827828fd7f75b0c9a1b8182acbcf098071c5db8855bf4a8a0a8e5e"
2200
+ "digest": "sha256:2b479271b1d93256390327d29e63d23add0595150cae675eb99635fac8dc6973"
2201
2201
  }
@@ -4,14 +4,14 @@ export const portfolioSnapshot = {
4
4
  "formatVersion": 1,
5
5
  "provenance": {
6
6
  "registry": "https://hraness.com/portfolio.json",
7
- "commit": "26b167cd5701caf2aec8ff15a0fd046d940bc6b5",
7
+ "commit": "64d51361007d5d38b3a4f0911c3bae58ca20de63",
8
8
  "committedOn": "2026-10-05",
9
9
  "upstreamContract": "hraness.portfolio-public/v1",
10
- "upstreamDigest": "sha256:0d3dd93744fc4adf183d81a4b62e4ba5bbca6dc417b6236fa99de7d19e6d7ecf",
10
+ "upstreamDigest": "sha256:a540c73eab4c3e7a363b8d2ebf9f1b5860e9ec89f486cc8a8d7ed737fb8a860b",
11
11
  "files": [
12
12
  {
13
13
  "path": "portfolio.public.generated.json",
14
- "sha256": "665c543651734bc2778c0bc50033cbb40e9604defbafbab83832e37af2a28e19"
14
+ "sha256": "862131d360164a8cb5a1dcf688993459d6f8077876ac5f6354117f497d318f98"
15
15
  },
16
16
  {
17
17
  "path": "packages/brand-catalog/brands.yaml",
@@ -922,7 +922,7 @@ export const portfolioSnapshot = {
922
922
  "hero": {
923
923
  "heading": "Give your agent a multimedia studio.",
924
924
  "summary": "Make images, animation, 3D scenes, and video with your coding agent. Direct the details. Keep the source.",
925
- "primaryAction": "Install Slopcamera",
925
+ "primaryAction": "Install SlopCamera",
926
926
  "secondaryAction": "Watch the films"
927
927
  },
928
928
  "headings": {
@@ -934,7 +934,7 @@ export const portfolioSnapshot = {
934
934
  "home-start-animation": "Make an animated film",
935
935
  "home-start-world": "Build a little world",
936
936
  "home-start-footage": "Give footage a new life",
937
- "home-install": "Install Slopcamera for your agent.",
937
+ "home-install": "Install SlopCamera for your agent.",
938
938
  "home-design": "Your tools. Your files.",
939
939
  "home-questions": "Common questions",
940
940
  "home-cta": "Make something worth replaying.",
@@ -942,7 +942,7 @@ export const portfolioSnapshot = {
942
942
  "agent-home-examples": "See what you can make",
943
943
  "agent-home-revision": "Direct it again",
944
944
  "agent-home-workflow": "From an idea to a finished piece",
945
- "agent-home-install": "Install Slopcamera",
945
+ "agent-home-install": "Install SlopCamera",
946
946
  "agent-home-tools": "Choose your tools",
947
947
  "agent-home-privacy": "Cost and privacy",
948
948
  "agent-home-explore": "Explore"
@@ -2198,5 +2198,5 @@ export const portfolioSnapshot = {
2198
2198
  "detail": "The icon.place drawing lab runs each experiment as an ALGAL program in the browser and records the proposal, candidates, and selection as receipts it can replay; the live site does not use ALGAL yet."
2199
2199
  }
2200
2200
  ],
2201
- "digest": "sha256:4cc6210bb3827828fd7f75b0c9a1b8182acbcf098071c5db8855bf4a8a0a8e5e"
2201
+ "digest": "sha256:2b479271b1d93256390327d29e63d23add0595150cae675eb99635fac8dc6973"
2202
2202
  } as const;
@@ -0,0 +1,202 @@
1
+ /* Product landscape: one product-owned illustration drawn behind marketing,
2
+ * documentation, and blog pages. See LANDSCAPE.md.
3
+ *
4
+ * The product supplies a grayscale luminance mask (light drawing on black) and
5
+ * the kit tints it with the page's own text mixed into its background, so the
6
+ * drawing follows every palette and appearance without an accent colour. The
7
+ * mask tiles down the page, so long pages never run out of scenery.
8
+ *
9
+ * Hosts:
10
+ * - `data-hraness-landscape="page"` on <body> or on any element inside it paints
11
+ * the layer behind the whole document. `data-hraness-landscape="off"` anywhere
12
+ * on the page turns it off, for application routes under a shared layout.
13
+ * - `data-hraness-landscape="contained"` paints the layer behind one element.
14
+ *
15
+ * Products set `--hraness-landscape-image` (and optionally the narrow image,
16
+ * opacity, and strength) on :root with root-relative or absolute URLs. Without
17
+ * an image the mask is fully transparent, so an unconfigured host paints
18
+ * nothing. Forced colours and print never draw the layer. */
19
+ :is(
20
+ body:is([data-hraness-landscape="page"], :has([data-hraness-landscape="page"])):not(:has([data-hraness-landscape="off"])),
21
+ [data-hraness-landscape="contained"]
22
+ ) {
23
+ --_hraness-landscape-ink: var(
24
+ --hraness-landscape-ink,
25
+ color-mix(
26
+ in oklch,
27
+ var(--foreground, var(--plain-foreground, CanvasText)) var(--hraness-landscape-strength, 30%),
28
+ var(--background, var(--plain-background, Canvas))
29
+ )
30
+ );
31
+ position: relative;
32
+ isolation: isolate;
33
+ }
34
+
35
+ @supports (mask-mode: luminance) or (-webkit-mask-source-type: luminance) {
36
+ :is(
37
+ body:is([data-hraness-landscape="page"], :has([data-hraness-landscape="page"])):not(:has([data-hraness-landscape="off"])),
38
+ [data-hraness-landscape="contained"]
39
+ )::before {
40
+ position: absolute;
41
+ inset: 0;
42
+ z-index: -1;
43
+ background: var(--_hraness-landscape-ink);
44
+ content: "";
45
+ opacity: var(--hraness-landscape-opacity, 0.5);
46
+ pointer-events: none;
47
+ -webkit-mask-image: var(--hraness-landscape-image, linear-gradient(transparent, transparent));
48
+ mask-image: var(--hraness-landscape-image, linear-gradient(transparent, transparent));
49
+ -webkit-mask-source-type: luminance;
50
+ mask-mode: luminance;
51
+ -webkit-mask-position: top center;
52
+ mask-position: top center;
53
+ -webkit-mask-repeat: repeat-y;
54
+ mask-repeat: repeat-y;
55
+ -webkit-mask-size: 100% auto;
56
+ mask-size: 100% auto;
57
+ }
58
+
59
+ @media (max-width: 48rem) {
60
+ :is(
61
+ body:is([data-hraness-landscape="page"], :has([data-hraness-landscape="page"])):not(:has([data-hraness-landscape="off"])),
62
+ [data-hraness-landscape="contained"]
63
+ )::before {
64
+ opacity: var(--hraness-landscape-opacity-narrow, 0.36);
65
+ -webkit-mask-image: var(--hraness-landscape-image-narrow, var(--hraness-landscape-image, linear-gradient(transparent, transparent)));
66
+ mask-image: var(--hraness-landscape-image-narrow, var(--hraness-landscape-image, linear-gradient(transparent, transparent)));
67
+ }
68
+ }
69
+ }
70
+
71
+ @media (forced-colors: active), print {
72
+ :is(
73
+ body:is([data-hraness-landscape="page"], :has([data-hraness-landscape="page"])):not(:has([data-hraness-landscape="off"])),
74
+ [data-hraness-landscape="contained"]
75
+ )::before {
76
+ display: none;
77
+ }
78
+ }
79
+
80
+ /* Page wrappers that repaint the page colour would hide the drawing behind a
81
+ * hard-edged slab. Over a landscape they let the identical body colour show. */
82
+ :is(
83
+ body:is([data-hraness-landscape="page"], :has([data-hraness-landscape="page"])):not(:has([data-hraness-landscape="off"])),
84
+ [data-hraness-landscape="contained"]
85
+ ) :is(.hraness-marketing-page, .plain-site:not(body), .plain-footer, [data-hraness-landscape-surface="clear"]) {
86
+ background: transparent;
87
+ }
88
+
89
+ /* Reading surfaces stay opaque and gain rounded edges: code, tables, and any
90
+ * product element marked `data-hraness-landscape-surface="solid"`. */
91
+ :is(
92
+ body:is([data-hraness-landscape="page"], :has([data-hraness-landscape="page"])):not(:has([data-hraness-landscape="off"])),
93
+ [data-hraness-landscape="contained"]
94
+ ) :is(.plain-publication__article-body pre, .plain-publication__table-scroll, [data-hraness-landscape-surface="solid"]) {
95
+ border-radius: var(--hraness-landscape-radius, 0.75rem);
96
+ background: var(--hraness-landscape-solid, var(--plain-surface, var(--hraness-marketing-surface, var(--background, Canvas))));
97
+ }
98
+
99
+ :is(
100
+ body:is([data-hraness-landscape="page"], :has([data-hraness-landscape="page"])):not(:has([data-hraness-landscape="off"])),
101
+ [data-hraness-landscape="contained"]
102
+ ) .plain-publication__article-body pre {
103
+ padding: 0.9rem 1.1rem;
104
+ }
105
+
106
+ :is(
107
+ body:is([data-hraness-landscape="page"], :has([data-hraness-landscape="page"])):not(:has([data-hraness-landscape="off"])),
108
+ [data-hraness-landscape="contained"]
109
+ ) .plain-publication__table-scroll {
110
+ border: 1px solid var(--plain-line, color-mix(in srgb, currentColor 14%, transparent));
111
+ }
112
+
113
+ /* Small cards and callouts become frosted panels over the drawing, using the
114
+ * sticky header's material. Without backdrop support, under reduced
115
+ * transparency, and in forced colours they keep their opaque surface. Keep the
116
+ * browser aliases in separate feature queries: CSS optimizers can collapse
117
+ * adjacent aliases to the prefixed property and lose Chromium blur. */
118
+ :is(
119
+ body:is([data-hraness-landscape="page"], :has([data-hraness-landscape="page"])):not(:has([data-hraness-landscape="off"])),
120
+ [data-hraness-landscape="contained"]
121
+ ) :is(
122
+ .hraness-marketing-card,
123
+ .hraness-marketing-quote,
124
+ .hraness-marketing-plan,
125
+ .hraness-marketing-trust-item,
126
+ .hraness-marketing-interface,
127
+ .hraness-marketing-primitive,
128
+ .plain-publication__callout,
129
+ .plain-publication__social-kit-item,
130
+ [data-hraness-landscape-surface="glass"]
131
+ ) {
132
+ --_hraness-landscape-glass-base: var(--hraness-landscape-glass, var(--hraness-marketing-surface, var(--plain-surface, var(--background, Canvas))));
133
+ background: var(--_hraness-landscape-glass-base);
134
+ }
135
+
136
+ :is(
137
+ body:is([data-hraness-landscape="page"], :has([data-hraness-landscape="page"])):not(:has([data-hraness-landscape="off"])),
138
+ [data-hraness-landscape="contained"]
139
+ ) [data-hraness-landscape-surface="glass"] {
140
+ border-radius: var(--hraness-landscape-radius, 0.75rem);
141
+ }
142
+
143
+ @supports (-webkit-backdrop-filter: blur(1px)) {
144
+ :is(
145
+ body:is([data-hraness-landscape="page"], :has([data-hraness-landscape="page"])):not(:has([data-hraness-landscape="off"])),
146
+ [data-hraness-landscape="contained"]
147
+ ) :is(
148
+ .hraness-marketing-card,
149
+ .hraness-marketing-quote,
150
+ .hraness-marketing-plan,
151
+ .hraness-marketing-trust-item,
152
+ .hraness-marketing-interface,
153
+ .hraness-marketing-primitive,
154
+ .plain-publication__callout,
155
+ .plain-publication__social-kit-item,
156
+ [data-hraness-landscape-surface="glass"]
157
+ ) {
158
+ background: color-mix(in srgb, var(--_hraness-landscape-glass-base) 82%, transparent);
159
+ -webkit-backdrop-filter: blur(14px) saturate(1.4);
160
+ }
161
+ }
162
+
163
+ @supports (backdrop-filter: blur(1px)) {
164
+ :is(
165
+ body:is([data-hraness-landscape="page"], :has([data-hraness-landscape="page"])):not(:has([data-hraness-landscape="off"])),
166
+ [data-hraness-landscape="contained"]
167
+ ) :is(
168
+ .hraness-marketing-card,
169
+ .hraness-marketing-quote,
170
+ .hraness-marketing-plan,
171
+ .hraness-marketing-trust-item,
172
+ .hraness-marketing-interface,
173
+ .hraness-marketing-primitive,
174
+ .plain-publication__callout,
175
+ .plain-publication__social-kit-item,
176
+ [data-hraness-landscape-surface="glass"]
177
+ ) {
178
+ background: color-mix(in srgb, var(--_hraness-landscape-glass-base) 82%, transparent);
179
+ backdrop-filter: blur(14px) saturate(1.4);
180
+ }
181
+ }
182
+
183
+ @media (prefers-reduced-transparency: reduce), (forced-colors: active) {
184
+ :is(
185
+ body:is([data-hraness-landscape="page"], :has([data-hraness-landscape="page"])):not(:has([data-hraness-landscape="off"])),
186
+ [data-hraness-landscape="contained"]
187
+ ) :is(
188
+ .hraness-marketing-card,
189
+ .hraness-marketing-quote,
190
+ .hraness-marketing-plan,
191
+ .hraness-marketing-trust-item,
192
+ .hraness-marketing-interface,
193
+ .hraness-marketing-primitive,
194
+ .plain-publication__callout,
195
+ .plain-publication__social-kit-item,
196
+ [data-hraness-landscape-surface="glass"]
197
+ ) {
198
+ background: var(--_hraness-landscape-glass-base);
199
+ -webkit-backdrop-filter: none;
200
+ backdrop-filter: none;
201
+ }
202
+ }
@@ -21,7 +21,7 @@ import {
21
21
  WrappingRow,
22
22
  } from "@hraness/ui";
23
23
  import { Chart01Icon, CodeIcon, DashboardSquare01Icon } from "@hugeicons/core-free-icons";
24
- import { useState } from "react";
24
+ import { type CSSProperties, useState } from "react";
25
25
 
26
26
  import { BrowserFrame, SampleText } from "../mockups/index.js";
27
27
  import { StepThrough } from "../mockups/client.js";
@@ -1062,6 +1062,21 @@ export function DesignSystemGallery({
1062
1062
  <p>Decorative paint is pointer-transparent and removable in forced colors. Marketing actions use system button colors so their labels stay readable.</p>
1063
1063
  </div>
1064
1064
  </div>
1065
+ <div
1066
+ className="design-gallery__landscape"
1067
+ data-hraness-landscape="contained"
1068
+ style={{
1069
+ "--hraness-landscape-image":
1070
+ "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 300'%3E%3Crect width='400' height='300'/%3E%3Cg fill='none' stroke='white' stroke-width='1.5'%3E%3Cpath d='M0 240 L40 190 L70 215 L110 150 L150 205 L170 185'/%3E%3Cpath d='M230 185 L260 150 L300 200 L340 140 L370 180 L400 160'/%3E%3Cpath d='M20 270 C60 250 100 262 150 250'/%3E%3Cpath d='M250 252 C300 262 340 248 390 262'/%3E%3Ccircle cx='340' cy='60' r='16'/%3E%3C/g%3E%3C/svg%3E\")",
1071
+ } as CSSProperties}
1072
+ >
1073
+ <div className="design-gallery__landscape-copy">
1074
+ <h3>Product landscape</h3>
1075
+ <p>One product drawing, tinted from the palette and tiled down the page. Code and tables stay solid with rounded edges; small cards and callouts frost over it.</p>
1076
+ </div>
1077
+ <pre className="design-gallery__landscape-code" data-hraness-landscape-surface="solid"><code>relay run job-01</code></pre>
1078
+ <p className="design-gallery__landscape-note" data-hraness-landscape-surface="glass">The log is written next to the job.</p>
1079
+ </div>
1065
1080
  </section>
1066
1081
 
1067
1082
  <section className="design-gallery__section" id="syntax">
@@ -133,16 +133,22 @@ function MarketingActions({
133
133
  * gutter for every direct child role. Products set `--hraness-site-accent`
134
134
  * on this element or on a parent.
135
135
  */
136
+ /** Where a product landscape draws (see LANDSCAPE.md): behind the whole page, behind one element, or nowhere. */
137
+ export type ProductLandscapeHost = "page" | "contained" | "off";
138
+
136
139
  export function MarketingPage({
137
140
  children,
138
141
  className,
139
142
  id,
143
+ landscape,
140
144
  preset,
141
145
  pattern,
142
146
  }: Readonly<{
143
147
  children: ReactNode;
144
148
  className?: string;
145
149
  id?: string;
150
+ /** Draw the product landscape from product-landscape.css behind this page. */
151
+ landscape?: ProductLandscapeHost;
146
152
  /** Opt in to the separately imported product-marketing-preset.css contract. */
147
153
  preset?: MarketingPreset;
148
154
  /**
@@ -153,9 +159,10 @@ export function MarketingPage({
153
159
  pattern?: MarketingPattern;
154
160
  }>) {
155
161
  if (preset !== undefined && preset !== "editorial" && preset !== "minimal") throw new RangeError("Unknown marketing preset.");
162
+ if (landscape !== undefined && landscape !== "page" && landscape !== "contained" && landscape !== "off") throw new RangeError("Unknown landscape host.");
156
163
  assertMarketingPattern(pattern);
157
164
  return (
158
- <div className={classNames("hraness-marketing-page", className)} data-hraness-marketing="page" data-hraness-marketing-preset={preset} data-hraness-pattern={pattern} id={id}>
165
+ <div className={classNames("hraness-marketing-page", className)} data-hraness-landscape={landscape} data-hraness-marketing="page" data-hraness-marketing-preset={preset} data-hraness-pattern={pattern} id={id}>
159
166
  {children}
160
167
  </div>
161
168
  );
package/src/styles.css CHANGED
@@ -19,6 +19,7 @@
19
19
  @import "./status-page.css";
20
20
  @import "./product-marketing.css";
21
21
  @import "./product-marketing-foundation.css";
22
+ @import "./product-landscape.css";
22
23
  @import "./design-gallery.css";
23
24
  @import "./mockups.css";
24
25
  @import "./palette-bridge.css";