@nanisoft/prism-ui 0.5.1 → 0.7.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 (132) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/blocks/cta-01/cta.d.ts +66 -6
  3. package/dist/blocks/cta-01/cta.d.ts.map +1 -1
  4. package/dist/blocks/cta-01/cta.js +8 -3
  5. package/dist/blocks/cta-01/index.d.ts +1 -0
  6. package/dist/blocks/cta-01/index.d.ts.map +1 -1
  7. package/dist/blocks/feature-grid-01/feature-grid.d.ts +57 -6
  8. package/dist/blocks/feature-grid-01/feature-grid.d.ts.map +1 -1
  9. package/dist/blocks/feature-grid-01/feature-grid.js +12 -2
  10. package/dist/blocks/feature-grid-01/index.d.ts +1 -0
  11. package/dist/blocks/feature-grid-01/index.d.ts.map +1 -1
  12. package/dist/blocks/hero-01/hero.d.ts +30 -1
  13. package/dist/blocks/hero-01/hero.d.ts.map +1 -1
  14. package/dist/blocks/hero-01/hero.js +14 -2
  15. package/dist/blocks/index.d.ts +10 -0
  16. package/dist/blocks/index.d.ts.map +1 -1
  17. package/dist/blocks/index.js +9 -0
  18. package/dist/blocks/instrument-panel-01/index.d.ts +3 -0
  19. package/dist/blocks/instrument-panel-01/index.d.ts.map +1 -0
  20. package/dist/blocks/instrument-panel-01/index.js +1 -0
  21. package/dist/blocks/instrument-panel-01/instrument-panel.d.ts +114 -0
  22. package/dist/blocks/instrument-panel-01/instrument-panel.d.ts.map +1 -0
  23. package/dist/blocks/instrument-panel-01/instrument-panel.js +64 -0
  24. package/dist/blocks/logo-strip-01/index.d.ts +3 -0
  25. package/dist/blocks/logo-strip-01/index.d.ts.map +1 -0
  26. package/dist/blocks/logo-strip-01/index.js +1 -0
  27. package/dist/blocks/logo-strip-01/logo-strip.d.ts +84 -0
  28. package/dist/blocks/logo-strip-01/logo-strip.d.ts.map +1 -0
  29. package/dist/blocks/logo-strip-01/logo-strip.js +40 -0
  30. package/dist/blocks/note-grid-01/index.d.ts +3 -0
  31. package/dist/blocks/note-grid-01/index.d.ts.map +1 -0
  32. package/dist/blocks/note-grid-01/index.js +1 -0
  33. package/dist/blocks/note-grid-01/note-grid.d.ts +79 -0
  34. package/dist/blocks/note-grid-01/note-grid.d.ts.map +1 -0
  35. package/dist/blocks/note-grid-01/note-grid.js +37 -0
  36. package/dist/blocks/pricing-01/pricing.js +1 -1
  37. package/dist/blocks/process-rail-01/index.d.ts +3 -0
  38. package/dist/blocks/process-rail-01/index.d.ts.map +1 -0
  39. package/dist/blocks/process-rail-01/index.js +1 -0
  40. package/dist/blocks/process-rail-01/process-rail.d.ts +99 -0
  41. package/dist/blocks/process-rail-01/process-rail.d.ts.map +1 -0
  42. package/dist/blocks/process-rail-01/process-rail.js +58 -0
  43. package/dist/blocks/product-grid-01/index.d.ts +3 -0
  44. package/dist/blocks/product-grid-01/index.d.ts.map +1 -0
  45. package/dist/blocks/product-grid-01/index.js +1 -0
  46. package/dist/blocks/product-grid-01/product-grid.d.ts +114 -0
  47. package/dist/blocks/product-grid-01/product-grid.d.ts.map +1 -0
  48. package/dist/blocks/product-grid-01/product-grid.js +51 -0
  49. package/dist/blocks/site-footer/index.d.ts +3 -0
  50. package/dist/blocks/site-footer/index.d.ts.map +1 -0
  51. package/dist/blocks/site-footer/index.js +1 -0
  52. package/dist/blocks/site-footer/site-footer.d.ts +135 -0
  53. package/dist/blocks/site-footer/site-footer.d.ts.map +1 -0
  54. package/dist/blocks/site-footer/site-footer.js +41 -0
  55. package/dist/blocks/site-header/index.d.ts +3 -0
  56. package/dist/blocks/site-header/index.d.ts.map +1 -0
  57. package/dist/blocks/site-header/index.js +1 -0
  58. package/dist/blocks/site-header/site-header.d.ts +142 -0
  59. package/dist/blocks/site-header/site-header.d.ts.map +1 -0
  60. package/dist/blocks/site-header/site-header.js +46 -0
  61. package/dist/blocks/stack-grid-01/index.d.ts +3 -0
  62. package/dist/blocks/stack-grid-01/index.d.ts.map +1 -0
  63. package/dist/blocks/stack-grid-01/index.js +1 -0
  64. package/dist/blocks/stack-grid-01/stack-grid.d.ts +118 -0
  65. package/dist/blocks/stack-grid-01/stack-grid.d.ts.map +1 -0
  66. package/dist/blocks/stack-grid-01/stack-grid.js +49 -0
  67. package/dist/blocks/status-ledger-01/index.d.ts +3 -0
  68. package/dist/blocks/status-ledger-01/index.d.ts.map +1 -0
  69. package/dist/blocks/status-ledger-01/index.js +1 -0
  70. package/dist/blocks/status-ledger-01/status-ledger.d.ts +131 -0
  71. package/dist/blocks/status-ledger-01/status-ledger.d.ts.map +1 -0
  72. package/dist/blocks/status-ledger-01/status-ledger.js +80 -0
  73. package/dist/catalog.d.ts.map +1 -1
  74. package/dist/catalog.js +180 -0
  75. package/dist/components/index.d.ts +11 -0
  76. package/dist/components/index.d.ts.map +1 -1
  77. package/dist/components/index.js +6 -0
  78. package/dist/components/ui/card.d.ts +25 -0
  79. package/dist/components/ui/card.d.ts.map +1 -1
  80. package/dist/components/ui/card.js +25 -0
  81. package/dist/components/ui/cta-link.d.ts +45 -0
  82. package/dist/components/ui/cta-link.d.ts.map +1 -0
  83. package/dist/components/ui/cta-link.js +81 -0
  84. package/dist/components/ui/diagram.d.ts +154 -0
  85. package/dist/components/ui/diagram.d.ts.map +1 -0
  86. package/dist/components/ui/diagram.js +150 -0
  87. package/dist/components/ui/fact-list.d.ts +90 -0
  88. package/dist/components/ui/fact-list.d.ts.map +1 -0
  89. package/dist/components/ui/fact-list.js +38 -0
  90. package/dist/components/ui/product-mark.d.ts +115 -0
  91. package/dist/components/ui/product-mark.d.ts.map +1 -0
  92. package/dist/components/ui/product-mark.js +82 -0
  93. package/dist/components/ui/product-switcher.d.ts +108 -0
  94. package/dist/components/ui/product-switcher.d.ts.map +1 -0
  95. package/dist/components/ui/product-switcher.js +47 -0
  96. package/dist/components/ui/prose.d.ts +88 -0
  97. package/dist/components/ui/prose.d.ts.map +1 -0
  98. package/dist/components/ui/prose.js +53 -0
  99. package/dist/components/ui/section.d.ts +24 -1
  100. package/dist/components/ui/section.d.ts.map +1 -1
  101. package/dist/components/ui/section.js +11 -2
  102. package/dist/components/ui/slider.d.ts.map +1 -1
  103. package/dist/components/ui/slider.js +7 -1
  104. package/dist/index.d.ts +24 -0
  105. package/dist/index.d.ts.map +1 -1
  106. package/dist/index.js +17 -0
  107. package/dist/pages/blog-post-page/index.d.ts +174 -0
  108. package/dist/pages/blog-post-page/index.d.ts.map +1 -0
  109. package/dist/pages/blog-post-page/index.js +49 -0
  110. package/dist/pages/docs-shell/docs-shell.d.ts +250 -0
  111. package/dist/pages/docs-shell/docs-shell.d.ts.map +1 -0
  112. package/dist/pages/docs-shell/docs-shell.js +197 -0
  113. package/dist/pages/docs-shell/index.d.ts +3 -0
  114. package/dist/pages/docs-shell/index.d.ts.map +1 -0
  115. package/dist/pages/docs-shell/index.js +1 -0
  116. package/dist/pages/index.d.ts +13 -2
  117. package/dist/pages/index.d.ts.map +1 -1
  118. package/dist/pages/index.js +10 -2
  119. package/dist/pages/not-found-page/index.d.ts +112 -0
  120. package/dist/pages/not-found-page/index.d.ts.map +1 -0
  121. package/dist/pages/not-found-page/index.js +46 -0
  122. package/dist/provider/provider.d.ts +5 -0
  123. package/dist/provider/provider.d.ts.map +1 -1
  124. package/dist/provider/provider.js +129 -38
  125. package/dist/provider/theme-script.d.ts +41 -3
  126. package/dist/provider/theme-script.d.ts.map +1 -1
  127. package/dist/provider/theme-script.js +68 -17
  128. package/dist/styles.css +555 -12
  129. package/dist/theming/index.d.ts +132 -2
  130. package/dist/theming/index.d.ts.map +1 -1
  131. package/dist/theming/index.js +127 -1
  132. package/package.json +14 -4
