@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
@@ -546,6 +546,8 @@ export interface ProductHeroProps {
546
546
  readonly heading: string;
547
547
  readonly headingId: string;
548
548
  readonly headingLevel?: MarketingHeadingLevel;
549
+ /** An install command or installer selector directly below the summary. */
550
+ readonly install?: ReactNode;
549
551
  readonly name: string;
550
552
  /** Product-owned notice after the copy boundary, without an added wrapper. */
551
553
  readonly notice?: ReactNode;
@@ -571,6 +573,7 @@ export function ProductHero({
571
573
  heading,
572
574
  headingId,
573
575
  headingLevel = 1,
576
+ install,
574
577
  name,
575
578
  notice,
576
579
  proof,
@@ -587,11 +590,12 @@ export function ProductHero({
587
590
  >
588
591
  <div className={classNames("hraness-marketing-hero__copy", undefined, align === "start" ? "start" : "default")}>
589
592
  {eyebrow === undefined || eyebrow === "" ? null : <p className={classNames("hraness-marketing-hero__eyebrow", undefined, tone === "accent" ? "accent" : "default")}>{eyebrow}</p>}
590
- <p className={classNames("hraness-marketing-hero__name")}>{name}</p>
593
+ {name === "" ? null : <p className={classNames("hraness-marketing-hero__name")}>{name}</p>}
591
594
  <Heading className={classNames("hraness-marketing-hero__heading")} id={headingId} level={headingLevel}>
592
595
  {heading}
593
596
  </Heading>
594
597
  <p className={classNames("hraness-marketing-hero__summary")}>{summary}</p>
598
+ {install === undefined ? null : <div className={classNames("hraness-marketing-hero__install")}>{install}</div>}
595
599
  {example === undefined
596
600
  ? null
597
601
  : <p className={classNames("hraness-marketing-hero__example")}>{example}</p>}
@@ -627,6 +631,7 @@ export function ProductHero({
627
631
  }
628
632
 
629
633
  export interface MarketingPillar {
634
+ readonly icon?: ReactNode;
630
635
  readonly label: string;
631
636
  readonly summary: string;
632
637
  }
@@ -636,25 +641,28 @@ export function MarketingPillars({
636
641
  className,
637
642
  columns,
638
643
  pillars,
644
+ presentation = "columns",
639
645
  }: Readonly<{
640
646
  ariaLabel: string;
641
647
  className?: string;
642
648
  /** Static desktop columns without inline styles; omission uses the collection length. Mobile stays one column. */
643
649
  columns?: MarketingColumnCount;
644
650
  pillars: readonly MarketingPillar[];
651
+ presentation?: "columns" | "benefits";
645
652
  }>) {
646
- const rootClassName = marketingColumnClassName("hraness-marketing-pillars", className, columns);
653
+ const rootClassName = marketingColumnClassName("hraness-marketing-pillars", className, columns, presentation === "benefits" ? "benefits" : "default");
647
654
  if (pillars.length === 0) return null;
648
655
  return (
649
656
  <dl
650
657
  aria-label={ariaLabel}
651
658
  className={rootClassName}
652
659
  data-hraness-marketing="pillars"
660
+ data-presentation={presentation}
653
661
  style={columns === undefined ? { "--hraness-marketing-pillar-columns": String(pillars.length) } as Record<string, string> : undefined}
654
662
  >
655
663
  {pillars.map((pillar, index) => (
656
- <div className={classNames("hraness-marketing-pillars__item", undefined, index === 0 ? "default" : "later")} key={pillar.label}>
657
- <dt className={classNames("hraness-marketing-pillars__label")}>{pillar.label}</dt>
664
+ <div className={classNames("hraness-marketing-pillars__item", undefined, presentation === "benefits" ? "benefit" : index === 0 ? "default" : "later")} key={pillar.label}>
665
+ <dt className={classNames("hraness-marketing-pillars__label")}>{pillar.icon === undefined ? null : <span aria-hidden="true" className={classNames("hraness-marketing-pillars__icon")}>{pillar.icon}</span>}{pillar.label}</dt>
658
666
  <dd className={classNames("hraness-marketing-pillars__summary")}>{pillar.summary}</dd>
659
667
  </div>
660
668
  ))}
@@ -1148,6 +1156,7 @@ export interface MarketingInterface {
1148
1156
 
1149
1157
  export function MarketingInterfaceGrid({
1150
1158
  className,
1159
+ columns,
1151
1160
  heading,
1152
1161
  headingId,
1153
1162
  headingLevel = 2,
@@ -1157,6 +1166,8 @@ export function MarketingInterfaceGrid({
1157
1166
  summary,
1158
1167
  }: Readonly<{
1159
1168
  className?: string;
1169
+ /** Maximum columns; cards wrap within the available container width. */
1170
+ columns?: MarketingColumnCount;
1160
1171
  heading: string;
1161
1172
  headingId: string;
1162
1173
  headingLevel?: MarketingHeadingLevel;
@@ -1173,7 +1184,7 @@ export function MarketingInterfaceGrid({
1173
1184
  id={id}
1174
1185
  >
1175
1186
  <MarketingCollectionHeader {...{ heading, headingId, headingLevel, label, summary }} prefix="interfaces" />
1176
- <div className={classNames("hraness-marketing-interface-grid")}>
1187
+ <div className={marketingColumnClassName("hraness-marketing-interface-grid", undefined, columns)}>
1177
1188
  {interfaces.map((entry) => (
1178
1189
  <article className={classNames("hraness-marketing-interface")} key={entry.label}>
1179
1190
  <Heading
@@ -1198,6 +1209,7 @@ export interface MarketingTrustItem {
1198
1209
 
1199
1210
  export function MarketingTrustBoundary({
1200
1211
  className,
1212
+ columns,
1201
1213
  heading,
1202
1214
  headingId,
1203
1215
  headingLevel = 2,
@@ -1207,6 +1219,8 @@ export function MarketingTrustBoundary({
1207
1219
  summary,
1208
1220
  }: Readonly<{
1209
1221
  className?: string;
1222
+ /** Maximum columns; cards wrap within the available container width. */
1223
+ columns?: MarketingColumnCount;
1210
1224
  heading: string;
1211
1225
  headingId: string;
1212
1226
  headingLevel?: MarketingHeadingLevel;
@@ -1223,7 +1237,7 @@ export function MarketingTrustBoundary({
1223
1237
  id={id}
1224
1238
  >
1225
1239
  <MarketingCollectionHeader {...{ heading, headingId, headingLevel, label, summary }} prefix="trust" />
1226
- <dl className={classNames("hraness-marketing-trust-grid")}>
1240
+ <dl className={marketingColumnClassName("hraness-marketing-trust-grid", undefined, columns)}>
1227
1241
  {items.map((item) => (
1228
1242
  <div className={classNames("hraness-marketing-trust-item")} key={item.label}>
1229
1243
  <dt className={classNames("hraness-marketing-trust-item__label")}>{item.label}</dt>
@@ -1481,8 +1495,10 @@ export function MarketingMaker({
1481
1495
  }
1482
1496
 
1483
1497
  export interface MarketingRelatedProduct {
1484
- /** Custom artwork in place of `mark`, drawn in the same 44px slot. */
1498
+ /** Custom artwork in place of `mark`, drawn in the same 28px slot. */
1485
1499
  readonly art?: ReactNode;
1500
+ /** Optional public host, displayed beside the product name. */
1501
+ readonly domain?: string;
1486
1502
  readonly href: string;
1487
1503
  /** Transparent product mark, such as a portfolio item's `mark`; drawn as a foil mark before the name. */
1488
1504
  readonly mark?: string;
@@ -1496,39 +1512,54 @@ export interface MarketingRelatedProduct {
1496
1512
  readonly role: string;
1497
1513
  }
1498
1514
 
1515
+ export type MarketingRelatedTone = "rose" | "indigo" | "amber" | "emerald" | "neutral";
1516
+
1499
1517
  export interface MarketingRelatedGroup {
1518
+ /** Maximum columns for this group, overriding the section default. */
1519
+ readonly columns?: MarketingColumnCount;
1500
1520
  /** Heading naming this tier of siblings, rendered one level below the section heading. */
1501
1521
  readonly heading: string;
1502
1522
  readonly headingId: string;
1503
1523
  readonly items: readonly MarketingRelatedProduct[];
1504
1524
  readonly summary?: string;
1525
+ /** The category color used on the studio portfolio. */
1526
+ readonly tone?: MarketingRelatedTone;
1505
1527
  }
1506
1528
 
1507
1529
  function MarketingRelatedCards({
1530
+ ariaLabel,
1531
+ columns,
1508
1532
  items,
1509
1533
  level,
1510
1534
  }: Readonly<{
1535
+ ariaLabel?: string;
1536
+ columns?: MarketingColumnCount;
1511
1537
  items: readonly MarketingRelatedProduct[];
1512
1538
  level: MarketingHeadingLevel;
1513
1539
  }>) {
1514
1540
  return (
1515
- <>
1541
+ <ul aria-label={ariaLabel} className={marketingColumnClassName("hraness-marketing-related__list", undefined, columns)}>
1516
1542
  {items.map((item) => (
1517
- <a className={classNames("hraness-marketing-related__card")} data-foil="" data-hraness-marketing="card" href={item.href} key={item.name}>
1518
- {isPresentNode(item.art) || (item.mark !== undefined && item.mark !== "")
1519
- ? (
1520
- <span aria-hidden="true" className={classNames("hraness-marketing-related__card-mark")}>
1521
- {isPresentNode(item.art) ? item.art : <FoilMark size={44} src={item.mark ?? ""} />}
1522
- </span>
1523
- )
1524
- : null}
1525
- <span className={classNames("hraness-marketing-related__card-text")}>
1526
- <Heading className={classNames("hraness-marketing-related__card-name")} level={level}>{item.name}</Heading>
1527
- <span className={classNames("hraness-marketing-related__card-role")}>{item.role}</span>
1528
- </span>
1529
- </a>
1543
+ <li className={classNames("hraness-marketing-related__item")} key={item.name}>
1544
+ <a className={classNames("hraness-marketing-related__card")} data-foil="" data-hraness-marketing="card" href={item.href}>
1545
+ {isPresentNode(item.art) || (item.mark !== undefined && item.mark !== "")
1546
+ ? (
1547
+ <span aria-hidden="true" className={classNames("hraness-marketing-related__card-mark")}>
1548
+ {isPresentNode(item.art) ? item.art : <FoilMark size={28} src={item.mark ?? ""} />}
1549
+ </span>
1550
+ )
1551
+ : null}
1552
+ <div className={classNames("hraness-marketing-related__card-text")}>
1553
+ <div className={classNames("hraness-marketing-related__card-heading")}>
1554
+ <Heading className={classNames("hraness-marketing-related__card-name")} level={level}>{item.name}</Heading>
1555
+ {item.domain === undefined ? null : <span className={classNames("hraness-marketing-related__card-domain")}>{item.domain}</span>}
1556
+ </div>
1557
+ <span className={classNames("hraness-marketing-related__card-role")}>{item.role}</span>
1558
+ </div>
1559
+ </a>
1560
+ </li>
1530
1561
  ))}
1531
- </>
1562
+ </ul>
1532
1563
  );
1533
1564
  }
1534
1565
 
@@ -1536,9 +1567,10 @@ type MarketingRelatedBody =
1536
1567
  | Readonly<{ groups: readonly MarketingRelatedGroup[]; items?: undefined }>
1537
1568
  | Readonly<{ groups?: undefined; items: readonly MarketingRelatedProduct[] }>;
1538
1569
 
1539
- /** Linked cards for sibling products: each shows the product's mark, name, and one-line description. */
1570
+ /** Compact studio product rows, grouped by portfolio category when supplied. */
1540
1571
  export function MarketingRelated({
1541
1572
  className,
1573
+ columns,
1542
1574
  heading,
1543
1575
  headingId,
1544
1576
  headingLevel = 2,
@@ -1549,6 +1581,8 @@ export function MarketingRelated({
1549
1581
  summary,
1550
1582
  }: Readonly<{
1551
1583
  className?: string;
1584
+ /** Maximum columns for each card row; a group can override it. */
1585
+ columns?: MarketingColumnCount;
1552
1586
  heading: string;
1553
1587
  headingId: string;
1554
1588
  headingLevel?: MarketingHeadingLevel;
@@ -1557,6 +1591,7 @@ export function MarketingRelated({
1557
1591
  summary?: string;
1558
1592
  }> &
1559
1593
  MarketingRelatedBody) {
1594
+ const defaultColumnProps = columns === undefined ? {} : { columns };
1560
1595
  return (
1561
1596
  <section
1562
1597
  aria-labelledby={headingId}
@@ -1573,30 +1608,33 @@ export function MarketingRelated({
1573
1608
  summary={summary}
1574
1609
  />
1575
1610
  {groups === undefined
1576
- ? (
1577
- <MarketingCardRow>
1578
- <MarketingRelatedCards items={items} level={childHeadingLevel(headingLevel)} />
1579
- </MarketingCardRow>
1580
- )
1581
- : groups.map((group) => (
1582
- <div className={classNames("hraness-marketing-related__group")} key={group.headingId}>
1583
- <div className={classNames("hraness-marketing-related__group-header")}>
1584
- <Heading
1585
- className={classNames("hraness-marketing-related__group-heading")}
1586
- id={group.headingId}
1587
- level={childHeadingLevel(headingLevel)}
1611
+ ? <MarketingRelatedCards {...defaultColumnProps} items={items} level={childHeadingLevel(headingLevel)} />
1612
+ : (
1613
+ <div className={classNames("hraness-marketing-related__groups")}>
1614
+ {groups.map((group) => (
1615
+ <div
1616
+ aria-labelledby={group.headingId}
1617
+ className={classNames("hraness-marketing-related__group", undefined, group.tone ?? "neutral")}
1618
+ data-tone={group.tone ?? "neutral"}
1619
+ key={group.headingId}
1588
1620
  >
1589
- {group.heading}
1590
- </Heading>
1591
- {group.summary === undefined
1592
- ? null
1593
- : <p className={classNames("hraness-marketing-related__group-summary")}>{group.summary}</p>}
1594
- </div>
1595
- <MarketingCardRow ariaLabel={group.heading}>
1596
- <MarketingRelatedCards items={group.items} level={childHeadingLevel(childHeadingLevel(headingLevel))} />
1597
- </MarketingCardRow>
1621
+ <div className={classNames("hraness-marketing-related__group-header")}>
1622
+ <Heading
1623
+ className={classNames("hraness-marketing-related__group-heading")}
1624
+ id={group.headingId}
1625
+ level={childHeadingLevel(headingLevel)}
1626
+ >
1627
+ {group.heading}
1628
+ </Heading>
1629
+ {group.summary === undefined
1630
+ ? null
1631
+ : <p className={classNames("hraness-marketing-related__group-summary")}>{group.summary}</p>}
1632
+ </div>
1633
+ <MarketingRelatedCards ariaLabel={group.heading} {...(group.columns === undefined ? defaultColumnProps : { columns: group.columns })} items={group.items} level={childHeadingLevel(childHeadingLevel(headingLevel))} />
1634
+ </div>
1635
+ ))}
1598
1636
  </div>
1599
- ))}
1637
+ )}
1600
1638
  </section>
1601
1639
  );
1602
1640
  }
@@ -3,9 +3,12 @@ export * from "./particle-halo.js";
3
3
  export * from "./procedural-backdrop.js";
4
4
  export * from "./procedural-recipe.js";
5
5
  export * from "./product-marketing.js";
6
+ export * from "./marketing-account.js";
7
+ export * from "./marketing-comparison.js";
6
8
  export * from "./surfaces.js";
7
9
  export * from "./syntax-code.js";
8
10
  export * from "./foil-mark.js";
9
11
  export * from "./provider-mark.js";
10
12
  export * from "./platform-icons.js";
11
13
  export * from "./launch-beats.js";
14
+ export * from "./marketing-diagram.js";
package/src/styles.css CHANGED
@@ -12,6 +12,8 @@
12
12
  @import "./charts.css";
13
13
  @import "./plain-site.css";
14
14
  @import "./plain-publication.css";
15
+ @import "./marketing-comparison.css";
16
+ @import "./marketing-diagram.css";
15
17
  @import "./reading.css";
16
18
  @import "./status-page.css";
17
19
  @import "./product-marketing.css";
@@ -11,6 +11,12 @@
11
11
  * face. */
12
12
  @layer base {
13
13
  :root {
14
+ /* Shared studio category accents. Text names retain the foreground role. */
15
+ --hraness-studio-rose: light-dark(#b03a5b, #f39cb5);
16
+ --hraness-studio-indigo: light-dark(#4a4fbf, #a9adff);
17
+ --hraness-studio-amber: light-dark(#9a5a00, #f0b659);
18
+ --hraness-studio-emerald: light-dark(#1d7a55, #71d2a6);
19
+
14
20
  --hraness-type-reading-size: clamp(1rem, .95rem + .25vw, 1.0625rem);
15
21
  --hraness-type-reading-leading: 1.7;
16
22
  --hraness-type-reading-measure: 70ch;
@@ -84,3 +90,29 @@
84
90
  -webkit-text-fill-color: currentColor;
85
91
  }
86
92
  }
93
+
94
+ /* Prose and secondary text actions share a quiet, persistent link affordance. */
95
+ .hraness-text-link,
96
+ .hraness-prose :where(a:not([role="button"], [data-emphasis], [data-foil])),
97
+ .plain-publication__article-body :where(a:not([role="button"], [data-emphasis], [data-foil])),
98
+ .plain-page :where(p, li, dd, dt, figcaption) a:not([role="button"], [data-emphasis], [data-foil]) {
99
+ text-decoration-line: underline;
100
+ text-decoration-style: dotted;
101
+ text-decoration-color: color-mix(in srgb, currentColor 40%, transparent);
102
+ text-decoration-thickness: 1px;
103
+ text-underline-offset: 0.2em;
104
+ }
105
+
106
+ .hraness-text-link:is(:hover, :focus-visible),
107
+ .hraness-prose :where(a:not([role="button"], [data-emphasis], [data-foil])):is(:hover, :focus-visible),
108
+ .plain-page :where(p, li, dd, dt, figcaption) a:not([role="button"], [data-emphasis], [data-foil]):is(:hover, :focus-visible) {
109
+ text-decoration-color: currentColor;
110
+ }
111
+
112
+ @media (forced-colors: active) {
113
+ .hraness-text-link,
114
+ .hraness-prose :where(a),
115
+ .plain-page :where(p, li, dd, dt, figcaption) a {
116
+ text-decoration-color: LinkText;
117
+ }
118
+ }
File without changes