@hraness/design-kit 0.33.0 → 0.35.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.
Files changed (44) hide show
  1. package/README.md +19 -3
  2. package/dist/{chunk-mr1vdcjq.js → chunk-8xfssb9c.js} +6 -3
  3. package/dist/{chunk-95mpmzc9.js → chunk-c9mhn3yb.js} +1130 -442
  4. package/dist/{chunk-h4k7yv6x.js → chunk-fjf81bww.js} +28 -2
  5. package/dist/chunk-gmea6p0d.js +13 -0
  6. package/dist/index.js +5 -1
  7. package/dist/mockups/client.js +50 -15
  8. package/dist/portfolio.js +586 -85
  9. package/dist/react/index.js +141 -13
  10. package/dist/react/platform-install.js +3 -2
  11. package/dist/react/server.js +17 -3
  12. package/dist/stylex-manifest.json +1 -1
  13. package/dist/stylex.css +350 -36
  14. package/package.json +14 -3
  15. package/portfolio-inventory.json +1 -1
  16. package/src/diagrams.ts +11 -0
  17. package/src/index.ts +2 -0
  18. package/src/marketing-comparison.css +162 -0
  19. package/src/marketing-diagram.css +24 -0
  20. package/src/mockups/client.tsx +25 -17
  21. package/src/mockups.css +19 -0
  22. package/src/plain-publication.css +9 -6
  23. package/src/portfolio.generated.json +578 -86
  24. package/src/portfolio.generated.ts +578 -86
  25. package/src/portfolio.ts +73 -0
  26. package/src/product-marketing-foundation.css +29 -0
  27. package/src/product-marketing.css +136 -39
  28. package/src/react/article.tsx +2 -2
  29. package/src/react/design-gallery.tsx +37 -8
  30. package/src/react/index.ts +3 -0
  31. package/src/react/marketing-account.stylex.ts +74 -0
  32. package/src/react/marketing-account.tsx +69 -0
  33. package/src/react/marketing-comparison.stylex.ts +24 -0
  34. package/src/react/marketing-comparison.tsx +73 -0
  35. package/src/react/marketing-diagram.stylex.ts +8 -0
  36. package/src/react/marketing-diagram.tsx +33 -0
  37. package/src/react/platform-install.stylex.ts +2 -1
  38. package/src/react/platform-install.tsx +2 -1
  39. package/src/react/product-marketing.stylex.ts +144 -111
  40. package/src/react/product-marketing.tsx +83 -45
  41. package/src/react/server.ts +3 -0
  42. package/src/styles.css +2 -0
  43. package/src/typography.css +32 -0
  44. /package/dist/{chunk-4d79eskk.js → chunk-c80g58j1.js} +0 -0
package/src/portfolio.ts CHANGED
@@ -108,11 +108,39 @@ export type PortfolioProvenance = Readonly<{
108
108
  files: readonly Readonly<{ path: string; sha256: string }>[];
109
109
  }>;
110
110
 
111
+ export type PortfolioPackTone = "rose" | "indigo" | "amber" | "emerald";
112
+
113
+ /** The homepage categories, in the same order as hraness.com. */
114
+ export type PortfolioPack = Readonly<{
115
+ slug: string;
116
+ title: string;
117
+ tone: PortfolioPackTone;
118
+ members: readonly PortfolioProductId[];
119
+ }>;
120
+
121
+ export type PortfolioStudioItem = Readonly<{
122
+ productId: PortfolioProductId;
123
+ href: string;
124
+ name: string;
125
+ role: string;
126
+ mark: string;
127
+ domain: string;
128
+ }>;
129
+
130
+ /** Product-neutral data shaped for the shared MarketingRelated composition. */
131
+ export type PortfolioStudioGroup = Readonly<{
132
+ heading: string;
133
+ headingId: string;
134
+ tone: PortfolioPackTone | "neutral";
135
+ items: readonly PortfolioStudioItem[];
136
+ }>;
137
+
111
138
  export type PortfolioSnapshot = Readonly<{
112
139
  contract: "hraness.design-kit-portfolio/v1";
113
140
  formatVersion: 1;
114
141
  provenance: PortfolioProvenance;
115
142
  products: Readonly<Record<PortfolioProductId, PortfolioProduct>>;
143
+ packs: readonly PortfolioPack[];
116
144
  relations: readonly PortfolioRelation[];
117
145
  /** SHA-256 of the snapshot's compact JSON without this key. Pin it in a consumer test. */
118
146
  digest: string;
@@ -154,6 +182,7 @@ function deepFreeze<T>(value: T): T {
154
182
  /** The whole snapshot, frozen so one consumer cannot change another's facts at runtime. */
155
183
  export const portfolioFacts: PortfolioSnapshot = deepFreeze(portfolioSnapshot);
156
184
  export const portfolioProducts: PortfolioSnapshot["products"] = portfolioFacts.products;
185
+ export const portfolioPacks: readonly PortfolioPack[] = portfolioFacts.packs;
157
186
  export const portfolioRelations: PortfolioSnapshot["relations"] = portfolioFacts.relations;
158
187
  export const portfolioProvenance: PortfolioProvenance = portfolioFacts.provenance;
159
188
  export const portfolioDigest: string = portfolioFacts.digest;
@@ -224,3 +253,47 @@ export function usesPairs(): readonly PortfolioUsesPair[] {
224
253
  target: product(relation.target),
225
254
  }));