@@ -0,0 +1,84 @@
1
+ import { type HeadingLevel } from '../../components/ui/section';
2
+ /**
3
+ * One item of the strip, as a string.
4
+ *
5
+ * A string rather than a node, because the strip is a run of short words and a
6
+ * node would let a caller put a paragraph in a slot whose whole job is to be
7
+ * scannable in one glance. A caller with more than a few words about one item
8
+ * wants a `NoteGrid01` row or a `StatusLedger01` row, not a longer strip.
9
+ */
10
+ export type LogoStripItem = string;
11
+ /**
12
+ * The props a LogoStrip01 takes.
13
+ *
14
+ * Every string is a prop and the Block ships none. There is no default list of
15
+ * technologies, no default list of customers and no default list of words: a
16
+ * strip that hardcoded any of them would hand every consumer a claim about who
17
+ * uses their product.
18
+ */
19
+ export type LogoStrip01Props = {
20
+ /**
21
+ * The items, in the order a reader should meet them. Order is the caller's
22
+ * because it is a claim about which item matters first.
23
+ */
24
+ items: readonly LogoStripItem[];
25
+ /**
26
+ * The strip's accessible name, for a strip that is not inside a region the
27
+ * reader can already name. It is rendered as the list's name and as no visible
28
+ * text, because the items are the visible content and a heading above them would
29
+ * be a heading about a heading.
30
+ *
31
+ * It is required because it is a word a reader hears. A Block that ships no copy
32
+ * ships no reader-facing copy either, so "Highlights" is not this Block's to
33
+ * choose: on one site the strip is a list of data sources and on another it is a
34
+ * list of capabilities, and the word that says which is the caller's.
35
+ */
36
+ label: string;
37
+ /** Optional label above the section title. See the No-Default-Eyebrow Rule. */
38
+ eyebrow?: string;
39
+ /** The section title. Omit it for a strip composed under its own heading. */
40
+ title?: string;
41
+ /** One or two sentences under the title. */
42
+ description?: string;
43
+ /** Heading level for the section title. See `HeadingLevel`. */
44
+ headingLevel?: HeadingLevel;
45
+ /**
46
+ * Layout only, exactly as on every Component. Changing a Prism-owned visual
47
+ * property from here is prohibited.
48
+ */
49
+ className?: string;
50
+ };
51
+ /**
52
+ * A single line of short items: the transition band between a hero and the
53
+ * section that follows it.
54
+ *
55
+ * All four NaniSoft sites compose this band, immediately after the hero and
56
+ * before the first numbered section, and each one wrote it as the same thing: a
57
+ * row of three to six short phrases, separated by a middot, in muted text, with
58
+ * no heading at all. Four hand-written strips, four sets of site CSS, and one
59
+ * question each of them answered differently - whether the strip is a list, a
60
+ * paragraph with separators, or a decorative row of spans.
61
+ *
62
+ * It is a `ul`, so a screen reader hears how many items there are before it
63
+ * hears the first one. That is the whole answer to the question: a strip whose
64
+ * items are spans in a `div` is announced as one run of text with no count, and a
65
+ * reader deciding whether to keep listening has nothing to weigh.
66
+ *
67
+ * **The strip does not move.** A band of items that slides is an entrance
68
+ * animation, and this system has none: motion is state feedback only, shortened
69
+ * rather than removed under reduced motion. What the four sites actually wanted
70
+ * from their strips was a second, quieter statement of what the product is, and
71
+ * repetition of the type does that without anything moving. A strip of more than
72
+ * about eight items wraps onto three lines on a phone and stops being a band, so
73
+ * a longer set belongs in a `StackGrid01`.
74
+ *
75
+ * The items are `text-balance` so a two-word item does not break across lines
76
+ * where a one-word item beside it does not. There is no separator glyph between
77
+ * them: a middot is punctuation a screen reader reads and a sighted reader parses
78
+ * as content, and the gap already separates two words from two words.
79
+ *
80
+ * It is a server Component: no hook, no state, no keyframes and no client code.
81
+ */
82
+ export declare function LogoStrip01({ items, label, eyebrow, title, description, headingLevel, className, }: LogoStrip01Props): import("react").JSX.Element | null;
83
+ export default LogoStrip01;
84
+ //# sourceMappingURL=logo-strip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logo-strip.d.ts","sourceRoot":"","sources":["../../../src/blocks/logo-strip-01/logo-strip.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA2B,KAAK,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAGxF;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAA;AAElC;;;;;;;GAOG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B;;;OAGG;IACH,KAAK,EAAE,SAAS,aAAa,EAAE,CAAA;IAC/B;;;;;;;;;;OAUG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,+EAA+E;IAC/E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,4CAA4C;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,+DAA+D;IAC/D,YAAY,CAAC,EAAE,YAAY,CAAA;IAC3B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,KAAK,EACL,OAAO,EACP,KAAK,EACL,WAAW,EACX,YAAmB,EACnB,SAAS,GACV,EAAE,gBAAgB,sCA+BlB;AAED,eAAe,WAAW,CAAA"}
@@ -0,0 +1,40 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Section, SectionHeading } from '../../components/ui/section';
3
+ import { cn } from '../../lib/utils';
4
+ /**
5
+ * A single line of short items: the transition band between a hero and the
6
+ * section that follows it.
7
+ *
8
+ * All four NaniSoft sites compose this band, immediately after the hero and
9
+ * before the first numbered section, and each one wrote it as the same thing: a
10
+ * row of three to six short phrases, separated by a middot, in muted text, with
11
+ * no heading at all. Four hand-written strips, four sets of site CSS, and one
12
+ * question each of them answered differently - whether the strip is a list, a
13
+ * paragraph with separators, or a decorative row of spans.
14
+ *
15
+ * It is a `ul`, so a screen reader hears how many items there are before it
16
+ * hears the first one. That is the whole answer to the question: a strip whose
17
+ * items are spans in a `div` is announced as one run of text with no count, and a
18
+ * reader deciding whether to keep listening has nothing to weigh.
19
+ *
20
+ * **The strip does not move.** A band of items that slides is an entrance
21
+ * animation, and this system has none: motion is state feedback only, shortened
22
+ * rather than removed under reduced motion. What the four sites actually wanted
23
+ * from their strips was a second, quieter statement of what the product is, and
24
+ * repetition of the type does that without anything moving. A strip of more than
25
+ * about eight items wraps onto three lines on a phone and stops being a band, so
26
+ * a longer set belongs in a `StackGrid01`.
27
+ *
28
+ * The items are `text-balance` so a two-word item does not break across lines
29
+ * where a one-word item beside it does not. There is no separator glyph between
30
+ * them: a middot is punctuation a screen reader reads and a sighted reader parses
31
+ * as content, and the gap already separates two words from two words.
32
+ *
33
+ * It is a server Component: no hook, no state, no keyframes and no client code.
34
+ */
35
+ export function LogoStrip01({ items, label, eyebrow, title, description, headingLevel = 'h2', className, }) {
36
+ if (items.length === 0)
37
+ return null;
38
+ return (_jsxs(Section, { className: "py-10 sm:py-14", children: [title ? (_jsx(SectionHeading, { as: headingLevel, align: "left", eyebrow: eyebrow, title: title, description: description, className: "mb-10" })) : null, _jsx("ul", { "data-slot": "logo-strip", "aria-label": items.length > 1 ? label : undefined, className: cn('text-muted-foreground flex flex-wrap items-center justify-center gap-x-4 gap-y-2 text-center text-sm', className), children: items.map((item) => (_jsx("li", { "data-slot": "logo-strip-item", className: "text-balance", children: item }, item))) })] }));
39
+ }
40
+ export default LogoStrip01;
@@ -0,0 +1,3 @@
1
+ export { NoteGrid01, default } from './note-grid';
2
+ export type { NoteGrid01Props, NoteGrid01Note } from './note-grid';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/blocks/note-grid-01/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AACjD,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA"}
@@ -0,0 +1 @@
1
+ export { NoteGrid01, default } from './note-grid';
@@ -0,0 +1,79 @@
1
+ import { type HeadingLevel } from '../../components/ui/section';
2
+ /**
3
+ * One note in the grid: a title and one or two sentences under it.
4
+ *
5
+ * Both are required, and a note with no title is a bullet and a note with no
6
+ * body is a heading. The grid is a set of titled points, and each point is
7
+ * short enough that neither half is optional.
8
+ */
9
+ export type NoteGrid01Note = {
10
+ /**
11
+ * The point's title: a short noun phrase, not a sentence. The grid is scanned
12
+ * down its left edge, so a title that wraps to two lines costs every other row
13
+ * its alignment.
14
+ */
15
+ title: string;
16
+ /** One or two sentences about the point. */
17
+ body: string;
18
+ };
19
+ /**
20
+ * The props a NoteGrid01 takes.
21
+ *
22
+ * Every string is a prop and the Block ships none: no note, no title and no
23
+ * default set of arguments. A grid that hardcoded its points would hand every
24
+ * consumer an argument for a product that is not theirs.
25
+ */
26
+ export type NoteGrid01Props = {
27
+ /** Optional label above the section title. See the No-Default-Eyebrow Rule. */
28
+ eyebrow?: string;
29
+ /** The section title. Omit it for a grid composed under its own heading. */
30
+ title?: string;
31
+ /** One or two sentences under the title. */
32
+ description?: string;
33
+ /**
34
+ * The notes, in the order a reader should meet them. Order is the caller's
35
+ * because it is a claim about which point matters first.
36
+ */
37
+ notes: readonly NoteGrid01Note[];
38
+ /**
39
+ * The line under the grid, for the sentence that qualifies it: what the points
40
+ * do not cover, or where to read more.
41
+ */
42
+ caption?: string;
43
+ /** Heading level for the section title. See `HeadingLevel`. */
44
+ headingLevel?: HeadingLevel;
45
+ };
46
+ /**
47
+ * A grid of short points: a title and a sentence or two, on a hairline above each
48
+ * one, with no tile and no icon.
49
+ *
50
+ * Three of the four NaniSoft sites compose this section, immediately after the
51
+ * process rail and before the next numbered band, and each one drew it as the
52
+ * same thing: a multi-column set of short points, each a bold title and a short
53
+ * paragraph, with no card and no icon. All three also composed it *twice* - once
54
+ * under the rail and once under a different section - so three hand-written grids
55
+ * became six, each with its own column count and its own spacing.
56
+ *
57
+ * It is a `dl` rather than a grid of `div`s, because each note is a term and its
58
+ * explanation and a definition list is what says so. The titles are `dt`s and the
59
+ * bodies are `dd`s, so a screen reader announces "the title, the body" for each
60
+ * point rather than reading six bold words and then six paragraphs and leaving a
61
+ * reader to pair them up.
62
+ *
63
+ * **No tile, no icon, no badge.** The point of this section is that it carries
64
+ * more words than a card does, and a card's job is to carry few. A note with an
65
+ * icon tile in front of it reads as a feature and is read as one, and a section of
66
+ * twelve features is a page that argues rather than explains. Where a point wants
67
+ * an icon and a card, that is `FeatureGrid01`, and the two Blocks are two
68
+ * different jobs rather than two densities of one.
69
+ *
70
+ * The column count is two on a wide viewport and three at the largest, with four
71
+ * notes as the comfortable case. A note whose body runs past three lines stops
72
+ * being a note, so the detail belongs in the row that needs it or above the grid
73
+ * in `description`.
74
+ *
75
+ * It is a server Component: no hook, no state and no client code.
76
+ */
77
+ export declare function NoteGrid01({ eyebrow, title, description, notes, caption, headingLevel, }: NoteGrid01Props): import("react").JSX.Element;
78
+ export default NoteGrid01;
79
+ //# sourceMappingURL=note-grid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"note-grid.d.ts","sourceRoot":"","sources":["../../../src/blocks/note-grid-01/note-grid.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA2B,KAAK,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAExF;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,4CAA4C;IAC5C,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,+EAA+E;IAC/E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,4CAA4C;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;OAGG;IACH,KAAK,EAAE,SAAS,cAAc,EAAE,CAAA;IAChC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,+DAA+D;IAC/D,YAAY,CAAC,EAAE,YAAY,CAAA;CAC5B,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,KAAK,EACL,WAAW,EACX,KAAK,EACL,OAAO,EACP,YAAmB,GACpB,EAAE,eAAe,+BA0BjB;AAED,eAAe,UAAU,CAAA"}
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Section, SectionHeading } from '../../components/ui/section';
3
+ /**
4
+ * A grid of short points: a title and a sentence or two, on a hairline above each
5
+ * one, with no tile and no icon.
6
+ *
7
+ * Three of the four NaniSoft sites compose this section, immediately after the
8
+ * process rail and before the next numbered band, and each one drew it as the
9
+ * same thing: a multi-column set of short points, each a bold title and a short
10
+ * paragraph, with no card and no icon. All three also composed it *twice* - once
11
+ * under the rail and once under a different section - so three hand-written grids
12
+ * became six, each with its own column count and its own spacing.
13
+ *
14
+ * It is a `dl` rather than a grid of `div`s, because each note is a term and its
15
+ * explanation and a definition list is what says so. The titles are `dt`s and the
16
+ * bodies are `dd`s, so a screen reader announces "the title, the body" for each
17
+ * point rather than reading six bold words and then six paragraphs and leaving a
18
+ * reader to pair them up.
19
+ *
20
+ * **No tile, no icon, no badge.** The point of this section is that it carries
21
+ * more words than a card does, and a card's job is to carry few. A note with an
22
+ * icon tile in front of it reads as a feature and is read as one, and a section of
23
+ * twelve features is a page that argues rather than explains. Where a point wants
24
+ * an icon and a card, that is `FeatureGrid01`, and the two Blocks are two
25
+ * different jobs rather than two densities of one.
26
+ *
27
+ * The column count is two on a wide viewport and three at the largest, with four
28
+ * notes as the comfortable case. A note whose body runs past three lines stops
29
+ * being a note, so the detail belongs in the row that needs it or above the grid
30
+ * in `description`.
31
+ *
32
+ * It is a server Component: no hook, no state and no client code.
33
+ */
34
+ export function NoteGrid01({ eyebrow, title, description, notes, caption, headingLevel = 'h2', }) {
35
+ return (_jsxs(Section, { children: [title ? (_jsx(SectionHeading, { as: headingLevel, align: "left", eyebrow: eyebrow, title: title, description: description, className: "mb-12" })) : null, _jsx("dl", { "data-slot": "note-grid", className: "grid gap-8 sm:grid-cols-2 lg:grid-cols-3", children: notes.map((note) => (_jsxs("div", { "data-slot": "note-grid-note", className: "border-border flex flex-col gap-2 border-t pt-4", children: [_jsx("dt", { className: "text-sm font-semibold", children: note.title }), _jsx("dd", { className: "text-muted-foreground text-pretty text-sm", children: note.body })] }, note.title))) }), caption ? _jsx("p", { className: "text-muted-foreground mt-6 text-pretty text-sm", children: caption }) : null] }));
36
+ }
37
+ export default NoteGrid01;
@@ -15,6 +15,6 @@ import { Section, SectionHeading } from '../../components/ui/section';
15
15
  * inherited them.
