@hraness/design-kit 0.34.0 → 0.35.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/AGENT_SETUP.md +4 -0
- package/ARTICLE_COPY.md +12 -6
- package/README.md +20 -2
- package/dist/{chunk-nwpcjm48.js → chunk-6bfr0g23.js} +1115 -441
- package/dist/{chunk-h4k7yv6x.js → chunk-6ts4955n.js} +36 -36
- package/dist/chunk-gmea6p0d.js +13 -0
- package/dist/{chunk-mr1vdcjq.js → chunk-tzd7177a.js} +142 -82
- package/dist/index.js +5 -1
- package/dist/mockups/client.js +50 -15
- package/dist/portfolio.js +586 -85
- package/dist/react/index.js +169 -105
- package/dist/react/platform-install.js +3 -2
- package/dist/react/server.js +17 -3
- package/dist/stylex-manifest.json +1 -1
- package/dist/stylex.css +376 -58
- package/package.json +16 -3
- package/portfolio-inventory.json +1 -1
- package/src/diagrams.ts +11 -0
- package/src/index.ts +2 -0
- package/src/marketing-comparison.css +162 -0
- package/src/marketing-diagram.css +24 -0
- package/src/mockups/client.tsx +25 -17
- package/src/mockups.css +19 -0
- package/src/plain-publication.css +9 -6
- package/src/portfolio.generated.json +578 -86
- package/src/portfolio.generated.ts +578 -86
- package/src/portfolio.ts +73 -0
- package/src/product-marketing-foundation.css +29 -0
- package/src/product-marketing.css +133 -38
- package/src/react/agent-setup-prompt.stylex.ts +5 -25
- package/src/react/agent-setup-prompt.tsx +9 -18
- package/src/react/article.tsx +2 -2
- package/src/react/design-gallery.tsx +34 -6
- package/src/react/index.ts +3 -0
- package/src/react/marketing-account.stylex.ts +74 -0
- package/src/react/marketing-account.tsx +69 -0
- package/src/react/marketing-comparison.stylex.ts +24 -0
- package/src/react/marketing-comparison.tsx +73 -0
- package/src/react/marketing-diagram.stylex.ts +8 -0
- package/src/react/marketing-diagram.tsx +33 -0
- package/src/react/platform-install.stylex.ts +9 -37
- package/src/react/platform-install.tsx +17 -24
- package/src/react/product-marketing.stylex.ts +138 -109
- package/src/react/product-marketing.tsx +69 -43
- package/src/react/server.ts +3 -0
- package/src/react/setup-copy-button.stylex.ts +39 -0
- package/src/react/setup-copy-button.tsx +20 -0
- package/src/styles.css +2 -0
- package/src/typography.css +32 -0
- /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
|
-
/*
|
|
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:
|
|
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-
|
|
1368
|
-
font-weight:
|
|
1369
|
-
letter-spacing:
|
|
1370
|
-
line-height:
|
|
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:
|
|
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:
|
|
1415
|
+
font-size: 1rem;
|
|
1380
1416
|
line-height: 1.5;
|
|
1381
1417
|
}
|
|
1382
1418
|
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
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:
|
|
1439
|
+
align-items: flex-start;
|
|
1390
1440
|
position: relative;
|
|
1391
|
-
isolation: isolate;
|
|
1392
1441
|
min-inline-size: 0;
|
|
1393
|
-
min-block-size:
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
border:
|
|
1398
|
-
border-radius:
|
|
1399
|
-
background:
|
|
1400
|
-
color:
|
|
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:
|
|
1451
|
+
box-shadow: none;
|
|
1403
1452
|
}
|
|
1404
1453
|
|
|
1405
1454
|
.hraness-marketing-related__card:hover {
|
|
1406
|
-
|
|
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-
|
|
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:
|
|
1420
|
-
block-size:
|
|
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.
|
|
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.
|
|
1434
|
-
font-weight:
|
|
1494
|
+
font-size: 1.0625rem;
|
|
1495
|
+
font-weight: 600;
|
|
1435
1496
|
letter-spacing: -0.01em;
|
|
1436
|
-
line-height: 1.
|
|
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-
|
|
1517
|
+
color: var(--hraness-marketing-muted);
|
|
1443
1518
|
font-family: var(--hraness-marketing-text-font);
|
|
1444
|
-
font-size:
|
|
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 {
|
|
@@ -1720,7 +1816,7 @@ html:has(:is(.hraness-marketing-header, .hraness-marketing-header-surface):not([
|
|
|
1720
1816
|
}
|
|
1721
1817
|
|
|
1722
1818
|
.hraness-marketing-quote figcaption a:hover {
|
|
1723
|
-
text-decoration: underline;
|
|
1819
|
+
text-decoration-line: underline;
|
|
1724
1820
|
}
|
|
1725
1821
|
|
|
1726
1822
|
/* Pricing */
|
|
@@ -2126,7 +2222,6 @@ html:has(:is(.hraness-marketing-header, .hraness-marketing-header-surface):not([
|
|
|
2126
2222
|
.hraness-marketing-trust-item,
|
|
2127
2223
|
.hraness-marketing-card,
|
|
2128
2224
|
.hraness-marketing-card__art,
|
|
2129
|
-
.hraness-marketing-related__card,
|
|
2130
2225
|
.hraness-marketing-quote,
|
|
2131
2226
|
.hraness-marketing-plan {
|
|
2132
2227
|
border: 1px solid CanvasText;
|
|
@@ -89,39 +89,19 @@ export const agentSetupStyles = stylex.create({
|
|
|
89
89
|
":hover": { color: ink },
|
|
90
90
|
"::marker": { content: '""' },
|
|
91
91
|
},
|
|
92
|
-
copy: {
|
|
93
|
-
alignItems: "center",
|
|
94
|
-
appearance: "none",
|
|
95
|
-
backgroundColor: { default: background, ":hover": surface, [forced]: "ButtonFace" },
|
|
96
|
-
borderColor: { default: line, [forced]: "ButtonText" },
|
|
97
|
-
borderRadius: "0.5rem",
|
|
98
|
-
borderStyle: "solid",
|
|
99
|
-
borderWidth: "1px",
|
|
100
|
-
boxSizing: "border-box",
|
|
101
|
-
color: { default: ink, [forced]: "ButtonText" },
|
|
102
|
-
cursor: "pointer",
|
|
103
|
-
display: { default: "inline-flex", [noScript]: "none" },
|
|
104
|
-
flexShrink: 0,
|
|
105
|
-
fontFamily: "inherit",
|
|
106
|
-
fontSize: "0.8125rem",
|
|
107
|
-
gap: "0.4rem",
|
|
108
|
-
justifyContent: "center",
|
|
109
|
-
lineHeight: 1,
|
|
110
|
-
margin: 0,
|
|
111
|
-
minBlockSize: "2.75rem",
|
|
112
|
-
outline: { default: "none", ":focus-visible": `2px solid ${focus}` },
|
|
113
|
-
outlineOffset: "2px",
|
|
114
|
-
padding: "0.65rem 0.8rem",
|
|
115
|
-
},
|
|
116
92
|
copyOverlay: {
|
|
117
93
|
insetBlockEnd: "0.5rem",
|
|
118
94
|
insetInlineEnd: "0.5rem",
|
|
119
95
|
position: "absolute",
|
|
120
96
|
},
|
|
121
|
-
copyIcon: { blockSize: "1rem", flexShrink: 0, inlineSize: "1rem" },
|
|
122
97
|
targets: { display: "grid", gap: "0.6rem", minInlineSize: 0 },
|
|
123
98
|
targetsLabel: { color: { default: muted, [forced]: "CanvasText" }, fontSize: "0.8125rem", fontWeight: 500, margin: 0 },
|
|
124
99
|
targetList: { display: "grid", gap: "0.45rem", gridTemplateColumns: "repeat(auto-fit, minmax(min(100%, 10rem), 1fr))", listStyle: "none", margin: 0, padding: 0 },
|
|
100
|
+
targetGrid: {
|
|
101
|
+
gridTemplateColumns: "repeat(var(--_hraness-agent-target-wide-columns, var(--_hraness-agent-target-columns, 1)), minmax(0, 1fr))",
|
|
102
|
+
"--_hraness-agent-target-columns": { default: null, "@container hraness-agent-setup (min-width: 20rem)": "2" },
|
|
103
|
+
"--_hraness-agent-target-wide-columns": { default: null, "@container hraness-agent-setup (min-width: 34rem)": "3" },
|
|
104
|
+
},
|
|
125
105
|
target: {
|
|
126
106
|
alignItems: "center",
|
|
127
107
|
backgroundColor: { default: surface, ":hover": background, [forced]: "ButtonFace" },
|
|
@@ -5,6 +5,7 @@ import { useCallback, useEffect, useId, useRef, useState, type KeyboardEvent, ty
|
|
|
5
5
|
import type { ProviderMarkDescriptor } from "../provider-marks.js";
|
|
6
6
|
import { agentSetupClassName as cx } from "./agent-setup-prompt.stylex.js";
|
|
7
7
|
import { ProviderMark } from "./provider-mark.js";
|
|
8
|
+
import { SetupCopyButton } from "./setup-copy-button.js";
|
|
8
9
|
import { SyntaxCode } from "./syntax-code.js";
|
|
9
10
|
|
|
10
11
|
export interface AgentSetupTarget {
|
|
@@ -20,6 +21,8 @@ export interface AgentSetupPromptProps {
|
|
|
20
21
|
readonly prompt: string;
|
|
21
22
|
readonly label?: string;
|
|
22
23
|
readonly targets?: readonly AgentSetupTarget[];
|
|
24
|
+
/** Keep provider actions below the prompt, or place them beside it in wide containers. */
|
|
25
|
+
readonly targetsPlacement?: "responsive" | "below";
|
|
23
26
|
readonly className?: string;
|
|
24
27
|
/** Runs once after a successful copy; callback failures do not change copy feedback. */
|
|
25
28
|
readonly onCopied?: () => void;
|
|
@@ -186,21 +189,8 @@ function useCopy(text: string, subject: string, fallback: () => HTMLElement | nu
|
|
|
186
189
|
return { state, message, copy };
|
|
187
190
|
}
|
|
188
191
|
|
|
189
|
-
function CopyGlyph({ copied }: Readonly<{ copied: boolean }>) {
|
|
190
|
-
return (
|
|
191
|
-
<svg aria-hidden="true" className={cx(["copyIcon"])} fill="none" focusable="false" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} viewBox="0 0 24 24">
|
|
192
|
-
{copied ? <path d="M5 12.5l4.5 4.5L19 7.5" /> : <><rect height="12" rx="2" width="12" x="8" y="8" /><path d="M16 8V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2" /></>}
|
|
193
|
-
</svg>
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
192
|
function CopyButton({ copy, overlay = false, state, subject }: Readonly<{ copy: () => Promise<boolean>; overlay?: boolean; state: CopyState; subject: string }>) {
|
|
198
|
-
return (
|
|
199
|
-
<button aria-busy={state === "copying" || undefined} aria-label={`${state === "copied" ? "Copied" : "Copy"} ${subject}`} className={cx(["copy", overlay && "copyOverlay"])} data-copy-state={state} disabled={state === "copying"} onClick={() => { void copy(); }} type="button">
|
|
200
|
-
<CopyGlyph copied={state === "copied"} />
|
|
201
|
-
<span>{state === "copied" ? "Copied" : state === "copying" ? "Copying" : state === "failed" ? "Copy failed" : "Copy"}</span>
|
|
202
|
-
</button>
|
|
203
|
-
);
|
|
193
|
+
return <SetupCopyButton className={cx([overlay && "copyOverlay"], "hraness-agent-setup__copy")} iconClassName="hraness-agent-setup__copyIcon" onCopy={() => { void copy(); }} state={state} subject={subject} />;
|
|
204
194
|
}
|
|
205
195
|
|
|
206
196
|
function CopyStatus({ children }: Readonly<{ children: ReactNode }>) {
|
|
@@ -231,10 +221,11 @@ function reserveTargetTab(owner: Window | null): Window | null {
|
|
|
231
221
|
}
|
|
232
222
|
|
|
233
223
|
/** A quiet prompt preview with the complete source behind a native disclosure. */
|
|
234
|
-
export function AgentSetupPrompt({ prompt, label = "Agent setup", targets = [], className, onCopied }: AgentSetupPromptProps) {
|
|
224
|
+
export function AgentSetupPrompt({ prompt, label = "Agent setup", targets = [], targetsPlacement = "responsive", className, onCopied }: AgentSetupPromptProps) {
|
|
235
225
|
assertText(prompt, "Agent setup prompt");
|
|
236
226
|
assertText(label, "Agent setup label");
|
|
237
227
|
assertEntries(targets, "Agent setup target");
|
|
228
|
+
if (targetsPlacement !== "responsive" && targetsPlacement !== "below") throw new RangeError("Unsupported agent setup targets placement.");
|
|
238
229
|
for (const target of targets) {
|
|
239
230
|
assertHref(target.href);
|
|
240
231
|
if (target.mode !== undefined && target.mode !== "prefill" && target.mode !== "copy-and-open") throw new RangeError("Unsupported agent setup target mode.");
|
|
@@ -313,8 +304,8 @@ export function AgentSetupPrompt({ prompt, label = "Agent setup", targets = [],
|
|
|
313
304
|
}, []);
|
|
314
305
|
|
|
315
306
|
return (
|
|
316
|
-
<section aria-label={label} className={cx(["root"], className)} data-hraness-agent-setup-prompt="">
|
|
317
|
-
<div className={cx(["layout", targets.length > 0 && "withTargets"])}>
|
|
307
|
+
<section aria-label={label} className={cx(["root"], className)} data-hraness-agent-setup-prompt="" data-targets-placement={targetsPlacement}>
|
|
308
|
+
<div className={cx(["layout", targets.length > 0 && targetsPlacement === "responsive" && "withTargets"])}>
|
|
318
309
|
<div className={cx(["frame"])} data-copy-state={state}>
|
|
319
310
|
<div className={cx(["preview"])} hidden={expanded}>
|
|
320
311
|
<pre aria-label={`${label} preview`} className={cx(["pre", "previewText"])}>{prompt}</pre>
|
|
@@ -330,7 +321,7 @@ export function AgentSetupPrompt({ prompt, label = "Agent setup", targets = [],
|
|
|
330
321
|
{targets.length === 0 ? null : (
|
|
331
322
|
<aside aria-labelledby={`${id}-targets`} className={cx(["targets"])}>
|
|
332
323
|
<p className={cx(["targetsLabel"])} id={`${id}-targets`}>Open in</p>
|
|
333
|
-
<ul className={cx(["targetList"])}>
|
|
324
|
+
<ul className={cx(["targetList", targetsPlacement === "below" && "targetGrid"])}>
|
|
334
325
|
{targets.map((target) => (
|
|
335
326
|
<li key={target.id}>
|
|
336
327
|
<a aria-busy={target.mode === "copy-and-open" && state === "copying" || undefined} aria-disabled={target.mode === "copy-and-open" && state === "copying" || undefined} aria-label={target.mode === "copy-and-open" ? `Copy prompt and open ${target.label}` : undefined} className={cx(["target"])} data-agent-target={target.id} data-agent-target-mode={target.mode ?? "prefill"} href={target.href} onAuxClick={target.mode === "copy-and-open" ? (event) => { copyAndOpen(event, target); } : undefined} onClick={target.mode === "copy-and-open" ? (event) => { copyAndOpen(event, target); } : undefined} rel="noopener noreferrer" target="_blank">
|
package/src/react/article.tsx
CHANGED
|
@@ -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." },
|
|
@@ -397,6 +401,7 @@ export function DesignSystemGallery({
|
|
|
397
401
|
<MarketingSection heading="Ask your agent to set it up." headingId="gallery-agent-setup" headingLevel={3} label="Agent setup">
|
|
398
402
|
<AgentSetupPrompt
|
|
399
403
|
label="Relay agent setup"
|
|
404
|
+
targetsPlacement="below"
|
|
400
405
|
prompt={"Install Relay from https://relay.example/install.sh.\nRead the project instructions before making changes.\nRun relay init in this workspace and create a job named sample.\nRun the job, then show me its log."}
|
|
401
406
|
targets={[
|
|
402
407
|
{ href: "https://claude.ai/new", id: "claude", label: "Claude", mark: "claudecode", mode: "copy-and-open" },
|
|
@@ -523,41 +528,64 @@ export function DesignSystemGallery({
|
|
|
523
528
|
columns={2}
|
|
524
529
|
groups={[
|
|
525
530
|
{
|
|
526
|
-
heading: "
|
|
531
|
+
heading: "Relationships",
|
|
532
|
+
tone: "rose",
|
|
527
533
|
headingId: "design-gallery-related-tools",
|
|
528
534
|
items: [
|
|
529
535
|
{
|
|
530
536
|
href: "#gallery-install",
|
|
531
537
|
name: "Ledger",
|
|
538
|
+
domain: "ledger.example",
|
|
532
539
|
role: "Long-term storage for run logs",
|
|
533
540
|
},
|
|
534
541
|
{
|
|
535
542
|
href: "#marketing",
|
|
536
543
|
name: "Index",
|
|
544
|
+
domain: "index.example",
|
|
537
545
|
role: "A local search index",
|
|
538
546
|
},
|
|
539
547
|
],
|
|
540
548
|
},
|
|
541
549
|
{
|
|
542
550
|
columns: 1,
|
|
543
|
-
heading: "
|
|
551
|
+
heading: "Knowledge",
|
|
552
|
+
tone: "indigo",
|
|
544
553
|
headingId: "design-gallery-related-infra",
|
|
545
554
|
items: [
|
|
546
555
|
{
|
|
547
556
|
href: "#marketing",
|
|
548
557
|
name: "Relay",
|
|
558
|
+
domain: "relay.example",
|
|
549
559
|
role: "The shared job runner",
|
|
550
560
|
},
|
|
551
561
|
],
|
|
552
|
-
summary: "The runner the other tools depend on.",
|
|
553
562
|
},
|
|
554
563
|
]}
|
|
555
|
-
heading="
|
|
564
|
+
heading="Other tools from our studio"
|
|
556
565
|
headingId="design-gallery-related-title"
|
|
557
566
|
headingLevel={3}
|
|
558
|
-
label="Related"
|
|
559
|
-
summary="Each is a separate release. Its card says how it works with Relay."
|
|
560
567
|
/>
|
|
568
|
+
<MarketingDiagram width={720} height={216} label="Your sources stay on your device; your chosen agent reads the local workspace.">
|
|
569
|
+
<defs><DiagramArrowhead id="gallery-diagram-arrow" /></defs>
|
|
570
|
+
<rect className="hraness-diagram__node" x={24} y={40} width={276} height={136} rx={12} />
|
|
571
|
+
<rect className="hraness-diagram__node" x={420} y={40} width={276} height={136} rx={12} />
|
|
572
|
+
<path className="hraness-diagram__icon" d="M48 66h18v14H48z M53 84h8 M57 80v4" />
|
|
573
|
+
<text className="hraness-diagram__label" x={48} y={116}>Your sources</text>
|
|
574
|
+
<text className="hraness-diagram__detail" x={48} y={143}>Stored on your device</text>
|
|
575
|
+
<path className="hraness-diagram__connector" d="M320 108H400" markerEnd="url(#gallery-diagram-arrow)" />
|
|
576
|
+
<path className="hraness-diagram__icon" d="M444 73h18 M453 64v18 M447 67l12 12 M459 67l-12 12" />
|
|
577
|
+
<text className="hraness-diagram__label" x={444} y={116}>Your chosen agent</text>
|
|
578
|
+
<text className="hraness-diagram__detail" x={444} y={143}>Reads the local workspace</text>
|
|
579
|
+
</MarketingDiagram>
|
|
580
|
+
<MarketingComparison caption="Choose where your work lives" options={[{ name: "Relay" }, { name: "Hosted workspace" }]} highlight={0} rows={[
|
|
581
|
+
{ label: "Works offline", values: [true, false] },
|
|
582
|
+
{ label: "Bring your model", values: [true, { status: "optional", label: "On some plans" }] },
|
|
583
|
+
{ label: "Local storage", values: [true, { status: "depends", detail: "Check the provider's export format." }] },
|
|
584
|
+
{ label: "Cost", values: ["Free locally", "Subscription"] },
|
|
585
|
+
]} note="An illustrative comparison. Product pages supply current facts and sources." />
|
|
586
|
+
<MarketingAccount id="gallery-account" summary="Keep your preferences and shared work together.">
|
|
587
|
+
<MarketingAccountActions primary={{ href: "#gallery-account", label: "Create account" }} signIn={{ href: "#gallery-account" }} />
|
|
588
|
+
</MarketingAccount>
|
|
561
589
|
<MarketingMaker
|
|
562
590
|
heading="Who builds Relay"
|
|
563
591
|
headingId="design-gallery-maker-title"
|
package/src/react/index.ts
CHANGED
|
@@ -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";
|