226
255
  }
256
+
257
+ /**
258
+ * Present only the caller's selected products, once each, in the homepage's
259
+ * category and member order. Categories with no selected products disappear.
260
+ * Products outside a pack follow in a neutral group. This does not infer an
261
+ * integration or expand a site's curated related-product selection.
262
+ */
263
+ export function portfolioRelatedGroups(
264
+ ids: readonly PortfolioProductId[],
265
+ headingPrefix = "related",
266
+ ): readonly PortfolioStudioGroup[] {
267
+ if (!/^[a-z][a-z0-9-]*$/u.test(headingPrefix)) {
268
+ throw new PortfolioFactsError("The related heading prefix must be a lowercase HTML identifier.");
269
+ }
270
+ const selected = new Set(ids.map((id) => product(id).id));
271
+ const item = (id: PortfolioProductId): PortfolioStudioItem => {
272
+ const entry = product(id);
273
+ return {
274
+ productId: id,
275
+ href: entry.canonicalUrl,
276
+ name: entry.messaging.names.name,
277
+ role: entry.oneLiner,
278
+ mark: entry.mark,
279
+ domain: entry.canonicalUrl.replace(/^https?:\/\/(?:www\.)?([^/:?#]+).*$/u, "$1"),
280
+ };
281
+ };
282
+ const groups: PortfolioStudioGroup[] = [];
283
+ for (const pack of portfolioPacks) {
284
+ const members = pack.members.filter((id) => selected.delete(id));
285
+ if (members.length > 0) groups.push({
286
+ heading: pack.title,
287
+ headingId: `${headingPrefix}-${pack.slug}`,
288
+ tone: pack.tone,
289
+ items: members.map(item),
290
+ });
291
+ }
292
+ if (selected.size > 0) groups.push({
293
+ heading: "More from Hraness",
294
+ headingId: `${headingPrefix}-more`,
295
+ tone: "neutral",
296
+ items: portfolioProductIds.filter((id) => selected.has(id)).map(item),
297
+ });
298
+ return groups;
299
+ }
@@ -108,6 +108,21 @@
108
108
  text-underline-offset: 0.16em;
109
109
  }
110
110
 
111
+
112
+ .hraness-marketing-page :where(p, li, dd, dt, figcaption) a:not([role="button"], [data-emphasis], [data-hraness-marketing], [data-foil]),
113
+ .hraness-marketing-maker__links a {
114
+ text-decoration-line: underline;
115
+ text-decoration-style: dotted;
116
+ text-decoration-color: color-mix(in srgb, currentColor 40%, transparent);
117
+ text-decoration-thickness: 1px;
118
+ text-underline-offset: 0.2em;
119
+ }
120
+
121
+ .hraness-marketing-page :where(p, li, dd, dt, figcaption) a:not([role="button"], [data-emphasis], [data-hraness-marketing], [data-foil]):is(:hover, :focus-visible),
122
+ .hraness-marketing-maker__links a:is(:hover, :focus-visible) {
123
+ text-decoration-color: currentColor;
124
+ }
125
+
111
126
  .hraness-marketing-page :where(code, kbd, pre) {
112
127
  font-family: var(--hraness-marketing-mono-font);
113
128
  }
@@ -431,3 +446,17 @@ html:has(:is(.hraness-marketing-header, .hraness-marketing-header-surface):not([
431
446
  }
432
447
 
433
448
  }
449
+
450
+ /* Product-owned SVG content grammar; the diagram's React frame stays compiled. */
451
+ @layer components.hraness-design-kit.legacy {
452
+ .hraness-diagram .hraness-diagram__label { fill: var(--foreground); font-family: var(--font-text); font-size: 20px; font-weight: 500; }
453
+ .hraness-diagram .hraness-diagram__detail { fill: var(--muted); font-family: var(--font-text); font-size: 16px; font-weight: 400; }
454
+ .hraness-diagram .hraness-diagram__node { fill: var(--background); stroke: var(--line); stroke-width: 1; }
455
+ .hraness-diagram .hraness-diagram__connector,
456
+ .hraness-diagram .hraness-diagram__icon { color: var(--primary); fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
457
+ @media (forced-colors: active) {
458
+ .hraness-diagram .hraness-diagram__label, .hraness-diagram .hraness-diagram__detail { fill: CanvasText; }
459
+ .hraness-diagram .hraness-diagram__node { fill: Canvas; stroke: CanvasText; }
460
+ .hraness-diagram .hraness-diagram__connector, .hraness-diagram .hraness-diagram__icon { color: CanvasText; }
461
+ }
462
+ }
@@ -101,6 +101,21 @@
101
101
  text-underline-offset: 0.16em;
102
102
  }
103
103
 
104
+
105
+ .hraness-marketing-page :where(p, li, dd, dt, figcaption) a:not([role="button"], [data-emphasis], [data-hraness-marketing], [data-foil]),
106
+ .hraness-marketing-maker__links a {
107
+ text-decoration-line: underline;
108
+ text-decoration-style: dotted;
109
+ text-decoration-color: color-mix(in srgb, currentColor 40%, transparent);
110
+ text-decoration-thickness: 1px;
111
+ text-underline-offset: 0.2em;
112
+ }
113
+
114
+ .hraness-marketing-page :where(p, li, dd, dt, figcaption) a:not([role="button"], [data-emphasis], [data-hraness-marketing], [data-foil]):is(:hover, :focus-visible),
115
+ .hraness-marketing-maker__links a:is(:hover, :focus-visible) {
116
+ text-decoration-color: currentColor;
117
+ }
118
+
104
119
  .hraness-marketing-page :where(code, kbd, pre) {
105
120
  font-family: var(--hraness-marketing-mono-font);
106
121
  }
@@ -749,6 +764,8 @@ html:has(:is(.hraness-marketing-header, .hraness-marketing-header-surface):not([
749
764
  line-height: var(--hraness-marketing-summary-leading, 1.6);
750
765
  }
751
766
 
767
+ .hraness-marketing-hero__install { inline-size: min(100%, 44rem); min-inline-size: 0; margin-block-start: 0.75rem; text-align: start; }
768
+
752
769
  .hraness-marketing-hero__example {
753
770
  max-inline-size: var(--hraness-marketing-example-measure, var(--hraness-marketing-copy-measure));
754
771
  margin-block-start: -0.35rem;
@@ -1029,6 +1046,11 @@ html:has(:is(.hraness-marketing-header, .hraness-marketing-header-surface):not([
1029
1046
  line-height: 1.5;
1030
1047
  }
1031
1048
 
1049
+ .hraness-marketing-pillars[data-presentation="benefits"] { border-block: 0; gap: 2.75rem 3rem; }
1050
+ .hraness-marketing-pillars[data-presentation="benefits"] > div { border: 0; padding: 0; gap: 0.75rem; }
1051
+ .hraness-marketing-pillars__icon { display: block; inline-size: 1.5rem; block-size: 1.5rem; margin-block-end: 1.1rem; color: var(--primary); }
1052
+ .hraness-marketing-pillars__icon > svg { inline-size: 100%; block-size: 100%; }
1053
+
1032
1054
  /* Product frame */
1033
1055
 
1034
1056
  .hraness-marketing-proof-frame {
@@ -1345,12 +1367,28 @@ html:has(:is(.hraness-marketing-header, .hraness-marketing-header-surface):not([
1345
1367
  max-inline-size: none;
1346
1368
  }
1347
1369
 
1348
- /* Related: labeled tiers of sibling cards */
1370
+ /* Studio products use the portfolio category rule and compact mark rows. */
1371
+
1372
+ .hraness-marketing-related__groups {
1373
+ display: grid;
1374
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, 24rem), 1fr));
1375
+ gap: 2.5rem 3.5rem;
1376
+ align-items: start;
1377
+ }
1349
1378
 
1350
1379
  .hraness-marketing-related__group {
1380
+ --hraness-marketing-related-tone: var(--hraness-marketing-ink);
1351
1381
  display: grid;
1352
- gap: 1.5rem;
1382
+ gap: 1rem;
1353
1383
  min-inline-size: 0;
1384
+ border-block-start: 2px solid var(--hraness-marketing-related-tone);
1385
+ padding-block-start: 1rem;
1386
+ }
1387
+
1388
+ @media (forced-colors: active) {
1389
+ .hraness-marketing-related__group {
1390
+ --hraness-marketing-related-tone: CanvasText;
1391
+ }
1354
1392
  }
1355
1393
 
1356
1394
  .hraness-marketing-related__group-header {
@@ -1359,93 +1397,151 @@ html:has(:is(.hraness-marketing-header, .hraness-marketing-header-surface):not([
1359
1397
  gap: 0.5rem;
1360
1398
  }
1361
1399
 
1362
- /* Tier headings take the third heading size so a display face never renders
1363
- * at caption size. */
1364
1400
  .hraness-marketing-related__group-heading {
1365
1401
  margin: 0;
1366
1402
  color: var(--hraness-marketing-ink);
1367
- font-family: var(--hraness-marketing-heading-font);
1368
- font-weight: var(--hraness-marketing-heading-weight);
1369
- letter-spacing: var(--hraness-marketing-h2-tracking, -0.012em);
1370
- line-height: var(--hraness-marketing-h3-leading, 1.15);
1403
+ font-family: var(--hraness-marketing-text-font);
1404
+ font-weight: 600;
1405
+ letter-spacing: -0.01em;
1406
+ line-height: 1.35;
1371
1407
  text-wrap: balance;
1372
- font-size: var(--hraness-marketing-h3-size, clamp(1.75rem, 1.35rem + 1.6vw, 2.5rem));
1408
+ font-size: 1.125rem;
1373
1409
  }
1374
1410
 
1375
1411
  .hraness-marketing-related__group-summary {
1376
1412
  margin: 0;
1377
1413
  color: var(--hraness-marketing-muted);
1378
1414
  font-family: var(--hraness-marketing-text-font);
1379
- font-size: 1.125rem;
1415
+ font-size: 1rem;
1380
1416
  line-height: 1.5;
1381
1417
  }
1382
1418
 
1383
- /* A related product is one linked row, as on the hraness.com project index:
1384
- * its mark, its name, and its one-line description. Names and descriptions
1385
- * stay in the text face at reading sizes. */
1419
+ .hraness-marketing-related__list {
1420
+ --hraness-marketing-grid-columns: initial;
1421
+ --_hraness-marketing-grid-track: max(16rem, calc((100% - (var(--hraness-marketing-grid-columns) - 1) * 0.5rem) / var(--hraness-marketing-grid-columns)));
1422
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--_hraness-marketing-grid-track, 100%)), 1fr));
1423
+ display: grid;
1424
+ gap: 0.5rem;
1425
+ list-style: none;
1426
+ margin: 0;
1427
+ padding: 0;
1428
+ min-inline-size: 0;
1429
+ }
1430
+
1431
+ .hraness-marketing-related__item {
1432
+ margin: 0;
1433
+ min-inline-size: 0;
1434
+ }
1435
+
1386
1436
  .hraness-marketing-related__card {
1387
1437
  display: flex;
1388
1438
  flex-direction: row;
1389
- align-items: center;
1439
+ align-items: flex-start;
1390
1440
  position: relative;
1391
- isolation: isolate;
1392
1441
  min-inline-size: 0;
1393
- min-block-size: 100%;
1394
- align-self: stretch;
1395
- gap: 1rem;
1396
- padding: clamp(1rem, 2vw, 1.25rem) clamp(1.125rem, 2.5vw, 1.5rem);
1397
- border: var(--hraness-marketing-surface-rule);
1398
- border-radius: var(--hraness-marketing-frame-radius);
1399
- background: var(--hraness-marketing-surface);
1400
- color: inherit;
1442
+ min-block-size: 2.75rem;
1443
+ gap: 0.85rem;
1444
+ padding: 0.55rem 0.5rem;
1445
+ margin-inline: 0;
1446
+ border: 0;
1447
+ border-radius: 0.375rem;
1448
+ background-color: transparent;
1449
+ color: var(--hraness-marketing-ink);
1401
1450
  text-decoration: none;
1402
- box-shadow: var(--hraness-marketing-surface-shadow);
1451
+ box-shadow: none;
1403
1452
  }
1404
1453
 
1405
1454
  .hraness-marketing-related__card:hover {
1406
- box-shadow: 0 0 0 1px var(--hraness-marketing-line-strong);
1455
+ background-color: color-mix(in srgb, var(--hraness-marketing-related-tone, var(--hraness-marketing-ink)) 6%, transparent);
1407
1456
  }
1408
1457
 
1409
1458
  .hraness-marketing-related__card:focus-visible {
1410
- outline: 2px solid var(--hraness-marketing-accent);
1411
- outline-offset: 2px;
1459
+ outline: 2px solid var(--hraness-marketing-ink);
1460
+ outline-offset: -2px;
1412
1461
  }
1413
1462
 
1414
1463
  .hraness-marketing-related__card-mark {
1464
+ --hraness-foil-text-base: var(--hraness-marketing-related-tone, var(--hraness-marketing-ink));
1465
+ color: var(--hraness-marketing-related-tone, var(--hraness-marketing-ink));
1466
+ flex-grow: 0;
1467
+ flex-shrink: 0;
1468
+ flex-basis: auto;
1415
1469
  display: inline-flex;
1416
- flex: 0 0 auto;
1417
1470
  align-items: center;
1418
1471
  justify-content: center;
1419
- inline-size: 2.75rem;
1420
- block-size: 2.75rem;
1472
+ inline-size: 1.75rem;
1473
+ block-size: 1.75rem;
1474
+ margin-block-start: 0.1rem;
1421
1475
  }
1422
1476
 
1423
1477
  .hraness-marketing-related__card-text {
1424
1478
  display: grid;
1425
- gap: 0.25rem;
1479
+ gap: 0.05rem;
1426
1480
  min-inline-size: 0;
1427
1481
  }
1428
1482
 
1483
+ .hraness-marketing-related__card-heading {
1484
+ display: flex;
1485
+ flex-wrap: wrap;
1486
+ align-items: baseline;
1487
+ column-gap: 0.6rem;
1488
+ }
1489
+
1429
1490
  .hraness-marketing-related__card-name {
1430
1491
  margin: 0;
1431
1492
  color: var(--hraness-marketing-ink);
1432
1493
  font-family: var(--hraness-marketing-text-font);
1433
- font-size: 1.25rem;
1434
- font-weight: 700;
1494
+ font-size: 1.0625rem;
1495
+ font-weight: 600;
1435
1496
  letter-spacing: -0.01em;
1436
- line-height: 1.25;
1497
+ line-height: 1.35;
1498
+ overflow-wrap: anywhere;
1499
+ text-decoration-line: underline;
1500
+ text-decoration-style: dotted;
1501
+ text-decoration-color: color-mix(in srgb, currentColor 40%, transparent);
1502
+ text-decoration-thickness: 1px;
1503
+ text-underline-offset: 0.2em;
1504
+ }
1505
+
1506
+ .hraness-marketing-related__card-domain {
1507
+ color: var(--hraness-marketing-muted);
1508
+ font-family: var(--hraness-marketing-text-font);
1509
+ font-size: 0.8125rem;
1510
+ font-weight: 400;
1511
+ line-height: 1.5;
1437
1512
  overflow-wrap: anywhere;
1438
1513
  }
1439
1514
 
1440
1515
  .hraness-marketing-related__card-role {
1441
1516
  margin: 0;
1442
- color: var(--hraness-marketing-ink);
1517
+ color: var(--hraness-marketing-muted);
1443
1518
  font-family: var(--hraness-marketing-text-font);
1444
- font-size: 1.0625rem;
1519
+ font-size: 0.9375rem;
1445
1520
  line-height: 1.45;
1521
+ overflow-wrap: anywhere;
1446
1522
  text-wrap: pretty;
1447
1523
  }
1448
1524
 
1525
+ .hraness-marketing-related__group[data-tone="rose"] {
1526
+ --hraness-marketing-related-tone: var(--hraness-studio-rose);
1527
+ }
1528
+
1529
+ .hraness-marketing-related__group[data-tone="indigo"] {
1530
+ --hraness-marketing-related-tone: var(--hraness-studio-indigo);
1531
+ }
1532
+
1533
+ .hraness-marketing-related__group[data-tone="amber"] {
1534
+ --hraness-marketing-related-tone: var(--hraness-studio-amber);
1535
+ }
1536
+
1537
+ .hraness-marketing-related__group[data-tone="emerald"] {
1538
+ --hraness-marketing-related-tone: var(--hraness-studio-emerald);
1539
+ }
1540
+
1541
+ @media (forced-colors: active) {
1542
+ .hraness-marketing-related__group[data-tone] { --hraness-marketing-related-tone: CanvasText; }
1543
+ }
1544
+
1449
1545
  /* Primitives: numbered building blocks */
1450
1546
 
1451
1547
  .hraness-marketing-primitives {
@@ -1513,11 +1609,13 @@ html:has(:is(.hraness-marketing-header, .hraness-marketing-header-surface):not([
1513
1609
 
1514
1610
  .hraness-marketing-interface-grid,
1515
1611
  .hraness-marketing-trust-grid {
1612
+ --hraness-marketing-grid-columns: initial;
1613
+ --_hraness-marketing-grid-track: max(16rem, calc((100% - (var(--hraness-marketing-grid-columns) - 1) * 1rem) / var(--hraness-marketing-grid-columns)));
1516
1614
  display: grid;
1517
1615
  align-items: stretch;
1518
1616
  margin: 0;
1519
1617
  gap: 1rem;
1520
- grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
1618
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--_hraness-marketing-grid-track, 16rem)), 1fr));
1521
1619
  }
1522
1620
 
1523
1621
  .hraness-marketing-interface-grid > *,
@@ -1718,7 +1816,7 @@ html:has(:is(.hraness-marketing-header, .hraness-marketing-header-surface):not([
1718
1816
  }
1719
1817
 
1720
1818
  .hraness-marketing-quote figcaption a:hover {
1721
- text-decoration: underline;
1819
+ text-decoration-line: underline;
1722
1820
  }
1723
1821
 
1724
1822
  /* Pricing */
@@ -2124,7 +2222,6 @@ html:has(:is(.hraness-marketing-header, .hraness-marketing-header-surface):not([
2124
2222
  .hraness-marketing-trust-item,
2125
2223
  .hraness-marketing-card,
2126
2224
  .hraness-marketing-card__art,
2127
- .hraness-marketing-related__card,
2128
2225
  .hraness-marketing-quote,
2129
2226
  .hraness-marketing-plan {
2130
2227
  border: 1px solid CanvasText;
@@ -512,10 +512,10 @@ export type ComparisonRow = Readonly<{
512
512
 
513
513
  const COMPARISON_TEXT = { yes: "Yes", no: "No", partial: "Partly" } as const;
514
514
 
515
- function ComparisonGlyph({ kind }: Readonly<{ kind: "yes" | "no" | "partial" }>) {
515
+ export function ComparisonGlyph({ kind, className }: Readonly<{ kind: "yes" | "no" | "partial"; className?: string }>) {
516
516
  const path = kind === "yes" ? "M3.5 8.5l3 3 6-7" : kind === "no" ? "M4.5 4.5l7 7m0-7l-7 7" : "M4 8h8";
517
517
  return (
518
- <svg aria-hidden="true" className="plain-publication__comparison-glyph" focusable="false" height="16" viewBox="0 0 16 16" width="16">
518
+ <svg aria-hidden="true" className={joinClasses("plain-publication__comparison-glyph", className)} focusable="false" height="16" viewBox="0 0 16 16" width="16">
519
519
  <path d={path} fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.75" />
520
520
  </svg>
521
521
  );
@@ -1,5 +1,8 @@
1
1
  "use client";
2
2
 
3
+ import { MarketingAccount, MarketingAccountActions } from "./marketing-account.js";
4
+ import { MarketingComparison } from "./marketing-comparison.js";
5
+ import { MarketingDiagram, DiagramArrowhead } from "./marketing-diagram.js";
3
6
  import {
4
7
  Badge,
5
8
  Button,
@@ -343,6 +346,7 @@ export function DesignSystemGallery({
343
346
  />
344
347
  <MarketingPillars
345
348
  ariaLabel="Relay in three points"
349
+ presentation="benefits"
346
350
  columns={3}
347
351
  pillars={[
348
352
  { label: "No hosted service", summary: "Jobs run on your machine and never wait on a server." },
@@ -436,7 +440,7 @@ export function DesignSystemGallery({
436
440
  <MarketingSectionLabel size="body">Reference</MarketingSectionLabel>
437
441
  <p>Consumer-owned content can include <a href="#gallery-install">links</a> and <code>inline code</code>.</p>
438
442
  </MarketingSection>
439
- <MarketingInterfaceGrid heading="Choose your interface." headingId="gallery-marketing-interfaces" headingLevel={3} label="Interfaces" interfaces={[
443
+ <MarketingInterfaceGrid columns={2} heading="Choose your interface." headingId="gallery-marketing-interfaces" headingLevel={3} label="Interfaces" interfaces={[
440
444
  { label: "CLI", summary: "Run a named job.", example: <MarketingCodeBlock code="relay run job-01" /> },
441
445
  { label: "SDK", summary: "Use typed application code." },
442
446
  ]} />
@@ -444,7 +448,7 @@ export function DesignSystemGallery({
444
448
  { art: <svg aria-hidden="true" viewBox="0 0 24 24" width="24" height="24"><circle cx="12" cy="12" r="8" /></svg>, href: "#marketing", title: "Grok 4.7", meta: "First observed 21 September 2026." },
445
449
  { art: <svg aria-hidden="true" viewBox="0 0 24 24" width="24" height="24"><rect x="4" y="4" width="16" height="16" rx="4" /></svg>, href: "#gallery-install", title: "GLM 5.3 Flash", meta: "First observed 26 August 2026. Early DeepSWE coverage on OpenRouter." },
446
450
  ]} />
447
- <MarketingTrustBoundary heading="What leaves your machine." headingId="gallery-marketing-trust" headingLevel={3} label="Boundary" items={[
451
+ <MarketingTrustBoundary columns={2} heading="What leaves your machine." headingId="gallery-marketing-trust" headingLevel={3} label="Boundary" items={[
448
452
  { label: "Stays local", detail: "Source files and credentials." },
449
453
  { label: "Shared", detail: "Only the logs you choose to sync." },
450
454
  ]} />
@@ -520,42 +524,67 @@ export function DesignSystemGallery({
520
524
  ]}
521
525
  />
522
526
  <MarketingRelated
527
+ columns={2}
523
528
  groups={[
524
529
  {
525
- heading: "Sibling tools",
530
+ heading: "Relationships",
531
+ tone: "rose",
526
532
  headingId: "design-gallery-related-tools",
527
533
  items: [
528
534
  {
529
535
  href: "#gallery-install",
530
536
  name: "Ledger",
537
+ domain: "ledger.example",
531
538
  role: "Long-term storage for run logs",
532
539
  },
533
540
  {
534
541
  href: "#marketing",
535
542
  name: "Index",
543
+ domain: "index.example",
536
544
  role: "A local search index",
537
545
  },
538
546
  ],
539
547
  },
540
548
  {
541
- heading: "Shared infrastructure",
549
+ columns: 1,
550
+ heading: "Knowledge",
551
+ tone: "indigo",
542
552
  headingId: "design-gallery-related-infra",
543
553
  items: [
544
554
  {
545
555
  href: "#marketing",
546
556
  name: "Relay",
557
+ domain: "relay.example",
547
558
  role: "The shared job runner",
548
559
  },
549
560
  ],
550
- summary: "The runner the other tools depend on.",
551
561
  },
552
562
  ]}
553
- heading="Related tools."
563
+ heading="Other tools from our studio"
554
564
  headingId="design-gallery-related-title"
555
565
  headingLevel={3}
556
- label="Related"
557
- summary="Each is a separate release. Its card says how it works with Relay."
558
566
  />
567
+ <MarketingDiagram width={720} height={216} label="Your sources stay on your device; your chosen agent reads the local workspace.">
568
+ <defs><DiagramArrowhead id="gallery-diagram-arrow" /></defs>
569
+ <rect className="hraness-diagram__node" x={24} y={40} width={276} height={136} rx={12} />
570
+ <rect className="hraness-diagram__node" x={420} y={40} width={276} height={136} rx={12} />
571
+ <path className="hraness-diagram__icon" d="M48 66h18v14H48z M53 84h8 M57 80v4" />
572
+ <text className="hraness-diagram__label" x={48} y={116}>Your sources</text>
573
+ <text className="hraness-diagram__detail" x={48} y={143}>Stored on your device</text>
574
+ <path className="hraness-diagram__connector" d="M320 108H400" markerEnd="url(#gallery-diagram-arrow)" />
575
+ <path className="hraness-diagram__icon" d="M444 73h18 M453 64v18 M447 67l12 12 M459 67l-12 12" />
576
+ <text className="hraness-diagram__label" x={444} y={116}>Your chosen agent</text>
577
+ <text className="hraness-diagram__detail" x={444} y={143}>Reads the local workspace</text>
578
+ </MarketingDiagram>
579
+ <MarketingComparison caption="Choose where your work lives" options={[{ name: "Relay" }, { name: "Hosted workspace" }]} highlight={0} rows={[
580
+ { label: "Works offline", values: [true, false] },
581
+ { label: "Bring your model", values: [true, { status: "optional", label: "On some plans" }] },
582
+ { label: "Local storage", values: [true, { status: "depends", detail: "Check the provider's export format." }] },
583
+ { label: "Cost", values: ["Free locally", "Subscription"] },
584
+ ]} note="An illustrative comparison. Product pages supply current facts and sources." />
585
+ <MarketingAccount id="gallery-account" summary="Keep your preferences and shared work together.">
586
+ <MarketingAccountActions primary={{ href: "#gallery-account", label: "Create account" }} signIn={{ href: "#gallery-account" }} />
587
+ </MarketingAccount>
559
588
  <MarketingMaker
560
589
  heading="Who builds Relay"
561
590
  headingId="design-gallery-maker-title"
@@ -26,6 +26,8 @@ export * from "./procedural-recipe.js";
26
26
  export * from "./relative-time.js";
27
27
  export * from "./production-data-preview-notice.js";
28
28
  export * from "./product-marketing.js";
29
+ export * from "./marketing-account.js";
30
+ export * from "./marketing-comparison.js";
29
31
  export * from "./route-state.js";
30
32
  export * from "./sticky-offset.js";
31
33
  export * from "./surfaces.js";
@@ -37,3 +39,4 @@ export * from "./launch-beats.js";
37
39
  export * from "./social-kit-panel.js";
38
40
  export * from "./platform-icons.js";
39
41
  export * from "./platform-install.js";
42
+ export * from "./marketing-diagram.js";
@@ -0,0 +1,74 @@
1
+ import * as stylex from "@stylexjs/stylex";
2
+
3
+ export const marketingAccountStyles = stylex.create({
4
+ section: {
5
+ alignItems: "center",
6
+ "border-top": "var(--hraness-marketing-rule, 1px solid var(--line))",
7
+ color: "var(--foreground)",
8
+ display: "grid",
9
+ gap: "2rem 4rem",
10
+ gridTemplateColumns: { default: "minmax(0, 1fr)", "@media (min-width: 50rem)": "minmax(0, 1fr) minmax(0, 1fr)" },
11
+ paddingBlock: "clamp(2.5rem, 6vw, 4rem)",
12
+ scrollMarginTop: "calc(var(--hraness-sticky-offset, 5rem) + 2rem)",
13
+ },
14
+ copy: { minWidth: 0 },
15
+ heading: {
16
+ fontFamily: "var(--font-text)",
17
+ fontSize: "clamp(1.625rem, 1.3rem + 1vw, 2.25rem)",
18
+ fontWeight: 550,
19
+ letterSpacing: "-0.02em",
20
+ lineHeight: 1.15,
21
+ margin: 0,
22
+ textWrap: "balance",
23
+ },
24
+ summary: {
25
+ color: "var(--muted)",
26
+ fontSize: "1rem",
27
+ lineHeight: 1.6,
28
+ marginTop: "1rem",
29
+ maxWidth: "42ch",
30
+ textWrap: "pretty",
31
+ },
32
+ content: { minWidth: 0 },
33
+ actions: { alignItems: "center", display: "flex", flexWrap: "wrap", gap: "0.75rem 1.5rem" },
34
+ primary: {
35
+ alignItems: "center",
36
+ backgroundColor: { default: "var(--primary)", ":hover": "color-mix(in oklch, var(--primary) 88%, var(--foreground))", "@media (forced-colors: active)": "ButtonFace" },
37
+ borderWidth: "1px",
38
+ borderStyle: "solid",
39
+ borderColor: { default: "transparent", "@media (forced-colors: active)": "ButtonText" },
40
+ borderRadius: "0.625rem",
41
+ color: { default: "var(--primary-foreground)", "@media (forced-colors: active)": "ButtonText" },
42
+ display: "inline-flex",
43
+ fontFamily: "var(--font-text)",
44
+ fontSize: "1.0625rem",
45
+ fontWeight: 550,
46
+ justifyContent: "center",
47
+ lineHeight: 1.3,
48
+ maxWidth: "100%",
49
+ minHeight: "3.5rem",
50
+ outline: { default: null, ":focus-visible": "2px solid var(--primary)" },
51
+ outlineOffset: { default: null, ":focus-visible": "3px" },
52
+ paddingBlock: "0.875rem",
53
+ paddingInline: "1.5rem",
54
+ textAlign: "center",
55
+ textDecoration: "none",
56
+ },
57
+ signIn: {
58
+ alignItems: "center",
59
+ color: "var(--foreground)",
60
+ display: "inline-flex",
61
+ fontFamily: "var(--font-text)",
62
+ fontSize: "1.0625rem",
63
+ fontWeight: 450,
64
+ lineHeight: 1.3,
65
+ minHeight: "3.5rem",
66
+ outline: { default: null, ":focus-visible": "2px solid var(--primary)" },
67
+ outlineOffset: { default: null, ":focus-visible": "3px" },
68
+ textDecorationLine: "underline",
69
+ textDecorationStyle: "dotted",
70
+ textDecorationColor: { default: "color-mix(in srgb, currentColor 45%, transparent)", ":hover": "currentColor", "@media (forced-colors: active)": "LinkText" },
71
+ textDecorationThickness: "1px",
72
+ textUnderlineOffset: "0.22em",
73
+ },
74
+ });