16
16
  */
17
17
  export function Pricing01({ eyebrow, title, description, plans, headingLevel = 'h2', }) {
18
- return (_jsxs(Section, { children: [title ? (_jsx(SectionHeading, { as: headingLevel, eyebrow: eyebrow, title: title, description: description, className: "mb-12" })) : null, _jsx("div", { className: "grid gap-6 lg:grid-cols-3", children: plans.map((plan) => (_jsxs(Card, { className: plan.featured ? 'border-primary shadow-md' : undefined, children: [_jsxs(CardHeader, { children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx(CardTitle, { children: plan.name }), plan.featured ? _jsx(Badge, { children: plan.badge ?? 'Popular' }) : null] }), plan.body ? _jsx(CardDescription, { children: plan.body }) : null, _jsxs("p", { className: "mt-2 text-3xl font-semibold tracking-tight", children: [plan.price, plan.period ? (_jsx("span", { className: "text-muted-foreground text-sm font-normal", children: plan.period })) : null] })] }), _jsx(CardContent, { className: "flex flex-col gap-3", children: plan.features.map((feature, index) => (_jsxs("p", { className: "flex items-start gap-2 text-sm", children: [_jsx(Check, { className: "text-success mt-0.5 size-4 shrink-0" }), feature] }, index))) }), _jsx(CardFooter, { className: "mt-auto", children: _jsx(Button, { className: "w-full", variant: plan.featured ? 'default' : 'outline', children: plan.cta }) })] }, plan.name))) })] }));
18
+ return (_jsxs(Section, { children: [title ? (_jsx(SectionHeading, { as: headingLevel, eyebrow: eyebrow, title: title, description: description, className: "mb-12" })) : null, _jsx("div", { className: "grid gap-6 lg:grid-cols-3", children: plans.map((plan) => (_jsxs(Card, { className: plan.featured ? 'border-primary shadow-md' : undefined, children: [_jsxs(CardHeader, { children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx(CardTitle, { children: plan.name }), plan.featured && plan.badge ? _jsx(Badge, { children: plan.badge }) : null] }), plan.body ? _jsx(CardDescription, { children: plan.body }) : null, _jsxs("p", { className: "mt-2 text-3xl font-semibold tracking-tight", children: [plan.price, plan.period ? (_jsx("span", { className: "text-muted-foreground text-sm font-normal", children: plan.period })) : null] })] }), _jsx(CardContent, { className: "flex flex-col gap-3", children: plan.features.map((feature, index) => (_jsxs("p", { className: "flex items-start gap-2 text-sm", children: [_jsx(Check, { className: "text-success mt-0.5 size-4 shrink-0" }), feature] }, index))) }), _jsx(CardFooter, { className: "mt-auto", children: _jsx(Button, { className: "w-full", variant: plan.featured ? 'default' : 'outline', children: plan.cta }) })] }, plan.name))) })] }));
19
19
  }
20
20
  export default Pricing01;
@@ -0,0 +1,3 @@
1
+ export { ProcessRail01, default } from './process-rail';
2
+ export type { ProcessRail01Props, ProcessRail01Steps, ProcessStep } from './process-rail';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/blocks/process-rail-01/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AACvD,YAAY,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA"}
@@ -0,0 +1 @@
1
+ export { ProcessRail01, default } from './process-rail';
@@ -0,0 +1,99 @@
1
+ import { type HeadingLevel } from '../../components/ui/section';
2
+ /**
3
+ * One step of a process, as the rail draws it.
4
+ *
5
+ * `name` is the step's own name and `description` is one line about what happens
6
+ * there. Both are required: a rail is a sequence, and a step with no name is a
7
+ * divider and a step with no description is a label.
8
+ */
9
+ export type ProcessStep = {
10
+ /** The name of the step, as a reader would name it in conversation. */
11
+ name: string;
12
+ /** One line about what happens at this step. */
13
+ description: string;
14
+ };
15
+ /**
16
+ * The number of steps a ProcessRail01 admits, as a type rather than as a runtime
17
+ * check.
18
+ *
19
+ * Four, and the refusal of five is the type's own work: `steps` is declared as a
20
+ * tuple, so a five-element array is not assignable to any arm of the union below
21
+ * and a four-element array is. There is no length check at render and no list
22
+ * that silently truncates, because a rail that quietly dropped its fifth step
23
+ * would be a process diagram that lied about the process.
24
+ *
25
+ * Four is the measured number, not a round one. Every NaniSoft site states a
26
+ * process as a rail, and every one of them states four stages: the collection
27
+ * line, the lakehouse path, the agent loop and the research path are four steps
28
+ * each, and each site drew its rail from a list of four. A fifth step is a
29
+ * different shape of thing and would be a second Block, taken to the open with
30
+ * the same evidence rather than smuggled in as a wider tuple.
31
+ */
32
+ export type ProcessRail01Steps = readonly [ProcessStep, ProcessStep] | readonly [ProcessStep, ProcessStep, ProcessStep] | readonly [ProcessStep, ProcessStep, ProcessStep, ProcessStep];
33
+ /**
34
+ * The props a ProcessRail01 takes.
35
+ *
36
+ * Every string and every number is a prop. The Block ships no step, no ordinal
37
+ * and no label for the end of the rail: a process rail that hardcoded a stage
38
+ * name would hand every consumer a diagram of somebody else's process.
39
+ */
40
+ export type ProcessRail01Props = {
41
+ /** Optional label above the section title. See the No-Default-Eyebrow Rule. */
42
+ eyebrow?: string;
43
+ /** The section title. Omit it for a rail that is composed under its own heading. */
44
+ title?: string;
45
+ /** One or two sentences under the title. */
46
+ description?: string;
47
+ /**
48
+ * The label on the last step, for a rail that ends in a state worth naming:
49
+ * "serving", "merged", "verified". Omit it when the last step needs no label,
50
+ * which is the honest state for a rail that has not finished.
51
+ */
52
+ finalLabel?: string;
53
+ /**
54
+ * The steps, in the order the process runs. Two, three or four; a fifth is a
55
+ * type error. See `ProcessRail01Steps`.
56
+ */
57
+ steps: ProcessRail01Steps;
58
+ /** Heading level for the section title. See `HeadingLevel`. */
59
+ headingLevel?: HeadingLevel;
60
+ };
61
+ /**
62
+ * A process drawn as a rail: the steps of a pipeline, in order, on one line.
63
+ *
64
+ * All four NaniSoft sites compose this section, and each one drew it by hand
65
+ * with the same three parts: an ordinal per step, a name, and one caption per
66
+ * step, separated by hairlines with a label on the last one. This is that
67
+ * section, once, with the ordinal and the separation owned here rather than by
68
+ * four sets of site CSS.
69
+ *
70
+ * **The rail admits four steps and refuses five, in the type.** `steps` is a
71
+ * tuple union rather than an array, so a fifth element is a compile error and
72
+ * not a fifth column. The reason is that a rail is a claim about a sequence, and
73
+ * a rail that quietly dropped a step to fit a width would be a diagram of a
74
+ * process that is not the process. Four is the number every one of the four
75
+ * sites actually states; a longer process is a different shape and earns its own
76
+ * Block.
77
+ *
78
+ * The ordinal is rendered here from the step's position, zero-padded to two
79
+ * digits, because a sequence is the one thing the Block knows that the caller
80
+ * cannot be asked to state twice. Nothing else is derived: a step's name and
81
+ * caption are the caller's words, and `finalLabel` is the caller's word for where
82
+ * the rail ends.
83
+ *
84
+ * The rail is a `Section` with the system container and padding, so a page built
85
+ * from this Block keeps the rhythm of the Blocks around it. The steps sit on one
86
+ * `grid-cols-N` row whose template is written from the tuple's own length, so a
87
+ * three-step rail has three tracks rather than four tracks with one empty, and
88
+ * the hairline between two steps sits on the track boundary rather than being
89
+ * drawn by each step.
90
+ *
91
+ * A caption of more than about twelve words wraps under a step and the row stops
92
+ * reading as a line, so the caption is a line and the detail belongs above the
93
+ * rail in `description`.
94
+ *
95
+ * It is a server Component: no hook, no state and no client code.
96
+ */
97
+ export declare function ProcessRail01({ eyebrow, title, description, finalLabel, steps, headingLevel, }: ProcessRail01Props): import("react").JSX.Element;
98
+ export default ProcessRail01;
99
+ //# sourceMappingURL=process-rail.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process-rail.d.ts","sourceRoot":"","sources":["../../../src/blocks/process-rail-01/process-rail.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA2B,KAAK,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAGxF;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAA;IACZ,gDAAgD;IAChD,WAAW,EAAE,MAAM,CAAA;CACpB,CAAA;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,kBAAkB,GAC1B,SAAS,CAAC,WAAW,EAAE,WAAW,CAAC,GACnC,SAAS,CAAC,WAAW,EAAE,WAAW,EAAE,WAAW,CAAC,GAChD,SAAS,CAAC,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,CAAC,CAAA;AAkBjE;;;;;;GAMG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,+EAA+E;IAC/E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,oFAAoF;IACpF,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,4CAA4C;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,KAAK,EAAE,kBAAkB,CAAA;IACzB,+DAA+D;IAC/D,YAAY,CAAC,EAAE,YAAY,CAAA;CAC5B,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,KAAK,EACL,WAAW,EACX,UAAU,EACV,KAAK,EACL,YAAmB,GACpB,EAAE,kBAAkB,+BAuCpB;AAED,eAAe,aAAa,CAAA"}
@@ -0,0 +1,58 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Section, SectionHeading } from '../../components/ui/section';
3
+ import { cn } from '../../lib/utils';
4
+ /**
5
+ * The grid tracks each admissible length gets, keyed on the tuple's own length.
6
+ *
7
+ * Written as Tailwind classes rather than as an inline `gridTemplateColumns`,
8
+ * because an inline template is a layout value in the markup where every other
9
+ * layout value in this package is a class, and because a class is compiled into
10
+ * the stylesheet while an inline style is recomputed per render. Each length is
11
+ * spelled out, so a two-step rail has two tracks and a four-step rail has four,
12
+ * and neither has an empty track at the end of the row.
13
+ */
14
+ const TRACKS = {
15
+ 2: 'sm:grid-cols-2',
16
+ 3: 'sm:grid-cols-3',
17
+ 4: 'sm:grid-cols-2 lg:grid-cols-4',
18
+ };
19
+ /**
20
+ * A process drawn as a rail: the steps of a pipeline, in order, on one line.
21
+ *
22
+ * All four NaniSoft sites compose this section, and each one drew it by hand
23
+ * with the same three parts: an ordinal per step, a name, and one caption per
24
+ * step, separated by hairlines with a label on the last one. This is that
25
+ * section, once, with the ordinal and the separation owned here rather than by
26
+ * four sets of site CSS.
27
+ *
28
+ * **The rail admits four steps and refuses five, in the type.** `steps` is a
29
+ * tuple union rather than an array, so a fifth element is a compile error and
30
+ * not a fifth column. The reason is that a rail is a claim about a sequence, and
31
+ * a rail that quietly dropped a step to fit a width would be a diagram of a
32
+ * process that is not the process. Four is the number every one of the four
33
+ * sites actually states; a longer process is a different shape and earns its own
34
+ * Block.
35
+ *
36
+ * The ordinal is rendered here from the step's position, zero-padded to two
37
+ * digits, because a sequence is the one thing the Block knows that the caller
38
+ * cannot be asked to state twice. Nothing else is derived: a step's name and
39
+ * caption are the caller's words, and `finalLabel` is the caller's word for where
40
+ * the rail ends.
41
+ *
42
+ * The rail is a `Section` with the system container and padding, so a page built
43
+ * from this Block keeps the rhythm of the Blocks around it. The steps sit on one
44
+ * `grid-cols-N` row whose template is written from the tuple's own length, so a
45
+ * three-step rail has three tracks rather than four tracks with one empty, and
46
+ * the hairline between two steps sits on the track boundary rather than being
47
+ * drawn by each step.
48
+ *
49
+ * A caption of more than about twelve words wraps under a step and the row stops
50
+ * reading as a line, so the caption is a line and the detail belongs above the
51
+ * rail in `description`.
52
+ *
53
+ * It is a server Component: no hook, no state and no client code.
54
+ */
55
+ export function ProcessRail01({ eyebrow, title, description, finalLabel, steps, headingLevel = 'h2', }) {
56
+ return (_jsxs(Section, { children: [title ? (_jsx(SectionHeading, { as: headingLevel, align: "left", eyebrow: eyebrow, title: title, description: description, className: "mb-12" })) : null, _jsx("ol", { "data-slot": "process-rail", className: cn('grid gap-6 sm:gap-px', TRACKS[steps.length]), children: steps.map((step, index) => (_jsxs("li", { "data-slot": "process-step", className: "border-border flex flex-col gap-2 border-t pt-4", children: [_jsxs("div", { className: "flex items-baseline justify-between gap-3", children: [_jsx("span", { className: "text-muted-foreground font-mono text-xs", children: String(index + 1).padStart(2, '0') }), finalLabel && index === steps.length - 1 ? (_jsx("span", { className: "text-muted-foreground font-mono text-xs", children: finalLabel })) : null] }), _jsx("span", { className: "text-sm font-semibold", children: step.name }), _jsx("span", { className: "text-muted-foreground text-pretty text-sm", children: step.description })] }, step.name))) })] }));
57
+ }
58
+ export default ProcessRail01;
@@ -0,0 +1,3 @@
1
+ export { ProductGrid01, default } from './product-grid';
2
+ export type { ProductGrid01Props, ProductGrid01Product } from './product-grid';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/blocks/product-grid-01/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AACvD,YAAY,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA"}
@@ -0,0 +1 @@
1
+ export { ProductGrid01, default } from './product-grid';
@@ -0,0 +1,114 @@
1
+ import { type ProductMarkProps } from '../../components/ui/product-mark';
2
+ import { type HeadingLevel } from '../../components/ui/section';
3
+ /**
4
+ * One product in the grid, as the row draws it.
5
+ *
6
+ * The three facts the mark is drawn from, the one line under the name, and where
7
+ * the row goes. The mark is `ProductMark`, so a product's two-part identity is
8
+ * the same two colours here as it is in a switcher and on a page header.
9
+ */
10
+ export type ProductGrid01Product = {
11
+ /**
12
+ * The product's key within the set. Carried on the markup as `data-product`
13
+ * through `ProductMark`, so a test can name one product rather than the first
14
+ * one, and two products in one document are individually addressable.
15
+ */
16
+ id: string;
17
+ /**
18
+ * The product's own name, which is the second part of its mark. It is the
19
+ * anchor of the row and the first thing a reader reads.
20
+ */
21
+ name: string;
22
+ /**
23
+ * The product's pack, which is the hue of the mark's core. Omit it, or pass
24
+ * `null`, for the one member of a set that has no pack of its own: that member
25
+ * wears the full spectrum rather than a colourless mark.
26
+ */
27
+ pack?: ProductMarkProps['pack'];
28
+ /** One line about what the product is. */
29
+ tagline: string;
30
+ /**
31
+ * Where the row goes. Rendered as a native anchor's `href`, so the row is a
32
+ * link with the browser's own affordances rather than a click handler.
33
+ */
34
+ href: string;
35
+ /**
36
+ * Opens the destination in a new browsing context, which defaults the link
37
+ * relationship to `noopener noreferrer`. Prism does not decide what counts as
38
+ * external, so the caller declares it.
39
+ */
40
+ newTab?: boolean;
41
+ };
42
+ /**
43
+ * The props a ProductGrid01 takes.
44
+ *
45
+ * Every string is a prop and the Block ships none. There is no product list in
46
+ * this package, no default tagline and no "built on" heading: the set of products
47
+ * a page lists is the page's own fact, and Prism ships the row rather than the
48
+ * roster.
49
+ */
50
+ export type ProductGrid01Props = {
51
+ /** Optional label above the section title. See the No-Default-Eyebrow Rule. */
52
+ eyebrow?: string;
53
+ /** The section title. Omit it for a grid composed under its own heading. */
54
+ title?: string;
55
+ /** One or two sentences under the title. */
56
+ description?: string;
57
+ /**
58
+ * The products, in the order a reader should meet them. Order is the caller's
59
+ * because it is a claim about which product matters first.
60
+ */
61
+ products: readonly ProductGrid01Product[];
62
+ /**
63
+ * The line under the grid, for the sentence that qualifies it: what the set
64
+ * does not include, or where to read more.
65
+ */
66
+ caption?: string;
67
+ /** Heading level for the section title. See `HeadingLevel`. */
68
+ headingLevel?: HeadingLevel;
69
+ };
70
+ /**
71
+ * A set of products as a stack of hairline rows: a two-part mark, a name, one
72
+ * tagline and a destination per row.
73
+ *
74
+ * All four NaniSoft sites publish this section, and each one drew it by hand with
75
+ * the same three parts in the same order: a colour mark for the product, the
76
+ * product's name, and one line about what it is, on a hairline row with the whole
77
+ * row a link. Each site also resolved the mark's ink in JavaScript from the live
78
+ * mode, which is the part that is a second source of truth: four copies of the
79
+ * rule that a pastel is a fill and the name is an ink, and four copies of the
80
+ * arithmetic that a filled dot fails contrast on a light card.
81
+ *
82
+ * **The mark is two parts, and it is `ProductMark`.** The ring is `brand-ink` and
83
+ * the core is the product's own `primary`, with the name in the brand ink beside
84
+ * it. That split is the contested shape this Block ships, and it is settled by a
85
+ * measurement rather than by taste: a pastel brand value is a fill and never text,
86
+ * so the core may be `primary` and the name may not, and a *filled* mark on a
87
+ * light card measures under the threshold a mark has to clear, so the mark is a
88
+ * hairline ring around the fill rather than a disc of it. A product row that drew
89
+ * a single filled dot with the name in the same value would be wrong in both
90
+ * directions at once, and nothing in the rendered result would say which.
91
+ *
92
+ * The pack is carried through `ProductMark` rather than set here, so the mark's
93
+ * own `data-pack` boundary lands on the mark and the product's hue follows from
94
+ * the pack the caller passed rather than from whichever pack the page is wearing.
95
+ * That is the half of the pack-boundary law a hand-written mark gets wrong: it
96
+ * resolves a colour once, at mount, from one element's computed style, and it
97
+ * keeps showing the old pack's values when a boundary lands above it.
98
+ *
99
+ * The row is a link, not a click handler. The whole row is an `a` with the
100
+ * product's `href`, so a status bar shows the destination, a context menu copies
101
+ * it, and middle-click opens it in a new tab. The mark is `aria-hidden` inside
102
+ * it, so the link is announced once, by name, rather than as a shape and a word.
103
+ *
104
+ * A row carries one line of tagline. A second line is a second paragraph and
105
+ * belongs above the grid in `description`, where it applies to the set rather
106
+ * than to one member of it.
107
+ *
108
+ * It is a server Component: no hook, no state, no client code and no mode. The
109
+ * mark resolves its colour through the cascade, so a page that renders this in a
110
+ * server file needs nothing from the provider mounted.
111
+ */
112
+ export declare function ProductGrid01({ eyebrow, title, description, products, caption, headingLevel, }: ProductGrid01Props): import("react").JSX.Element | null;
113
+ export default ProductGrid01;
114
+ //# sourceMappingURL=product-grid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product-grid.d.ts","sourceRoot":"","sources":["../../../src/blocks/product-grid-01/product-grid.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,gBAAgB,EAAE,MAAM,kCAAkC,CAAA;AACrF,OAAO,EAA2B,KAAK,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAExF;;;;;;GAMG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC;;;;OAIG;IACH,EAAE,EAAE,MAAM,CAAA;IACV;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAA;IACZ;;;;OAIG;IACH,IAAI,CAAC,EAAE,gBAAgB,CAAC,MAAM,CAAC,CAAA;IAC/B,0CAA0C;IAC1C,OAAO,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAA;IACZ;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,+EAA+E;IAC/E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,4CAA4C;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;OAGG;IACH,QAAQ,EAAE,SAAS,oBAAoB,EAAE,CAAA;IACzC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,+DAA+D;IAC/D,YAAY,CAAC,EAAE,YAAY,CAAA;CAC5B,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,KAAK,EACL,WAAW,EACX,QAAQ,EACR,OAAO,EACP,YAAmB,GACpB,EAAE,kBAAkB,sCAqCpB;AAED,eAAe,aAAa,CAAA"}
@@ -0,0 +1,51 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ProductMark } from '../../components/ui/product-mark';
3
+ import { Section, SectionHeading } from '../../components/ui/section';
4
+ /**
5
+ * A set of products as a stack of hairline rows: a two-part mark, a name, one
6
+ * tagline and a destination per row.
7
+ *
8
+ * All four NaniSoft sites publish this section, and each one drew it by hand with
9
+ * the same three parts in the same order: a colour mark for the product, the
10
+ * product's name, and one line about what it is, on a hairline row with the whole
11
+ * row a link. Each site also resolved the mark's ink in JavaScript from the live
12
+ * mode, which is the part that is a second source of truth: four copies of the
13
+ * rule that a pastel is a fill and the name is an ink, and four copies of the
14
+ * arithmetic that a filled dot fails contrast on a light card.
15
+ *
16
+ * **The mark is two parts, and it is `ProductMark`.** The ring is `brand-ink` and
17
+ * the core is the product's own `primary`, with the name in the brand ink beside
18
+ * it. That split is the contested shape this Block ships, and it is settled by a
19
+ * measurement rather than by taste: a pastel brand value is a fill and never text,
20
+ * so the core may be `primary` and the name may not, and a *filled* mark on a
21
+ * light card measures under the threshold a mark has to clear, so the mark is a
22
+ * hairline ring around the fill rather than a disc of it. A product row that drew
23
+ * a single filled dot with the name in the same value would be wrong in both
24
+ * directions at once, and nothing in the rendered result would say which.
25
+ *
26
+ * The pack is carried through `ProductMark` rather than set here, so the mark's
27
+ * own `data-pack` boundary lands on the mark and the product's hue follows from
28
+ * the pack the caller passed rather than from whichever pack the page is wearing.
29
+ * That is the half of the pack-boundary law a hand-written mark gets wrong: it
30
+ * resolves a colour once, at mount, from one element's computed style, and it
31
+ * keeps showing the old pack's values when a boundary lands above it.
32
+ *
33
+ * The row is a link, not a click handler. The whole row is an `a` with the
34
+ * product's `href`, so a status bar shows the destination, a context menu copies
35
+ * it, and middle-click opens it in a new tab. The mark is `aria-hidden` inside
36
+ * it, so the link is announced once, by name, rather than as a shape and a word.
37
+ *
38
+ * A row carries one line of tagline. A second line is a second paragraph and
39
+ * belongs above the grid in `description`, where it applies to the set rather
40
+ * than to one member of it.
41
+ *
42
+ * It is a server Component: no hook, no state, no client code and no mode. The
43
+ * mark resolves its colour through the cascade, so a page that renders this in a
44
+ * server file needs nothing from the provider mounted.
45
+ */
46
+ export function ProductGrid01({ eyebrow, title, description, products, caption, headingLevel = 'h2', }) {
47
+ if (products.length === 0)
48
+ return null;
49
+ return (_jsxs(Section, { children: [title ? (_jsx(SectionHeading, { as: headingLevel, align: "left", eyebrow: eyebrow, title: title, description: description, className: "mb-12" })) : null, _jsx("ul", { "data-slot": "product-grid", className: "flex flex-col", children: products.map((product) => (_jsx("li", { "data-slot": "product-grid-row", className: "border-b first:border-t", children: _jsxs("a", { href: product.href, rel: product.newTab ? 'noopener noreferrer' : undefined, target: product.newTab ? '_blank' : undefined, className: "hover:bg-accent/50 focus-visible:ring-ring flex flex-col gap-1 px-2 py-4 transition-colors duration-fast ease-out sm:flex-row sm:items-baseline sm:gap-6 focus-visible:ring-[3px] focus-visible:outline-none", children: [_jsx("span", { className: "flex shrink-0 items-center sm:w-64", children: _jsx(ProductMark, { id: product.id, name: product.name, pack: product.pack, size: "md" }) }), _jsx("span", { className: "text-muted-foreground text-pretty text-sm", children: product.tagline })] }) }, product.id))) }), caption ? _jsx("p", { className: "text-muted-foreground mt-6 text-pretty text-sm", children: caption }) : null] }));
50
+ }
51
+ export default ProductGrid01;