@misoto22/folio 0.17.0 → 0.18.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 (174) hide show
  1. package/dist/agent/Accordion.md +1 -1
  2. package/dist/agent/Actions.md +1 -1
  3. package/dist/agent/Alert.md +1 -1
  4. package/dist/agent/AppShell.md +1 -1
  5. package/dist/agent/ArchitectureFigure.md +1 -1
  6. package/dist/agent/AreaChart.md +1 -1
  7. package/dist/agent/Article.md +1 -1
  8. package/dist/agent/AspectRatio.md +1 -1
  9. package/dist/agent/Avatar.md +1 -1
  10. package/dist/agent/Badge.md +1 -1
  11. package/dist/agent/BarChart.md +1 -1
  12. package/dist/agent/BarList.md +1 -1
  13. package/dist/agent/BigNumber.md +1 -1
  14. package/dist/agent/BoxPlot.md +1 -1
  15. package/dist/agent/Breadcrumb.md +1 -1
  16. package/dist/agent/BulletChart.md +1 -1
  17. package/dist/agent/Button.md +1 -1
  18. package/dist/agent/Calendar.md +1 -1
  19. package/dist/agent/CalendarHeatmap.md +1 -1
  20. package/dist/agent/Card.md +1 -1
  21. package/dist/agent/Checkbox.md +1 -1
  22. package/dist/agent/Code.md +1 -1
  23. package/dist/agent/CodeBlock.md +1 -1
  24. package/dist/agent/Collapsible.md +1 -1
  25. package/dist/agent/Collection.md +1 -1
  26. package/dist/agent/ColorPicker.md +1 -1
  27. package/dist/agent/Combobox.md +1 -1
  28. package/dist/agent/Command.md +2 -1
  29. package/dist/agent/ComposedChart.md +1 -1
  30. package/dist/agent/Contact.md +1 -1
  31. package/dist/agent/Content.md +1 -1
  32. package/dist/agent/ContextMenu.md +1 -1
  33. package/dist/agent/Conversation.md +1 -1
  34. package/dist/agent/DataflowFigure.md +1 -1
  35. package/dist/agent/DatePicker.md +1 -1
  36. package/dist/agent/DescriptionList.md +1 -1
  37. package/dist/agent/Diagram.md +1 -1
  38. package/dist/agent/DiagramCanvas.md +1 -1
  39. package/dist/agent/DiagramExportMenu.md +1 -1
  40. package/dist/agent/DiagramInspector.md +1 -1
  41. package/dist/agent/DiagramLegend.md +1 -1
  42. package/dist/agent/DiagramMinimap.md +1 -1
  43. package/dist/agent/DiagramToolbar.md +1 -1
  44. package/dist/agent/Dialog.md +1 -1
  45. package/dist/agent/DropdownMenu.md +1 -1
  46. package/dist/agent/EmptyState.md +1 -1
  47. package/dist/agent/ErrorState.md +1 -1
  48. package/dist/agent/Evidence.md +1 -1
  49. package/dist/agent/Facet.md +1 -1
  50. package/dist/agent/Field.md +1 -1
  51. package/dist/agent/FigureBand.md +1 -1
  52. package/dist/agent/FloatingIconButton.md +1 -1
  53. package/dist/agent/FunnelChart.md +1 -1
  54. package/dist/agent/Heading.md +1 -1
  55. package/dist/agent/Heatmap.md +1 -1
  56. package/dist/agent/Histogram.md +1 -1
  57. package/dist/agent/Input.md +1 -1
  58. package/dist/agent/Kbd.md +1 -1
  59. package/dist/agent/LifecycleFigure.md +1 -1
  60. package/dist/agent/LineChart.md +1 -1
  61. package/dist/agent/LinkArrow.md +1 -1
  62. package/dist/agent/Markdown.md +1 -1
  63. package/dist/agent/Media.md +1 -1
  64. package/dist/agent/Metric.md +1 -1
  65. package/dist/agent/Metrics.md +1 -1
  66. package/dist/agent/Music.md +1 -1
  67. package/dist/agent/NativeSelect.md +1 -1
  68. package/dist/agent/NavItem.md +1 -1
  69. package/dist/agent/NumberField.md +1 -1
  70. package/dist/agent/PageHeader.md +1 -1
  71. package/dist/agent/Pagination.md +1 -1
  72. package/dist/agent/PieChart.md +1 -1
  73. package/dist/agent/Popover.md +1 -1
  74. package/dist/agent/Portfolio.md +1 -1
  75. package/dist/agent/PortfolioIndex.md +1 -1
  76. package/dist/agent/Progress.md +1 -1
  77. package/dist/agent/RadarChart.md +1 -1
  78. package/dist/agent/RadialChart.md +1 -1
  79. package/dist/agent/RadioGroup.md +1 -1
  80. package/dist/agent/Reading.md +10 -1
  81. package/dist/agent/Recovery.md +3 -3
  82. package/dist/agent/SankeyChart.md +1 -1
  83. package/dist/agent/ScatterChart.md +1 -1
  84. package/dist/agent/ScrollArea.md +1 -1
  85. package/dist/agent/SearchPalette.md +1 -1
  86. package/dist/agent/SearchableMenu.md +1 -1
  87. package/dist/agent/Select.md +1 -1
  88. package/dist/agent/Separator.md +1 -1
  89. package/dist/agent/SequenceFigure.md +1 -1
  90. package/dist/agent/Sheet.md +1 -1
  91. package/dist/agent/Sidebar.md +1 -1
  92. package/dist/agent/SiteNavigation.md +1 -1
  93. package/dist/agent/SiteShell.md +1 -1
  94. package/dist/agent/Skeleton.md +1 -1
  95. package/dist/agent/Slider.md +1 -1
  96. package/dist/agent/Sparkline.md +1 -1
  97. package/dist/agent/Spinner.md +1 -1
  98. package/dist/agent/StatusDot.md +1 -1
  99. package/dist/agent/StatusPill.md +1 -1
  100. package/dist/agent/Steps.md +1 -1
  101. package/dist/agent/Switch.md +1 -1
  102. package/dist/agent/Table.md +1 -1
  103. package/dist/agent/Tabs.md +1 -1
  104. package/dist/agent/Tag.md +1 -1
  105. package/dist/agent/Text.md +1 -1
  106. package/dist/agent/Textarea.md +1 -1
  107. package/dist/agent/Timeline.md +6 -2
  108. package/dist/agent/Timestamp.md +1 -1
  109. package/dist/agent/Toast.md +1 -1
  110. package/dist/agent/ToggleGroup.md +1 -1
  111. package/dist/agent/Toolbar.md +1 -1
  112. package/dist/agent/Tooltip.md +1 -1
  113. package/dist/agent/TreemapChart.md +1 -1
  114. package/dist/agent/WaterfallChart.md +1 -1
  115. package/dist/agent/Workbench.md +1 -1
  116. package/dist/agent/WorkflowFigure.md +1 -1
  117. package/dist/agent/catalog.json +10 -1
  118. package/dist/agent/index.md +2 -1
  119. package/dist/article.css +1 -1
  120. package/dist/components/Button/Button.js +1 -1
  121. package/dist/components/Button/Button.js.map +1 -1
  122. package/dist/components/Command/Command.d.ts +3 -1
  123. package/dist/components/Command/Command.js +3 -2
  124. package/dist/components/Command/Command.js.map +1 -1
  125. package/dist/components/FigureBand/FigureBand.js +1 -1
  126. package/dist/components/FigureBand/FigureBand.js.map +1 -1
  127. package/dist/components/Pagination/Pagination.js +2 -2
  128. package/dist/components/Pagination/Pagination.js.map +1 -1
  129. package/dist/components/Table/Table.js +1 -1
  130. package/dist/components/Table/Table.js.map +1 -1
  131. package/dist/components/Tag/Tag.js +1 -1
  132. package/dist/components/Tag/Tag.js.map +1 -1
  133. package/dist/components/Text/Text.js +2 -2
  134. package/dist/components/Text/Text.js.map +1 -1
  135. package/dist/components/ToggleGroup/ToggleGroup.js +2 -2
  136. package/dist/components/ToggleGroup/ToggleGroup.js.map +1 -1
  137. package/dist/keyframes.css +22 -0
  138. package/dist/semantic.css +6 -4
  139. package/dist/styles.css +1 -1
  140. package/dist/themes.css +22 -5
  141. package/dist/tokens.css +27 -0
  142. package/dist/tokens.d.ts +6 -0
  143. package/dist/tokens.js +35 -1
  144. package/dist/tokens.json +35 -1
  145. package/dist/website/Media/Media.js +2 -2
  146. package/dist/website/Media/Media.js.map +1 -1
  147. package/dist/website/Reading/Reading.d.ts +7 -1
  148. package/dist/website/Reading/Reading.js +4 -0
  149. package/dist/website/Reading/Reading.js.map +1 -1
  150. package/dist/website/Recovery/Recovery.d.ts +10 -2
  151. package/dist/website/Recovery/Recovery.js +36 -2
  152. package/dist/website/Recovery/Recovery.js.map +1 -1
  153. package/dist/website/SearchPalette/SearchPalette.js +2 -0
  154. package/dist/website/SearchPalette/SearchPalette.js.map +1 -1
  155. package/dist/website/Timeline/Timeline.d.ts +11 -2
  156. package/dist/website/Timeline/Timeline.js +7 -1
  157. package/dist/website/Timeline/Timeline.js.map +1 -1
  158. package/dist/website/Workbench/theme.d.ts +2 -1
  159. package/dist/website/Workbench/theme.js +2 -1
  160. package/dist/website/Workbench/theme.js.map +1 -1
  161. package/dist/website/index.d.ts +2 -2
  162. package/dist/website-collection.css +2 -2
  163. package/dist/website-contact.css +9 -3
  164. package/dist/website-conversation.css +5 -5
  165. package/dist/website-evidence.css +1 -1
  166. package/dist/website-media.css +11 -5
  167. package/dist/website-metrics.css +1 -1
  168. package/dist/website-music.css +1 -1
  169. package/dist/website-reading.css +18 -7
  170. package/dist/website-recovery.css +9 -1
  171. package/dist/website-search.css +28 -14
  172. package/dist/website-timeline.css +9 -3
  173. package/package.json +1 -1
  174. package/skills/folio-design/rules/tokens.md +1 -0
@@ -39,8 +39,42 @@ function LoadingRecords({ rows = 4, media = true }) {
39
39
  ] })
40
40
  ] }, index)) });
41
41
  }
42
+ function LoadingArticle({ media }) {
43
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
44
+ /* @__PURE__ */ jsxs("header", { className: "folio-detail-masthead folio-loading-article", "data-loading-content": true, children: [
45
+ /* @__PURE__ */ jsx(SkeletonLine, { className: "h-2.5 w-20" }),
46
+ /* @__PURE__ */ jsx(SkeletonBlock, { className: "mt-8 h-[clamp(2.5rem,5vw,4rem)] w-[min(100%,36rem)]" }),
47
+ /* @__PURE__ */ jsx(SkeletonLine, { className: "mt-6 h-3.5 w-[min(100%,40rem)]" }),
48
+ /* @__PURE__ */ jsx(SkeletonLine, { className: "mt-2.5 h-3.5 w-[min(100%,30rem)]" }),
49
+ /* @__PURE__ */ jsx("div", { className: "mt-10 flex flex-wrap gap-8", children: [0, 1, 2].map((cell) => /* @__PURE__ */ jsx(SkeletonLine, { className: "h-2.5 w-20" }, cell)) })
50
+ ] }),
51
+ media && /* @__PURE__ */ jsx("div", { className: "folio-reading-figure", "data-loading-content": true, children: /* @__PURE__ */ jsx(SkeletonBlock, { className: "aspect-[16/9] w-full" }) }),
52
+ /* @__PURE__ */ jsx("div", { className: "folio-reading-layout", "data-loading-content": true, children: /* @__PURE__ */ jsx("div", { className: "folio-reading-body", children: ["w-full", "w-11/12", "w-full", "w-4/5", "w-full", "w-2/3"].map((width, index) => /* @__PURE__ */ jsx(SkeletonLine, { className: cn("mt-3.5 h-3.5", width) }, index)) }) })
53
+ ] });
54
+ }
55
+ function LoadingPhotograph() {
56
+ return /* @__PURE__ */ jsx("div", { className: "folio-media-detail-page", children: /* @__PURE__ */ jsxs("div", { className: "folio-media-detail", "data-loading-content": true, children: [
57
+ /* @__PURE__ */ jsxs("header", { className: "folio-media-index-bar", children: [
58
+ /* @__PURE__ */ jsx(SkeletonLine, { className: "h-3 w-24" }),
59
+ /* @__PURE__ */ jsx(SkeletonLine, { className: "h-2.5 w-12" })
60
+ ] }),
61
+ /* @__PURE__ */ jsxs("div", { className: "folio-media-spread", "data-photo-spread": "wide", children: [
62
+ /* @__PURE__ */ jsx("div", { className: "folio-media-well", children: /* @__PURE__ */ jsx(SkeletonBlock, { className: "aspect-[3/2] w-full" }) }),
63
+ /* @__PURE__ */ jsxs("div", { className: "folio-media-ledger", children: [
64
+ /* @__PURE__ */ jsxs("div", { children: [
65
+ /* @__PURE__ */ jsx(SkeletonLine, { className: "h-2.5 w-24" }),
66
+ /* @__PURE__ */ jsx(SkeletonBlock, { className: "mt-5 h-10 w-4/5" }),
67
+ /* @__PURE__ */ jsx(SkeletonLine, { className: "mt-6 h-3 w-full" }),
68
+ /* @__PURE__ */ jsx(SkeletonLine, { className: "mt-2.5 h-3 w-3/4" })
69
+ ] }),
70
+ /* @__PURE__ */ jsx("div", { className: "mt-10", children: [0, 1, 2, 3].map((row) => /* @__PURE__ */ jsx(SkeletonLine, { className: "mt-3 h-2.5 w-2/3" }, row)) })
71
+ ] })
72
+ ] })
73
+ ] }) });
74
+ }
42
75
  function WebsiteLoading({ label, layout = "records", titleClassName, rows = 4, media = true, filters = false }) {
43
- return /* @__PURE__ */ jsxs(SkeletonPage, { label, children: [
76
+ if (layout === "article" || layout === "photograph") return /* @__PURE__ */ jsx("div", { className: "folio-loading-page", children: /* @__PURE__ */ jsx(SkeletonPage, { label, children: layout === "article" ? /* @__PURE__ */ jsx(LoadingArticle, { media }) : /* @__PURE__ */ jsx(LoadingPhotograph, {}) }) });
77
+ return /* @__PURE__ */ jsx("div", { className: "folio-loading-page", children: /* @__PURE__ */ jsxs(SkeletonPage, { label, children: [
44
78
  layout === "identity" ? /* @__PURE__ */ jsxs("header", { className: "folio-loading-identity folio-loading-container", children: [
45
79
  /* @__PURE__ */ jsxs("div", { children: [
46
80
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-5", children: [
@@ -84,7 +118,7 @@ function WebsiteLoading({ label, layout = "records", titleClassName, rows = 4, m
84
118
  /* @__PURE__ */ jsx(SkeletonLine, { className: "mt-16 h-3 w-28" }),
85
119
  /* @__PURE__ */ jsx(SkeletonBlock, { className: "mt-6 h-[220px] w-full" })
86
120
  ] })
87
- ] });
121
+ ] }) });
88
122
  }
89
123
  export {
90
124
  LoadingRecords,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/website/Recovery/Recovery.tsx"],"sourcesContent":["import type { ReactElement, ReactNode } from 'react'\nimport { Button } from '../../components/Button/Button'\nimport { ErrorState, type ErrorStateProps } from '../../components/ErrorState/ErrorState'\nimport { Heading } from '../../components/Heading/Heading'\nimport { SkeletonBlock, SkeletonCircle, SkeletonLine, SkeletonPage } from '../../components/Skeleton/Skeleton'\nimport { cn } from '../../lib/cn'\n\nexport interface RecoveryStateProps extends ErrorStateProps {\n elsewhere?: ReactNode\n}\n\n/** A recovery page composed from the system error primitive and caller-owned destinations. */\nexport function RecoveryState({ action, elsewhere, ...props }: RecoveryStateProps) {\n return <ErrorState {...props} action={<><div className=\"folio-recovery-actions\">{action}</div>{elsewhere}</>} />\n}\n\nexport interface RecoveryLinksProps {\n label: string\n items: { id: string; link: ReactElement }[]\n}\n\n/** Suggested destinations, with routing supplied by the host. */\nexport function RecoveryLinks({ label, items }: RecoveryLinksProps) {\n return <nav aria-label={label} className=\"folio-recovery-links\"><Heading level={2} size=\"label\">{label}</Heading><ul>{items.map((item) => <li key={item.id}><Button asChild variant=\"ghost\" className=\"min-h-11 w-full justify-start\">{item.link}</Button></li>)}</ul></nav>\n}\n\nexport interface LoadingRouteIntroProps {\n titleClassName?: string\n}\n\n/** The shared masthead geometry, drawn from the core loading primitives. */\nexport function LoadingRouteIntro({ titleClassName = 'w-[min(100%,18rem)]' }: LoadingRouteIntroProps) {\n return <header className=\"folio-loading-intro folio-loading-container\"><div><SkeletonLine className=\"h-2.5 w-28\" /><SkeletonBlock className={cn('mt-6 h-[clamp(2.5rem,4.6vw,3.375rem)]', titleClassName)} /><SkeletonLine className=\"mt-7 h-3.5 w-[min(100%,34rem)]\" /><SkeletonLine className=\"mt-2.5 h-3.5 w-[min(100%,26rem)]\" /></div><SkeletonLine className=\"hidden h-2.5 w-40 justify-self-end md:block\" /></header>\n}\n\nexport interface LoadingRecordsProps {\n rows?: number\n media?: boolean\n}\n\n/** A collection skeleton with or without a media column. */\nexport function LoadingRecords({ rows = 4, media = true }: LoadingRecordsProps) {\n return <div data-loading-content className=\"folio-loading-records folio-loading-container\">{Array.from({ length: rows }, (_, index) => <div key={index} className=\"folio-loading-records__row\" data-media={media || undefined}><SkeletonLine className=\"hidden h-2.5 w-8 sm:block\" />{media && <SkeletonBlock className=\"h-[clamp(140px,20vw,154px)]\" />}<div className=\"min-w-0\"><SkeletonLine className=\"h-2.5 w-24\" /><SkeletonBlock className=\"mt-4 h-6 w-[min(100%,20rem)]\" /><SkeletonLine className=\"mt-5 h-3 w-full max-w-(--measure-record)\" /><SkeletonLine className=\"mt-2.5 h-3 w-[min(100%,26rem)]\" /></div></div>)}</div>\n}\n\nexport interface WebsiteLoadingProps extends LoadingRecordsProps, LoadingRouteIntroProps {\n label: string\n layout?: 'records' | 'gallery' | 'media' | 'metrics' | 'identity'\n filters?: boolean\n}\n\n/** Named loading layouts for the website's reusable page shapes. */\nexport function WebsiteLoading({ label, layout = 'records', titleClassName, rows = 4, media = true, filters = false }: WebsiteLoadingProps) {\n return <SkeletonPage label={label}>\n {layout === 'identity' ? <header className=\"folio-loading-identity folio-loading-container\"><div><div className=\"flex items-center gap-5\"><SkeletonCircle className=\"size-12\" /><SkeletonBlock className=\"h-12 w-64\" /></div><SkeletonLine className=\"mt-9 h-3.5 w-full\" /><SkeletonLine className=\"mt-3 h-3.5 w-4/5\" /><SkeletonLine className=\"mt-10 h-3 w-2/3\" /></div><SkeletonBlock className=\"aspect-square w-full\" /></header> : <LoadingRouteIntro titleClassName={titleClassName} />}\n {filters && <div className=\"folio-loading-filters folio-loading-container\" data-loading-content><div className=\"flex flex-wrap gap-6\">{[8, 16, 12, 20].map((width) => <SkeletonLine key={width} className=\"h-3 w-12\" />)}</div><SkeletonBlock className=\"h-11 w-56\" /></div>}\n {(layout === 'records' || layout === 'identity') && <LoadingRecords rows={rows} media={media} />}\n {layout === 'gallery' && <section data-loading-content className=\"folio-loading-gallery folio-loading-container\">{Array.from({ length: rows }, (_, index) => <div key={index}><SkeletonBlock className=\"aspect-[4/5] w-full\" /><SkeletonLine className=\"mt-3 h-3 w-3/4\" /></div>)}</section>}\n {layout === 'media' && <section data-loading-content className=\"folio-loading-media\"><div data-loading-now-playing className=\"folio-media-feature folio-loading-container\"><div className=\"folio-media-feature__artwork\"><SkeletonBlock data-loading-artwork className=\"h-full w-full\" /></div><div><SkeletonLine className=\"h-2.5 w-28\" /><SkeletonBlock className=\"mt-5 h-16 w-4/5\" /><SkeletonLine className=\"mt-5 h-3.5 w-2/3\" /><SkeletonLine className=\"mt-6 h-1 w-full\" /></div></div></section>}\n {layout === 'metrics' && <div data-loading-content className=\"folio-loading-metrics folio-loading-container\"><div className=\"folio-loading-filters\"><SkeletonLine className=\"h-2.5 w-12\" /><SkeletonBlock className=\"h-11 w-72\" /></div><div className=\"folio-loading-metrics__figures\">{[0, 1, 2, 3].map((cell) => <div key={cell}><SkeletonLine className=\"h-2.5 w-24\" /><SkeletonBlock className=\"mt-4 h-9 w-20\" /><SkeletonLine className=\"mt-4 h-2.5 w-16\" /></div>)}</div><SkeletonLine className=\"mt-16 h-3 w-28\" /><SkeletonBlock className=\"mt-6 h-[220px] w-full\" /></div>}\n </SkeletonPage>\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/website/Recovery/Recovery.tsx"],"sourcesContent":["import type { ReactElement, ReactNode } from 'react'\nimport { Button } from '../../components/Button/Button'\nimport { ErrorState, type ErrorStateProps } from '../../components/ErrorState/ErrorState'\nimport { Heading } from '../../components/Heading/Heading'\nimport { SkeletonBlock, SkeletonCircle, SkeletonLine, SkeletonPage } from '../../components/Skeleton/Skeleton'\nimport { cn } from '../../lib/cn'\n\nexport interface RecoveryStateProps extends ErrorStateProps {\n elsewhere?: ReactNode\n}\n\n/** A recovery page composed from the system error primitive and caller-owned destinations. */\nexport function RecoveryState({ action, elsewhere, ...props }: RecoveryStateProps) {\n return <ErrorState {...props} action={<><div className=\"folio-recovery-actions\">{action}</div>{elsewhere}</>} />\n}\n\nexport interface RecoveryLinksProps {\n label: string\n items: { id: string; link: ReactElement }[]\n}\n\n/** Suggested destinations, with routing supplied by the host. */\nexport function RecoveryLinks({ label, items }: RecoveryLinksProps) {\n return <nav aria-label={label} className=\"folio-recovery-links\"><Heading level={2} size=\"label\">{label}</Heading><ul>{items.map((item) => <li key={item.id}><Button asChild variant=\"ghost\" className=\"min-h-11 w-full justify-start\">{item.link}</Button></li>)}</ul></nav>\n}\n\nexport interface LoadingRouteIntroProps {\n titleClassName?: string\n}\n\n/** The shared masthead geometry, drawn from the core loading primitives. */\nexport function LoadingRouteIntro({ titleClassName = 'w-[min(100%,18rem)]' }: LoadingRouteIntroProps) {\n return <header className=\"folio-loading-intro folio-loading-container\"><div><SkeletonLine className=\"h-2.5 w-28\" /><SkeletonBlock className={cn('mt-6 h-[clamp(2.5rem,4.6vw,3.375rem)]', titleClassName)} /><SkeletonLine className=\"mt-7 h-3.5 w-[min(100%,34rem)]\" /><SkeletonLine className=\"mt-2.5 h-3.5 w-[min(100%,26rem)]\" /></div><SkeletonLine className=\"hidden h-2.5 w-40 justify-self-end md:block\" /></header>\n}\n\nexport interface LoadingRecordsProps {\n rows?: number\n media?: boolean\n}\n\n/** A collection skeleton with or without a media column. */\nexport function LoadingRecords({ rows = 4, media = true }: LoadingRecordsProps) {\n return <div data-loading-content className=\"folio-loading-records folio-loading-container\">{Array.from({ length: rows }, (_, index) => <div key={index} className=\"folio-loading-records__row\" data-media={media || undefined}><SkeletonLine className=\"hidden h-2.5 w-8 sm:block\" />{media && <SkeletonBlock className=\"h-[clamp(140px,20vw,154px)]\" />}<div className=\"min-w-0\"><SkeletonLine className=\"h-2.5 w-24\" /><SkeletonBlock className=\"mt-4 h-6 w-[min(100%,20rem)]\" /><SkeletonLine className=\"mt-5 h-3 w-full max-w-(--measure-record)\" /><SkeletonLine className=\"mt-2.5 h-3 w-[min(100%,26rem)]\" /></div></div>)}</div>\n}\n\nexport interface WebsiteLoadingProps extends LoadingRecordsProps, LoadingRouteIntroProps {\n label: string\n layout?: 'records' | 'gallery' | 'media' | 'metrics' | 'identity' | 'article' | 'photograph'\n filters?: boolean\n}\n\n/** A detail page's masthead and opening paragraphs, drawn on the reading page's own frame. */\nfunction LoadingArticle({ media }: { media: boolean }) {\n return <>\n <header className=\"folio-detail-masthead folio-loading-article\" data-loading-content><SkeletonLine className=\"h-2.5 w-20\" /><SkeletonBlock className=\"mt-8 h-[clamp(2.5rem,5vw,4rem)] w-[min(100%,36rem)]\" /><SkeletonLine className=\"mt-6 h-3.5 w-[min(100%,40rem)]\" /><SkeletonLine className=\"mt-2.5 h-3.5 w-[min(100%,30rem)]\" /><div className=\"mt-10 flex flex-wrap gap-8\">{[0, 1, 2].map((cell) => <SkeletonLine key={cell} className=\"h-2.5 w-20\" />)}</div></header>\n {media && <div className=\"folio-reading-figure\" data-loading-content><SkeletonBlock className=\"aspect-[16/9] w-full\" /></div>}\n <div className=\"folio-reading-layout\" data-loading-content><div className=\"folio-reading-body\">{['w-full', 'w-11/12', 'w-full', 'w-4/5', 'w-full', 'w-2/3'].map((width, index) => <SkeletonLine key={index} className={cn('mt-3.5 h-3.5', width)} />)}</div></div>\n </>\n}\n\n/** One photograph's spread: the back bar, the image well and its ledger, in the detail page's own grid. */\nfunction LoadingPhotograph() {\n return <div className=\"folio-media-detail-page\"><div className=\"folio-media-detail\" data-loading-content>\n <header className=\"folio-media-index-bar\"><SkeletonLine className=\"h-3 w-24\" /><SkeletonLine className=\"h-2.5 w-12\" /></header>\n <div className=\"folio-media-spread\" data-photo-spread=\"wide\"><div className=\"folio-media-well\"><SkeletonBlock className=\"aspect-[3/2] w-full\" /></div><div className=\"folio-media-ledger\"><div><SkeletonLine className=\"h-2.5 w-24\" /><SkeletonBlock className=\"mt-5 h-10 w-4/5\" /><SkeletonLine className=\"mt-6 h-3 w-full\" /><SkeletonLine className=\"mt-2.5 h-3 w-3/4\" /></div><div className=\"mt-10\">{[0, 1, 2, 3].map((row) => <SkeletonLine key={row} className=\"mt-3 h-2.5 w-2/3\" />)}</div></div></div>\n </div></div>\n}\n\n/**\n * Named loading layouts for the website's reusable page shapes.\n *\n * Every layout waits a beat before it shows and then fades in, so a route that\n * resolves within that beat goes straight from the old page to the new one\n * instead of flashing grey bars for a frame. A detail route needs its own\n * `article` or `photograph` shape: inheriting the list skeleton above it\n * promises a layout the page then abandons.\n */\nexport function WebsiteLoading({ label, layout = 'records', titleClassName, rows = 4, media = true, filters = false }: WebsiteLoadingProps) {\n if (layout === 'article' || layout === 'photograph') return <div className=\"folio-loading-page\"><SkeletonPage label={label}>{layout === 'article' ? <LoadingArticle media={media} /> : <LoadingPhotograph />}</SkeletonPage></div>\n return <div className=\"folio-loading-page\"><SkeletonPage label={label}>\n {layout === 'identity' ? <header className=\"folio-loading-identity folio-loading-container\"><div><div className=\"flex items-center gap-5\"><SkeletonCircle className=\"size-12\" /><SkeletonBlock className=\"h-12 w-64\" /></div><SkeletonLine className=\"mt-9 h-3.5 w-full\" /><SkeletonLine className=\"mt-3 h-3.5 w-4/5\" /><SkeletonLine className=\"mt-10 h-3 w-2/3\" /></div><SkeletonBlock className=\"aspect-square w-full\" /></header> : <LoadingRouteIntro titleClassName={titleClassName} />}\n {filters && <div className=\"folio-loading-filters folio-loading-container\" data-loading-content><div className=\"flex flex-wrap gap-6\">{[8, 16, 12, 20].map((width) => <SkeletonLine key={width} className=\"h-3 w-12\" />)}</div><SkeletonBlock className=\"h-11 w-56\" /></div>}\n {(layout === 'records' || layout === 'identity') && <LoadingRecords rows={rows} media={media} />}\n {layout === 'gallery' && <section data-loading-content className=\"folio-loading-gallery folio-loading-container\">{Array.from({ length: rows }, (_, index) => <div key={index}><SkeletonBlock className=\"aspect-[4/5] w-full\" /><SkeletonLine className=\"mt-3 h-3 w-3/4\" /></div>)}</section>}\n {layout === 'media' && <section data-loading-content className=\"folio-loading-media\"><div data-loading-now-playing className=\"folio-media-feature folio-loading-container\"><div className=\"folio-media-feature__artwork\"><SkeletonBlock data-loading-artwork className=\"h-full w-full\" /></div><div><SkeletonLine className=\"h-2.5 w-28\" /><SkeletonBlock className=\"mt-5 h-16 w-4/5\" /><SkeletonLine className=\"mt-5 h-3.5 w-2/3\" /><SkeletonLine className=\"mt-6 h-1 w-full\" /></div></div></section>}\n {layout === 'metrics' && <div data-loading-content className=\"folio-loading-metrics folio-loading-container\"><div className=\"folio-loading-filters\"><SkeletonLine className=\"h-2.5 w-12\" /><SkeletonBlock className=\"h-11 w-72\" /></div><div className=\"folio-loading-metrics__figures\">{[0, 1, 2, 3].map((cell) => <div key={cell}><SkeletonLine className=\"h-2.5 w-24\" /><SkeletonBlock className=\"mt-4 h-9 w-20\" /><SkeletonLine className=\"mt-4 h-2.5 w-16\" /></div>)}</div><SkeletonLine className=\"mt-16 h-3 w-28\" /><SkeletonBlock className=\"mt-6 h-[220px] w-full\" /></div>}\n </SkeletonPage></div>\n}\n"],"mappings":"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","names":[]}
@@ -6,6 +6,7 @@ import { Badge } from "../../components/Badge/Badge.js";
6
6
  import { Button } from "../../components/Button/Button.js";
7
7
  import { CommandDialog, CommandEmpty, CommandFooter, CommandGroup, CommandHint, CommandInput, CommandItem, CommandList } from "../../components/Command/Command.js";
8
8
  import { Input } from "../../components/Input/Input.js";
9
+ const PANEL = "folio-search-palette top-[max(4rem,15svh)] w-[min(94vw,40rem)] data-[state=open]:animate-[folio-palette-in_var(--duration-base)_var(--ease)] data-[state=closed]:animate-[folio-palette-out_var(--duration-fast)_var(--ease)]";
9
10
  function SearchPalette({
10
11
  open,
11
12
  onOpenChange,
@@ -37,6 +38,7 @@ function SearchPalette({
37
38
  label,
38
39
  inputLabel,
39
40
  shouldFilter: false,
41
+ className: PANEL,
40
42
  onOpenAutoFocus: () => {
41
43
  previousFocus.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
42
44
  },
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/website/SearchPalette/SearchPalette.tsx"],"sourcesContent":["'use client'\n\nimport { useEffect, useRef, type KeyboardEvent, type ReactNode, type RefObject } from 'react'\nimport { RiArrowLeftLine, RiCloseLine } from '@remixicon/react'\nimport { Badge } from '../../components/Badge/Badge'\nimport { Button } from '../../components/Button/Button'\nimport { CommandDialog, CommandEmpty, CommandFooter, CommandGroup, CommandHint, CommandInput, CommandItem, CommandList } from '../../components/Command/Command'\nimport type { DialogContentProps } from '../../components/Dialog/Dialog'\nimport { Input } from '../../components/Input/Input'\n\nexport interface SearchPaletteItem {\n /** Stable identity, independent of a translated title. */\n id: string\n title: ReactNode\n description?: ReactNode\n icon?: ReactNode\n meta?: ReactNode\n onSelect: () => void\n}\n\nexport interface SearchPaletteGroup {\n id: string\n label: string\n items: SearchPaletteItem[]\n}\n\nexport interface SearchPaletteProps {\n open: boolean\n onOpenChange: (open: boolean) => void\n label: string\n inputLabel: string\n placeholder: string\n query: string\n onQueryChange: (query: string) => void\n /** Already filtered results; search and data access belong to the consumer. */\n groups: SearchPaletteGroup[]\n emptyLabel: ReactNode\n labels: { close: string; navigate: string; select: string; back: string }\n /** Optional answer or detail view, with Escape returning to the results. */\n detail?: { label: string; content: ReactNode; onBack: () => void; onSubmit: () => void; submitDisabled?: boolean }\n onContentLinkClick?: () => void\n onCloseAutoFocus?: DialogContentProps['onCloseAutoFocus']\n}\n\n/** Search, actions and an optional result detail inside one accessible modal. */\nexport function SearchPalette({\n open, onOpenChange, label, inputLabel, placeholder, query, onQueryChange,\n groups, emptyLabel, labels, detail, onContentLinkClick, onCloseAutoFocus,\n}: SearchPaletteProps) {\n const input = useRef<HTMLInputElement>(null)\n const previousFocus = useRef<HTMLElement | null>(null)\n const showingDetail = Boolean(detail)\n useEffect(() => {\n if (open) input.current?.focus()\n }, [open, showingDetail])\n const close = () => onOpenChange(false)\n const field = { input, inputLabel, placeholder, query, onQueryChange }\n\n return (\n <CommandDialog\n open={open} onOpenChange={onOpenChange} label={label} inputLabel={inputLabel} shouldFilter={false}\n onOpenAutoFocus={() => {\n previousFocus.current = document.activeElement instanceof HTMLElement ? document.activeElement : null\n }}\n // A palette opened by a shortcut has no trigger for the dialog to return\n // focus to, so it goes back to whatever held it before opening.\n onCloseAutoFocus={(event) => {\n onCloseAutoFocus?.(event)\n if (!event.defaultPrevented) {\n event.preventDefault()\n previousFocus.current?.focus()\n }\n }}\n onEscapeKeyDown={(event) => {\n if (detail) { event.preventDefault(); detail.onBack() }\n }}\n >\n {detail\n ? <SearchDetail {...field} detail={detail} labels={labels} onClose={close} onContentLinkClick={onContentLinkClick} />\n : <SearchResults {...field} label={label} groups={groups} emptyLabel={emptyLabel} closeLabel={labels.close} onClose={close} />}\n <CommandFooter className=\"folio-search-palette__footer\">\n {!detail && <CommandHint keys={['↑', '↓']}>{labels.navigate}</CommandHint>}\n <CommandHint keys={['↵']}>{labels.select}</CommandHint>\n <CommandHint keys={['esc']}>{detail ? labels.back : labels.close}</CommandHint>\n </CommandFooter>\n </CommandDialog>\n )\n}\n\ninterface SearchFieldProps {\n input: RefObject<HTMLInputElement | null>\n inputLabel: string\n placeholder: string\n query: string\n onQueryChange: (query: string) => void\n onClose: () => void\n}\n\ninterface SearchResultsProps extends SearchFieldProps {\n label: string\n groups: SearchPaletteGroup[]\n emptyLabel: ReactNode\n closeLabel: string\n}\n\n/** The combobox over the host's groups, which the palette never filters again. */\nfunction SearchResults({ input, inputLabel, placeholder, query, onQueryChange, onClose, label, groups, emptyLabel, closeLabel }: SearchResultsProps) {\n return (\n <>\n <div className=\"folio-search-palette__search\">\n <CommandInput ref={input} aria-label={inputLabel} placeholder={placeholder} value={query} onValueChange={onQueryChange} autoComplete=\"off\" spellCheck={false} />\n <Button variant=\"ghost\" iconOnly aria-label={closeLabel} onClick={onClose} className=\"folio-search-palette__close\"><RiCloseLine size={18} aria-hidden /></Button>\n </div>\n <CommandList label={label}>\n <CommandEmpty>{emptyLabel}</CommandEmpty>\n {groups.map((group) => (\n <CommandGroup key={group.id} heading={<span className=\"folio-search-palette__heading\">{group.label}</span>}>\n {group.items.map((item) => (\n <CommandItem\n key={item.id} value={item.id} icon={item.icon} onSelect={item.onSelect} className=\"folio-search-palette__item\"\n meta={item.meta ? <span className=\"folio-search-palette__meta\">{item.meta}</span> : undefined}\n >\n <span className=\"folio-search-palette__result\">\n <span>{item.title}</span>\n {item.description && <span className=\"folio-search-palette__description\">{item.description}</span>}\n </span>\n </CommandItem>\n ))}\n </CommandGroup>\n ))}\n </CommandList>\n </>\n )\n}\n\ninterface SearchDetailProps extends SearchFieldProps {\n detail: NonNullable<SearchPaletteProps['detail']>\n labels: SearchPaletteProps['labels']\n onContentLinkClick?: () => void\n}\n\n/**\n * The keys the command list takes for itself: moving its highlight, and running\n * the highlighted row with Enter, which it does by cancelling the key. The\n * detail view has no list, and needs them back — Enter submits the question or\n * follows a link, and the others move the caret or scroll the answer — so they\n * stop here instead of reaching it.\n */\nconst LIST_KEYS = new Set(['Enter', 'Home', 'End', 'ArrowUp', 'ArrowDown'])\n\nfunction keepFromList(event: KeyboardEvent<HTMLElement>) {\n if (LIST_KEYS.has(event.key)) event.stopPropagation()\n}\n\n/** The answer view: a question field over content the host renders. */\nfunction SearchDetail({ input, inputLabel, placeholder, query, onQueryChange, onClose, detail, labels, onContentLinkClick }: SearchDetailProps) {\n return (\n <>\n <form className=\"folio-search-palette__input\" onKeyDown={keepFromList} onSubmit={(event) => {\n event.preventDefault()\n if (query.trim() && !detail.submitDisabled) detail.onSubmit()\n }}>\n <Button variant=\"ghost\" iconOnly aria-label={labels.back} onClick={detail.onBack}><RiArrowLeftLine size={18} aria-hidden /></Button>\n <Input ref={input} aria-label={inputLabel} placeholder={placeholder} value={query} onChange={(event) => onQueryChange(event.target.value)} autoComplete=\"off\" />\n <Badge>{detail.label}</Badge>\n <Button variant=\"ghost\" iconOnly aria-label={labels.close} onClick={onClose}><RiCloseLine size={18} aria-hidden /></Button>\n </form>\n <div className=\"folio-search-palette__detail\" aria-live=\"polite\" onKeyDown={keepFromList} onClick={(event) => {\n if ((event.target as HTMLElement).closest('a')) onContentLinkClick?.()\n }}>{detail.content}</div>\n </>\n )\n}\n\nexport interface SearchExcerptProps {\n segments: ReadonlyArray<{ text: string; highlighted: boolean }>\n}\n\n/** Render query highlights as text, never as consumer-supplied HTML. */\nexport function SearchExcerpt({ segments }: SearchExcerptProps) {\n return <>{segments.map((segment, index) => segment.highlighted\n ? <mark className=\"folio-search-palette__highlight\" key={index}>{segment.text}</mark>\n : segment.text)}</>\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/website/SearchPalette/SearchPalette.tsx"],"sourcesContent":["'use client'\n\nimport { useEffect, useRef, type KeyboardEvent, type ReactNode, type RefObject } from 'react'\nimport { RiArrowLeftLine, RiCloseLine } from '@remixicon/react'\nimport { Badge } from '../../components/Badge/Badge'\nimport { Button } from '../../components/Button/Button'\nimport { CommandDialog, CommandEmpty, CommandFooter, CommandGroup, CommandHint, CommandInput, CommandItem, CommandList } from '../../components/Command/Command'\nimport type { DialogContentProps } from '../../components/Dialog/Dialog'\nimport { Input } from '../../components/Input/Input'\n\nexport interface SearchPaletteItem {\n /** Stable identity, independent of a translated title. */\n id: string\n title: ReactNode\n description?: ReactNode\n icon?: ReactNode\n meta?: ReactNode\n onSelect: () => void\n}\n\nexport interface SearchPaletteGroup {\n id: string\n label: string\n items: SearchPaletteItem[]\n}\n\nexport interface SearchPaletteProps {\n open: boolean\n onOpenChange: (open: boolean) => void\n label: string\n inputLabel: string\n placeholder: string\n query: string\n onQueryChange: (query: string) => void\n /** Already filtered results; search and data access belong to the consumer. */\n groups: SearchPaletteGroup[]\n emptyLabel: ReactNode\n labels: { close: string; navigate: string; select: string; back: string }\n /** Optional answer or detail view, with Escape returning to the results. */\n detail?: { label: string; content: ReactNode; onBack: () => void; onSubmit: () => void; submitDisabled?: boolean }\n onContentLinkClick?: () => void\n onCloseAutoFocus?: DialogContentProps['onCloseAutoFocus']\n}\n\nconst PANEL = 'folio-search-palette top-[max(4rem,15svh)] w-[min(94vw,40rem)] data-[state=open]:animate-[folio-palette-in_var(--duration-base)_var(--ease)] data-[state=closed]:animate-[folio-palette-out_var(--duration-fast)_var(--ease)]'\n\n/** Search, actions and an optional result detail inside one accessible modal. */\nexport function SearchPalette({\n open, onOpenChange, label, inputLabel, placeholder, query, onQueryChange,\n groups, emptyLabel, labels, detail, onContentLinkClick, onCloseAutoFocus,\n}: SearchPaletteProps) {\n const input = useRef<HTMLInputElement>(null)\n const previousFocus = useRef<HTMLElement | null>(null)\n const showingDetail = Boolean(detail)\n useEffect(() => {\n if (open) input.current?.focus()\n }, [open, showingDetail])\n const close = () => onOpenChange(false)\n const field = { input, inputLabel, placeholder, query, onQueryChange }\n\n return (\n <CommandDialog\n open={open} onOpenChange={onOpenChange} label={label} inputLabel={inputLabel} shouldFilter={false}\n // Placed on the optical centre rather than near the top edge: the panel's\n // resting height (field, ten rows, footer) puts its middle a little above\n // the viewport's. It settles in from a 6px lift and a hair of scale, and\n // leaves faster than it arrives, so closing never feels like waiting.\n className={PANEL}\n onOpenAutoFocus={() => {\n previousFocus.current = document.activeElement instanceof HTMLElement ? document.activeElement : null\n }}\n // A palette opened by a shortcut has no trigger for the dialog to return\n // focus to, so it goes back to whatever held it before opening.\n onCloseAutoFocus={(event) => {\n onCloseAutoFocus?.(event)\n if (!event.defaultPrevented) {\n event.preventDefault()\n previousFocus.current?.focus()\n }\n }}\n onEscapeKeyDown={(event) => {\n if (detail) { event.preventDefault(); detail.onBack() }\n }}\n >\n {detail\n ? <SearchDetail {...field} detail={detail} labels={labels} onClose={close} onContentLinkClick={onContentLinkClick} />\n : <SearchResults {...field} label={label} groups={groups} emptyLabel={emptyLabel} closeLabel={labels.close} onClose={close} />}\n <CommandFooter className=\"folio-search-palette__footer\">\n {!detail && <CommandHint keys={['↑', '↓']}>{labels.navigate}</CommandHint>}\n <CommandHint keys={['↵']}>{labels.select}</CommandHint>\n <CommandHint keys={['esc']}>{detail ? labels.back : labels.close}</CommandHint>\n </CommandFooter>\n </CommandDialog>\n )\n}\n\ninterface SearchFieldProps {\n input: RefObject<HTMLInputElement | null>\n inputLabel: string\n placeholder: string\n query: string\n onQueryChange: (query: string) => void\n onClose: () => void\n}\n\ninterface SearchResultsProps extends SearchFieldProps {\n label: string\n groups: SearchPaletteGroup[]\n emptyLabel: ReactNode\n closeLabel: string\n}\n\n/** The combobox over the host's groups, which the palette never filters again. */\nfunction SearchResults({ input, inputLabel, placeholder, query, onQueryChange, onClose, label, groups, emptyLabel, closeLabel }: SearchResultsProps) {\n return (\n <>\n <div className=\"folio-search-palette__search\">\n <CommandInput ref={input} aria-label={inputLabel} placeholder={placeholder} value={query} onValueChange={onQueryChange} autoComplete=\"off\" spellCheck={false} />\n <Button variant=\"ghost\" iconOnly aria-label={closeLabel} onClick={onClose} className=\"folio-search-palette__close\"><RiCloseLine size={18} aria-hidden /></Button>\n </div>\n <CommandList label={label}>\n <CommandEmpty>{emptyLabel}</CommandEmpty>\n {groups.map((group) => (\n <CommandGroup key={group.id} heading={<span className=\"folio-search-palette__heading\">{group.label}</span>}>\n {group.items.map((item) => (\n <CommandItem\n key={item.id} value={item.id} icon={item.icon} onSelect={item.onSelect} className=\"folio-search-palette__item\"\n meta={item.meta ? <span className=\"folio-search-palette__meta\">{item.meta}</span> : undefined}\n >\n <span className=\"folio-search-palette__result\">\n <span>{item.title}</span>\n {item.description && <span className=\"folio-search-palette__description\">{item.description}</span>}\n </span>\n </CommandItem>\n ))}\n </CommandGroup>\n ))}\n </CommandList>\n </>\n )\n}\n\ninterface SearchDetailProps extends SearchFieldProps {\n detail: NonNullable<SearchPaletteProps['detail']>\n labels: SearchPaletteProps['labels']\n onContentLinkClick?: () => void\n}\n\n/**\n * The keys the command list takes for itself: moving its highlight, and running\n * the highlighted row with Enter, which it does by cancelling the key. The\n * detail view has no list, and needs them back — Enter submits the question or\n * follows a link, and the others move the caret or scroll the answer — so they\n * stop here instead of reaching it.\n */\nconst LIST_KEYS = new Set(['Enter', 'Home', 'End', 'ArrowUp', 'ArrowDown'])\n\nfunction keepFromList(event: KeyboardEvent<HTMLElement>) {\n if (LIST_KEYS.has(event.key)) event.stopPropagation()\n}\n\n/** The answer view: a question field over content the host renders. */\nfunction SearchDetail({ input, inputLabel, placeholder, query, onQueryChange, onClose, detail, labels, onContentLinkClick }: SearchDetailProps) {\n return (\n <>\n <form className=\"folio-search-palette__input\" onKeyDown={keepFromList} onSubmit={(event) => {\n event.preventDefault()\n if (query.trim() && !detail.submitDisabled) detail.onSubmit()\n }}>\n <Button variant=\"ghost\" iconOnly aria-label={labels.back} onClick={detail.onBack}><RiArrowLeftLine size={18} aria-hidden /></Button>\n <Input ref={input} aria-label={inputLabel} placeholder={placeholder} value={query} onChange={(event) => onQueryChange(event.target.value)} autoComplete=\"off\" />\n <Badge>{detail.label}</Badge>\n <Button variant=\"ghost\" iconOnly aria-label={labels.close} onClick={onClose}><RiCloseLine size={18} aria-hidden /></Button>\n </form>\n <div className=\"folio-search-palette__detail\" aria-live=\"polite\" onKeyDown={keepFromList} onClick={(event) => {\n if ((event.target as HTMLElement).closest('a')) onContentLinkClick?.()\n }}>{detail.content}</div>\n </>\n )\n}\n\nexport interface SearchExcerptProps {\n segments: ReadonlyArray<{ text: string; highlighted: boolean }>\n}\n\n/** Render query highlights as text, never as consumer-supplied HTML. */\nexport function SearchExcerpt({ segments }: SearchExcerptProps) {\n return <>{segments.map((segment, index) => segment.highlighted\n ? <mark className=\"folio-search-palette__highlight\" key={index}>{segment.text}</mark>\n : segment.text)}</>\n}\n"],"mappings":"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","names":[]}
@@ -9,6 +9,15 @@ interface RecordSectionProps extends HTMLAttributes<HTMLElement> {
9
9
  }
10
10
  /** A named ledger section. The host supplies its chronology without reordering. */
11
11
  declare function RecordSection({ title, count, headingId, children, className, ...rest }: RecordSectionProps): react.JSX.Element;
12
+ /**
13
+ * One entry in a record's tag row. A bare string is a text chip; an object can
14
+ * add a mark beside the label. The mark is decoration — `aria-hidden` — so the
15
+ * label is still the whole of what a screen reader hears.
16
+ */
17
+ type CareerRecordTag = string | {
18
+ label: string;
19
+ icon?: ReactNode;
20
+ };
12
21
  interface CareerRecordProps extends Omit<HTMLAttributes<HTMLElement>, 'title'> {
13
22
  title: string;
14
23
  organization: ReactNode;
@@ -17,7 +26,7 @@ interface CareerRecordProps extends Omit<HTMLAttributes<HTMLElement>, 'title'> {
17
26
  location: string;
18
27
  locationLabel: string;
19
28
  description: string[];
20
- tags: string[];
29
+ tags: CareerRecordTag[];
21
30
  tagsLabel: string;
22
31
  mark?: ReactNode;
23
32
  currentLabel?: string;
@@ -77,4 +86,4 @@ declare function Timeline({ className, ...props }: HTMLAttributes<HTMLDivElement
77
86
  /** A record mount, isolated so motion can follow the shared data-motion axis. */
78
87
  declare function TimelineItem({ className, ...props }: HTMLAttributes<HTMLDivElement>): react.JSX.Element;
79
88
 
80
- export { CareerRecord, type CareerRecordProps, ProfileFigure, type ProfileFigureProps, ProfileInterests, type ProfileInterestsProps, ProfileNarrative, type ProfileNarrativeProps, ProfileSection, type ProfileSectionProps, type ProfileSkill, ProfileSkills, type ProfileSkillsProps, RecordSection, type RecordSectionProps, Timeline, TimelineItem };
89
+ export { CareerRecord, type CareerRecordProps, type CareerRecordTag, ProfileFigure, type ProfileFigureProps, ProfileInterests, type ProfileInterestsProps, ProfileNarrative, type ProfileNarrativeProps, ProfileSection, type ProfileSectionProps, type ProfileSkill, ProfileSkills, type ProfileSkillsProps, RecordSection, type RecordSectionProps, Timeline, TimelineItem };
@@ -29,7 +29,13 @@ function CareerRecord({ title, organization, period, periodLabel, location, loca
29
29
  /* @__PURE__ */ jsx("div", { className: "mt-2", children: organization }),
30
30
  description.length === 1 && /* @__PURE__ */ jsx(Text, { className: "mb-0 mt-5 max-w-(--measure-record)", children: description[0] }),
31
31
  description.length > 1 && /* @__PURE__ */ jsx("ul", { className: "folio-career-record__description", children: description.map((point, index) => /* @__PURE__ */ jsx(Text, { as: "li", children: point }, index)) }),
32
- tags.length > 0 && /* @__PURE__ */ jsx("ul", { "aria-label": tagsLabel, className: "folio-career-record__tags", children: tags.map((tag, index) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(Tag, { children: tag }) }, `${tag}-${index}`)) })
32
+ tags.length > 0 && /* @__PURE__ */ jsx("ul", { "aria-label": tagsLabel, className: "folio-career-record__tags", children: tags.map((tag, index) => {
33
+ const { label, icon } = typeof tag === "string" ? { label: tag, icon: void 0 } : tag;
34
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(Tag, { className: "folio-career-record__tag", children: [
35
+ icon && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "folio-career-record__tag-mark", children: icon }),
36
+ label
37
+ ] }) }, `${label}-${index}`);
38
+ }) })
33
39
  ] }),
34
40
  mark && /* @__PURE__ */ jsx("div", { className: "folio-career-record__mark", children: mark })
35
41
  ] });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/website/Timeline/Timeline.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from 'react'\nimport { Badge } from '../../components/Badge/Badge'\nimport { Heading } from '../../components/Heading/Heading'\nimport { Tag } from '../../components/Tag/Tag'\nimport { Text } from '../../components/Text/Text'\nimport { cn } from '../../lib/cn'\n\nexport interface RecordSectionProps extends HTMLAttributes<HTMLElement> {\n title: string\n count?: string\n headingId: string\n children: ReactNode\n}\n\n/** A named ledger section. The host supplies its chronology without reordering. */\nexport function RecordSection({ title, count, headingId, children, className, ...rest }: RecordSectionProps) {\n return <section aria-labelledby={headingId} className={cn('folio-record-section', className)} {...rest}><header className=\"folio-record-section__header\"><Heading level={2} id={headingId}>{title}</Heading>{count && <Text as=\"span\" size=\"xs\" tone=\"muted\">{count}</Text>}</header>{children}</section>\n}\n\nexport interface CareerRecordProps extends Omit<HTMLAttributes<HTMLElement>, 'title'> {\n title: string\n organization: ReactNode\n period: string\n periodLabel: string\n location: string\n locationLabel: string\n description: string[]\n tags: string[]\n tagsLabel: string\n mark?: ReactNode\n currentLabel?: string\n}\n\n/** One dated record, reusable for a role, qualification or other chronology. */\nexport function CareerRecord({ title, organization, period, periodLabel, location, locationLabel, description, tags, tagsLabel, mark, currentLabel, className, ...rest }: CareerRecordProps) {\n return (\n <article className={cn('folio-career-record', className)} {...rest}>\n <div className=\"folio-career-record__dates\"><dl><dt className=\"sr-only\">{periodLabel}</dt><dd><Text as=\"span\" size=\"sm\" tone=\"strong\">{period}</Text></dd><dt className=\"sr-only\">{locationLabel}</dt><dd><Text as=\"span\" size=\"xs\" tone=\"muted\">{location}</Text></dd></dl>{currentLabel && <Badge>{currentLabel}</Badge>}</div>\n <div className=\"folio-career-record__body\"><Heading level={3} size=\"heading\">{title}</Heading><div className=\"mt-2\">{organization}</div>{description.length === 1 && <Text className=\"mb-0 mt-5 max-w-(--measure-record)\">{description[0]}</Text>}{description.length > 1 && <ul className=\"folio-career-record__description\">{description.map((point, index) => <Text as=\"li\" key={index}>{point}</Text>)}</ul>}{tags.length > 0 && <ul aria-label={tagsLabel} className=\"folio-career-record__tags\">{tags.map((tag, index) => <li key={`${tag}-${index}`}><Tag>{tag}</Tag></li>)}</ul>}</div>\n {mark && <div className=\"folio-career-record__mark\">{mark}</div>}\n </article>\n )\n}\n\nexport interface ProfileSectionProps extends HTMLAttributes<HTMLElement> {\n headingId: string\n title: string\n description?: string\n children: ReactNode\n}\n\n/** A profile section follows the same heading and spacing contract as its neighbors. */\nexport function ProfileSection({ headingId, title, description, children, className, ...rest }: ProfileSectionProps) {\n return <section aria-labelledby={headingId} className={cn('folio-profile-section', className)} {...rest}><header className=\"folio-profile-section__header\"><Heading level={2} id={headingId}>{title}</Heading>{description && <Text className=\"m-0 max-w-[42rem]\">{description}</Text>}</header>{children}</section>\n}\n\nexport interface ProfileNarrativeProps {\n headingId: string\n title: string\n pull: string\n paragraphs: string[]\n action?: ReactNode\n}\n\n/** A short introduction with a pull quote and a longer account alongside it. */\nexport function ProfileNarrative({ headingId, title, pull, paragraphs, action }: ProfileNarrativeProps) {\n return <section aria-labelledby={headingId} className=\"folio-profile-narrative\"><div><Heading level={2} id={headingId}>{title}</Heading><Text size=\"lead\" tone=\"strong\" className=\"mb-0 mt-6 max-w-[30ch]\">{pull}</Text></div><div>{paragraphs.map((paragraph, index) => <Text key={index} className=\"mb-5 mt-0 max-w-(--measure-record)\">{paragraph}</Text>)}{action}</div></section>\n}\n\nexport interface ProfileSkill {\n id: string\n label: string\n icon?: ReactNode\n darkIcon?: ReactNode\n invertInDark?: boolean\n}\n\nexport interface ProfileSkillsProps {\n groups: { id: string; title: string; items: ProfileSkill[] }[]\n}\n\n/** Skill labels and theme-aware artwork, supplied by the host's content adapter. */\nexport function ProfileSkills({ groups }: ProfileSkillsProps) {\n return <div className=\"folio-profile-skills\">{groups.map((group) => <section className=\"folio-profile-skills__group\" key={group.id}><Heading level={3} size=\"item\">{group.title}</Heading><ul className=\"folio-profile-skills__items\">{group.items.map((skill) => <li key={skill.id}><span className=\"folio-profile-skill__icon\" data-invert={skill.invertInDark || undefined} data-has-dark={Boolean(skill.darkIcon) || undefined} aria-hidden><span data-artwork=\"light\">{skill.icon}</span>{skill.darkIcon && <span data-artwork=\"dark\">{skill.darkIcon}</span>}</span><Text as=\"span\" size=\"sm\" tone=\"strong\">{skill.label}</Text></li>)}</ul></section>)}</div>\n}\n\nexport interface ProfileInterestsProps {\n items: { id: string; title: string; description: string }[]\n}\n\n/** A small set of interests or areas of practice. */\nexport function ProfileInterests({ items }: ProfileInterestsProps) {\n return <div className=\"folio-profile-interests\">{items.map((item) => <article key={item.id}><Heading level={3}>{item.title}</Heading><Text className=\"mb-0 mt-4\">{item.description}</Text></article>)}</div>\n}\n\nexport interface ProfileFigureProps {\n children: ReactNode\n caption?: string\n}\n\n/** Source-faithful profile photography: retain the supplied image's aspect ratio. */\nexport function ProfileFigure({ children, caption }: ProfileFigureProps) {\n return <figure className=\"folio-profile-figure\"><div className=\"folio-profile-figure__image\">{children}</div>{caption && <Text as=\"figcaption\" size=\"xs\" tone=\"muted\" className=\"mt-3\">{caption}</Text>}</figure>\n}\n\n/** An ordered collection frame. No sorting or business data is owned here. */\nexport function Timeline({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\n return <div className={cn('folio-timeline', className)} {...props} />\n}\n\n/** A record mount, isolated so motion can follow the shared data-motion axis. */\nexport function TimelineItem({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\n return <div className={cn('folio-timeline__item', className)} {...props} />\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/website/Timeline/Timeline.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from 'react'\nimport { Badge } from '../../components/Badge/Badge'\nimport { Heading } from '../../components/Heading/Heading'\nimport { Tag } from '../../components/Tag/Tag'\nimport { Text } from '../../components/Text/Text'\nimport { cn } from '../../lib/cn'\n\nexport interface RecordSectionProps extends HTMLAttributes<HTMLElement> {\n title: string\n count?: string\n headingId: string\n children: ReactNode\n}\n\n/** A named ledger section. The host supplies its chronology without reordering. */\nexport function RecordSection({ title, count, headingId, children, className, ...rest }: RecordSectionProps) {\n return <section aria-labelledby={headingId} className={cn('folio-record-section', className)} {...rest}><header className=\"folio-record-section__header\"><Heading level={2} id={headingId}>{title}</Heading>{count && <Text as=\"span\" size=\"xs\" tone=\"muted\">{count}</Text>}</header>{children}</section>\n}\n\n/**\n * One entry in a record's tag row. A bare string is a text chip; an object can\n * add a mark beside the label. The mark is decoration — `aria-hidden` — so the\n * label is still the whole of what a screen reader hears.\n */\nexport type CareerRecordTag = string | { label: string; icon?: ReactNode }\n\nexport interface CareerRecordProps extends Omit<HTMLAttributes<HTMLElement>, 'title'> {\n title: string\n organization: ReactNode\n period: string\n periodLabel: string\n location: string\n locationLabel: string\n description: string[]\n tags: CareerRecordTag[]\n tagsLabel: string\n mark?: ReactNode\n currentLabel?: string\n}\n\n/** One dated record, reusable for a role, qualification or other chronology. */\nexport function CareerRecord({ title, organization, period, periodLabel, location, locationLabel, description, tags, tagsLabel, mark, currentLabel, className, ...rest }: CareerRecordProps) {\n return (\n <article className={cn('folio-career-record', className)} {...rest}>\n <div className=\"folio-career-record__dates\"><dl><dt className=\"sr-only\">{periodLabel}</dt><dd><Text as=\"span\" size=\"sm\" tone=\"strong\">{period}</Text></dd><dt className=\"sr-only\">{locationLabel}</dt><dd><Text as=\"span\" size=\"xs\" tone=\"muted\">{location}</Text></dd></dl>{currentLabel && <Badge>{currentLabel}</Badge>}</div>\n <div className=\"folio-career-record__body\"><Heading level={3} size=\"heading\">{title}</Heading><div className=\"mt-2\">{organization}</div>{description.length === 1 && <Text className=\"mb-0 mt-5 max-w-(--measure-record)\">{description[0]}</Text>}{description.length > 1 && <ul className=\"folio-career-record__description\">{description.map((point, index) => <Text as=\"li\" key={index}>{point}</Text>)}</ul>}{tags.length > 0 && <ul aria-label={tagsLabel} className=\"folio-career-record__tags\">{tags.map((tag, index) => { const { label, icon } = typeof tag === 'string' ? { label: tag, icon: undefined } : tag; return <li key={`${label}-${index}`}><Tag className=\"folio-career-record__tag\">{icon && <span aria-hidden=\"true\" className=\"folio-career-record__tag-mark\">{icon}</span>}{label}</Tag></li> })}</ul>}</div>\n {mark && <div className=\"folio-career-record__mark\">{mark}</div>}\n </article>\n )\n}\n\nexport interface ProfileSectionProps extends HTMLAttributes<HTMLElement> {\n headingId: string\n title: string\n description?: string\n children: ReactNode\n}\n\n/** A profile section follows the same heading and spacing contract as its neighbors. */\nexport function ProfileSection({ headingId, title, description, children, className, ...rest }: ProfileSectionProps) {\n return <section aria-labelledby={headingId} className={cn('folio-profile-section', className)} {...rest}><header className=\"folio-profile-section__header\"><Heading level={2} id={headingId}>{title}</Heading>{description && <Text className=\"m-0 max-w-[42rem]\">{description}</Text>}</header>{children}</section>\n}\n\nexport interface ProfileNarrativeProps {\n headingId: string\n title: string\n pull: string\n paragraphs: string[]\n action?: ReactNode\n}\n\n/** A short introduction with a pull quote and a longer account alongside it. */\nexport function ProfileNarrative({ headingId, title, pull, paragraphs, action }: ProfileNarrativeProps) {\n return <section aria-labelledby={headingId} className=\"folio-profile-narrative\"><div><Heading level={2} id={headingId}>{title}</Heading><Text size=\"lead\" tone=\"strong\" className=\"mb-0 mt-6 max-w-[30ch]\">{pull}</Text></div><div>{paragraphs.map((paragraph, index) => <Text key={index} className=\"mb-5 mt-0 max-w-(--measure-record)\">{paragraph}</Text>)}{action}</div></section>\n}\n\nexport interface ProfileSkill {\n id: string\n label: string\n icon?: ReactNode\n darkIcon?: ReactNode\n invertInDark?: boolean\n}\n\nexport interface ProfileSkillsProps {\n groups: { id: string; title: string; items: ProfileSkill[] }[]\n}\n\n/** Skill labels and theme-aware artwork, supplied by the host's content adapter. */\nexport function ProfileSkills({ groups }: ProfileSkillsProps) {\n return <div className=\"folio-profile-skills\">{groups.map((group) => <section className=\"folio-profile-skills__group\" key={group.id}><Heading level={3} size=\"item\">{group.title}</Heading><ul className=\"folio-profile-skills__items\">{group.items.map((skill) => <li key={skill.id}><span className=\"folio-profile-skill__icon\" data-invert={skill.invertInDark || undefined} data-has-dark={Boolean(skill.darkIcon) || undefined} aria-hidden><span data-artwork=\"light\">{skill.icon}</span>{skill.darkIcon && <span data-artwork=\"dark\">{skill.darkIcon}</span>}</span><Text as=\"span\" size=\"sm\" tone=\"strong\">{skill.label}</Text></li>)}</ul></section>)}</div>\n}\n\nexport interface ProfileInterestsProps {\n items: { id: string; title: string; description: string }[]\n}\n\n/** A small set of interests or areas of practice. */\nexport function ProfileInterests({ items }: ProfileInterestsProps) {\n return <div className=\"folio-profile-interests\">{items.map((item) => <article key={item.id}><Heading level={3}>{item.title}</Heading><Text className=\"mb-0 mt-4\">{item.description}</Text></article>)}</div>\n}\n\nexport interface ProfileFigureProps {\n children: ReactNode\n caption?: string\n}\n\n/** Source-faithful profile photography: retain the supplied image's aspect ratio. */\nexport function ProfileFigure({ children, caption }: ProfileFigureProps) {\n return <figure className=\"folio-profile-figure\"><div className=\"folio-profile-figure__image\">{children}</div>{caption && <Text as=\"figcaption\" size=\"xs\" tone=\"muted\" className=\"mt-3\">{caption}</Text>}</figure>\n}\n\n/** An ordered collection frame. No sorting or business data is owned here. */\nexport function Timeline({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\n return <div className={cn('folio-timeline', className)} {...props} />\n}\n\n/** A record mount, isolated so motion can follow the shared data-motion axis. */\nexport function TimelineItem({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\n return <div className={cn('folio-timeline__item', className)} {...props} />\n}\n"],"mappings":"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","names":[]}
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * A preset is a combination of theme axes, never a block of its own, so this
5
5
  * is the whole of it: linen ground and warm ink, an ember accent for links and
6
- * the current state, primary buttons in ink, headings in the grotesk at a quiet voice, and quiet rules. Radius, motion
6
+ * the current state, primary buttons in ink and every control a capsule, headings in the grotesk at a quiet voice, and quiet rules. Radius, motion
7
7
  * and density stay on their defaults. Spread it onto the root element:
8
8
  *
9
9
  * ```tsx
@@ -17,6 +17,7 @@ declare const WORKBENCH_THEME: {
17
17
  readonly 'data-type': "grotesk";
18
18
  readonly 'data-rules': "quiet";
19
19
  readonly 'data-voice': "quiet";
20
+ readonly 'data-controls': "pill";
20
21
  };
21
22
 
22
23
  export { WORKBENCH_THEME };
@@ -4,7 +4,8 @@ const WORKBENCH_THEME = {
4
4
  "data-action": "ink",
5
5
  "data-type": "grotesk",
6
6
  "data-rules": "quiet",
7
- "data-voice": "quiet"
7
+ "data-voice": "quiet",
8
+ "data-controls": "pill"
8
9
  };
9
10
  export {
10
11
  WORKBENCH_THEME
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/website/Workbench/theme.ts"],"sourcesContent":["/**\n * The Workbench preset as the attributes a host writes on `<html>`.\n *\n * A preset is a combination of theme axes, never a block of its own, so this\n * is the whole of it: linen ground and warm ink, an ember accent for links and\n * the current state, primary buttons in ink, headings in the grotesk at a quiet voice, and quiet rules. Radius, motion\n * and density stay on their defaults. Spread it onto the root element:\n *\n * ```tsx\n * <html {...WORKBENCH_THEME} data-mode=\"light\">\n * ```\n */\nexport const WORKBENCH_THEME = {\n 'data-surface': 'linen',\n 'data-accent': 'ember',\n 'data-action': 'ink',\n 'data-type': 'grotesk',\n 'data-rules': 'quiet',\n 'data-voice': 'quiet',\n} as const\n"],"mappings":"AAYO,MAAM,kBAAkB;AAAA,EAC7B,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf,eAAe;AAAA,EACf,aAAa;AAAA,EACb,cAAc;AAAA,EACd,cAAc;AAChB;","names":[]}
1
+ {"version":3,"sources":["../../../src/website/Workbench/theme.ts"],"sourcesContent":["/**\n * The Workbench preset as the attributes a host writes on `<html>`.\n *\n * A preset is a combination of theme axes, never a block of its own, so this\n * is the whole of it: linen ground and warm ink, an ember accent for links and\n * the current state, primary buttons in ink and every control a capsule, headings in the grotesk at a quiet voice, and quiet rules. Radius, motion\n * and density stay on their defaults. Spread it onto the root element:\n *\n * ```tsx\n * <html {...WORKBENCH_THEME} data-mode=\"light\">\n * ```\n */\nexport const WORKBENCH_THEME = {\n 'data-surface': 'linen',\n 'data-accent': 'ember',\n 'data-action': 'ink',\n 'data-type': 'grotesk',\n 'data-rules': 'quiet',\n 'data-voice': 'quiet',\n 'data-controls': 'pill',\n} as const\n"],"mappings":"AAYO,MAAM,kBAAkB;AAAA,EAC7B,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf,eAAe;AAAA,EACf,aAAa;AAAA,EACb,cAAc;AAAA,EACd,cAAc;AAAA,EACd,iBAAiB;AACnB;","names":[]}
@@ -4,10 +4,10 @@ export { Portfolio, PortfolioCategories, PortfolioCategoriesProps, PortfolioImag
4
4
  export { QuestionCard, QuestionCardProps } from './Portfolio/QuestionCard.js';
5
5
  export { PortfolioIndex, PortfolioIndexItem, PortfolioIndexProps } from './PortfolioIndex/PortfolioIndex.js';
6
6
  export { CollectionControls, CollectionControlsProps, CollectionEmpty, CollectionEmptyProps, CollectionFilter, CollectionFooter, CollectionFooterProps, CollectionGroup, CollectionGroupProps, CollectionIntro, CollectionIntroProps, CollectionLink, CollectionList, CollectionListProps, CollectionMetadata, CollectionMetadataItem, CollectionMetadataProps, CollectionSearch, CollectionSearchProps, CollectionTally, CollectionTallyProps, ContentBand, ContentBandProps, FeaturedRecord, FeaturedRecordProps, HighlightedText, HighlightedTextProps, RecordRow, RecordRowProps, ReferenceLink, ReferenceRows, ReferenceRowsProps, TaxonomyLinks, TaxonomyLinksProps } from './Collection/Collection.js';
7
- export { ActionBand, ActionBandProps, ArchitectureColumnNode, ArchitectureColumns, ArchitectureColumnsProps, AuthorPanel, AuthorPanelProps, CommandPiece, CommandSnippet, CommandSnippetProps, CommandTab, CommandTabs, CommandTabsProps, ContentFigure, ContentFigureProps, DetailMasthead, DetailMastheadProps, DevicePreview, DevicePreviewProps, FeaturePairs, FeaturePairsProps, MediaPlayback, MediaPlaybackProps, MobileOutline, MobileOutlineProps, OutlineItem, PreviewPair, PreviewPairProps, ReadingFigure, ReadingFigureProps, ReadingForm, ReadingFormProps, ReadingLayout, ReadingLayoutProps, ReadingLead, ReadingLeadProps, ReadingMetadataItem, ReadingPage, ReadingPageProps, ReadingPagerItem, ReadingProgress, ReadingSection, ReadingSectionProps, ReadingSurface, ReadingSurfaceProps, RecordPager, RecordPagerProps, TableOfContents, TableOfContentsProps, TokenLine, TokenLineProps, isSelfNumbered, numberToc } from './Reading/Reading.js';
7
+ export { ActionBand, ActionBandProps, ArchitectureColumnNode, ArchitectureColumns, ArchitectureColumnsProps, AuthorPanel, AuthorPanelProps, CommandPiece, CommandSnippet, CommandSnippetProps, CommandTab, CommandTabs, CommandTabsProps, ContentFigure, ContentFigureProps, DetailMasthead, DetailMastheadProps, DevicePreview, DevicePreviewProps, DeviceRow, DeviceRowProps, FeaturePairs, FeaturePairsProps, MediaPlayback, MediaPlaybackProps, MobileOutline, MobileOutlineProps, OutlineItem, PreviewPair, PreviewPairProps, ReadingFigure, ReadingFigureProps, ReadingForm, ReadingFormProps, ReadingLayout, ReadingLayoutProps, ReadingLead, ReadingLeadProps, ReadingMetadataItem, ReadingPage, ReadingPageProps, ReadingPagerItem, ReadingProgress, ReadingSection, ReadingSectionProps, ReadingSurface, ReadingSurfaceProps, RecordPager, RecordPagerProps, TableOfContents, TableOfContentsProps, TokenLine, TokenLineProps, isSelfNumbered, numberToc } from './Reading/Reading.js';
8
8
  export { ContactFacts, ContactFactsProps, ContactFormLabels, ContactFormValues, ContactFormView, ContactFormViewProps, CorrespondenceSection, CorrespondenceSectionProps, LocationFigure, LocationFigureProps, QuestionList, QuestionListProps } from './Contact/Contact.js';
9
9
  export { MediaCollectionCard, MediaCollectionCardProps, MediaListRow, MediaListRowProps, MediaPreviewAction, MusicFeature, MusicFeatureProps, MusicSection, MusicSectionProps } from './Music/Music.js';
10
- export { CareerRecord, CareerRecordProps, ProfileFigure, ProfileFigureProps, ProfileInterests, ProfileInterestsProps, ProfileNarrative, ProfileNarrativeProps, ProfileSection, ProfileSectionProps, ProfileSkill, ProfileSkills, ProfileSkillsProps, RecordSection, RecordSectionProps, Timeline, TimelineItem } from './Timeline/Timeline.js';
10
+ export { CareerRecord, CareerRecordProps, CareerRecordTag, ProfileFigure, ProfileFigureProps, ProfileInterests, ProfileInterestsProps, ProfileNarrative, ProfileNarrativeProps, ProfileSection, ProfileSectionProps, ProfileSkill, ProfileSkills, ProfileSkillsProps, RecordSection, RecordSectionProps, Timeline, TimelineItem } from './Timeline/Timeline.js';
11
11
  export { LoadingRecords, LoadingRecordsProps, LoadingRouteIntro, LoadingRouteIntroProps, RecoveryLinks, RecoveryLinksProps, RecoveryState, RecoveryStateProps, WebsiteLoading, WebsiteLoadingProps } from './Recovery/Recovery.js';
12
12
  export { MediaCollection, MediaCollectionProps, MediaDetailLayout, MediaDetailLayoutProps, MediaGallery, MediaGalleryItem, MediaGalleryItemProps, MediaGalleryProps, MediaGallerySkeleton, MediaGallerySkeletonProps, MediaLightNote, MediaLightNoteLine, MediaLightNoteProps, MediaMapArea, MediaMapAreaList, MediaMapAreaListProps, MediaMapCanvas, MediaMapCanvasProps, MediaMapFrame, MediaMapFrameProps, MediaMapMarker, MediaMapMarkerProps, MediaMapNavigation, MediaMapNavigationProps, MediaMapPin, MediaMapPinProps, MediaMapPreview, MediaMapPreviewProps, MediaMapSection, MediaMapSectionProps, MediaMapSkeleton, MediaMasthead, MediaMastheadProps, MediaMetadata, MediaMetadataProps, MediaPager, MediaPagerItem, MediaPagerItemProps, MediaPagerProps, MediaThumbnail, MediaThumbnailProps, MediaThumbnailStrip, MediaThumbnailStripProps, PhotographFeature, PhotographFeatureProps } from './Media/Media.js';
13
13
  export { MediaLightbox, MediaLightboxProps } from './Media/MediaLightbox.js';
@@ -19,7 +19,7 @@
19
19
  .folio-collection-metadata dd{margin:.5rem 0 0;color:var(--ink)}
20
20
  .folio-collection-group{padding-block-start:clamp(3rem,7vw,6rem)}
21
21
  .folio-collection-group[data-compact]{padding-block-start:1.5rem}
22
- .folio-collection-group-heading{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:1rem;border-block-end:1px solid var(--rule-hard);padding-block-end:1rem;margin-block-end:1rem}
22
+ .folio-collection-group-heading{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:1rem;border-block-end:1px solid var(--rule-divider);padding-block-end:1rem;margin-block-end:1rem}
23
23
  .folio-collection-group-heading h2{margin:0;font-family:var(--serif-i18n,var(--font-heading));font-weight:var(--display-weight);font-size:var(--fs-lead);line-height:1.1;letter-spacing:-.028em}
24
24
  .folio-collection-group-heading>span{font-family:var(--mono-i18n,var(--font-mono));font-size:.6875rem;color:var(--ink-3-aa)}
25
25
  .folio-collection-controls{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:1rem 2rem;border-block:1px solid var(--rule);padding-block:1rem}
@@ -65,7 +65,7 @@
65
65
  .folio-collection-footer{padding-block-start:clamp(2rem,5vw,4rem)}
66
66
  .folio-collection-footer>div{display:flex;flex-wrap:wrap;justify-content:center;align-items:center;gap:1rem 2rem}
67
67
  .folio-taxonomy-links{display:flex;flex-wrap:wrap;gap:.5rem;margin-block:1.5rem}
68
- .folio-reference-rows{margin:0;border-block-start:1px solid var(--rule-hard)}
68
+ .folio-reference-rows{margin:0;border-block-start:1px solid var(--rule-divider)}
69
69
  .folio-reference-rows>div{display:grid;grid-template-columns:minmax(10rem,1fr) minmax(0,3fr);gap:1.5rem;padding-block:1.5rem;border-block-end:1px solid var(--rule)}
70
70
  .folio-reference-rows dt{font-family:var(--mono-i18n,var(--font-mono));font-size:.75rem;color:var(--ink-3-aa)}
71
71
  .folio-reference-rows dt p{font-family:var(--sans);font-size:.8125rem;line-height:1.6;margin-block:.5rem 0}
@@ -2,15 +2,21 @@
2
2
  display: grid;
3
3
  align-items: start;
4
4
  gap: 3rem;
5
- border-block-start: 1px solid var(--rule-hard);
5
+ border-block-start: 1px solid var(--rule-divider);
6
6
  padding-block: clamp(2.5rem, 5vw, 4rem) clamp(4rem, 8vw, 7rem);
7
7
  }
8
8
  .folio-contact-form, .folio-contact-facts { min-inline-size: 0; }
9
- .folio-contact-form__fields { display: grid; gap: 2rem; border-block-start: 1px solid var(--rule-hard); padding-block-start: 2rem; }
9
+ .folio-contact-form__fields { display: grid; gap: 2rem; border-block-start: 1px solid var(--rule-divider); padding-block-start: 2rem; }
10
10
  .folio-contact-form__identity { display: grid; gap: 2rem; }
11
11
  .folio-contact-form__actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 1.25rem; }
12
12
  .folio-contact-facts__links { display: flex; flex-wrap: wrap; gap: .5rem 1rem; margin-block-start: 1rem; }
13
- .folio-question-list__items { border-block-start: 1px solid var(--rule-hard); }
13
+ /* A fact's link sits on the same left edge as its label: a ghost button's
14
+ inline padding read as an indent where an icon had gone missing. The 44px
15
+ block size the host gives the link is kept. */
16
+ .folio-contact-facts dd > a:first-child,
17
+ .folio-contact-facts__links > a { padding-inline: 0; }
18
+ .folio-contact-facts__links { column-gap: 1.5rem; }
19
+ .folio-question-list__items { border-block-start: 1px solid var(--rule-divider); }
14
20
  .folio-question-list__item { display: grid; gap: 1rem; padding-block: 1.5rem; border-block-end: 1px solid var(--rule); }
15
21
  .folio-location-figure { margin: 0; }
16
22
  .folio-location-figure__map { position: relative; aspect-ratio: 16 / 10; overflow: hidden; border-radius: var(--radius-sm); background: var(--paper-2); }
@@ -3,7 +3,7 @@
3
3
  .folio-conversation-facts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.25rem 2rem; border-block: 1px solid var(--rule); padding-block: 1.25rem; }
4
4
  .folio-conversation-facts dd > span { display: inline-flex; align-items: center; gap: .5rem; }
5
5
  .folio-conversation-thread { min-inline-size: 0; }
6
- .folio-conversation-thread__intro { border-block-start: 1px solid var(--rule-hard); padding-block-start: 2rem; }
6
+ .folio-conversation-thread__intro { border-block-start: 1px solid var(--rule-divider); padding-block-start: 2rem; }
7
7
  .folio-conversation-thread__intro > h2 { max-inline-size: 22ch; text-wrap: balance; }
8
8
  .folio-conversation-thread__intro > p { max-inline-size: 56ch; }
9
9
  .folio-conversation-suggestions { margin-block-start: 2rem; }
@@ -11,12 +11,12 @@
11
11
  .folio-conversation-suggestions > ul > li, .folio-conversation-sources > ul > li { border-block-end: 1px solid var(--rule); }
12
12
  .folio-conversation-suggestions__action { inline-size: 100%; justify-content: space-between; text-align: start; gap: 1.5rem; padding-inline: 0; }
13
13
  .folio-conversation-suggestions__action > svg { flex-shrink: 0; }
14
- .folio-conversation-thread__controls { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: .25rem 1.5rem; border-block-start: 1px solid var(--rule-hard); padding-block-start: .75rem; }
14
+ .folio-conversation-thread__controls { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: .25rem 1.5rem; border-block-start: 1px solid var(--rule-divider); padding-block-start: .75rem; }
15
15
  .folio-conversation-thread__controls > div { display: flex; flex-wrap: wrap; gap: .25rem; }
16
16
  .folio-conversation-log { display: flex; flex-direction: column; }
17
17
  .folio-conversation-log[data-compact] { gap: 2rem; }
18
18
  .folio-conversation-thread__composer { margin-block-start: 3rem; }
19
- .folio-conversation-thread__composer[data-sticky] { position: sticky; inset-block-end: 0; z-index: 10; margin-block-start: 2rem; padding-block: 1rem .75rem; border-block-start: 1px solid var(--rule-hard); background: var(--paper); }
19
+ .folio-conversation-thread__composer[data-sticky] { position: sticky; inset-block-end: 0; z-index: 10; margin-block-start: 2rem; padding-block: 1rem .75rem; border-block-start: 1px solid var(--rule-divider); background: var(--paper); }
20
20
  .folio-conversation-thread__proposal { margin-block-end: 1.25rem; }
21
21
  .folio-question-composer__row { display: flex; align-items: end; gap: 1rem; }
22
22
  .folio-question-composer__field { flex: 1; min-inline-size: 0; }
@@ -43,13 +43,13 @@
43
43
  .folio-conversation-prose p.folio-conversation-prose__heading[data-lead] { margin-block-start: 0; }
44
44
  .folio-conversation-prose sup { font-size: .75em; }
45
45
  .folio-conversation-caret { display: inline-block; inline-size: .375rem; block-size: 1em; margin-inline-start: .2rem; vertical-align: -.1em; background: var(--ink); }
46
- .folio-conversation-sources { margin-block-start: 1.5rem; border-block-start: 1px solid var(--rule-hard); padding-block-start: .75rem; }
46
+ .folio-conversation-sources { margin-block-start: 1.5rem; border-block-start: 1px solid var(--rule-divider); padding-block-start: .75rem; }
47
47
  .folio-conversation-sources__header { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
48
48
  .folio-conversation-sources__link { display: grid; grid-template-columns: 2rem minmax(0, 1fr) auto; inline-size: 100%; align-items: baseline; text-align: start; gap: .75rem; padding-inline: 0; }
49
49
  .folio-conversation-sources__link[data-highlighted] { background: var(--accent-muted); }
50
50
  .folio-conversation-sources__link > span:nth-child(2) { min-inline-size: 0; }
51
51
  .folio-conversation-sources__link > span:nth-child(2) > span { display: block; }
52
- .folio-action-proposal__actions { margin-block-start: .375rem; border-block-start: 1px solid var(--rule-hard); }
52
+ .folio-action-proposal__actions { margin-block-start: .375rem; border-block-start: 1px solid var(--rule-divider); }
53
53
  .folio-action-proposal__action { display: flex; inline-size: 100%; justify-content: space-between; gap: 1rem; text-align: start; border-block-end: 1px solid var(--rule); padding-inline: 0; }
54
54
  .folio-action-proposal__action > span { min-inline-size: 0; }
55
55
  .folio-action-proposal__action > span:last-child { display: flex; align-items: center; gap: .75rem; flex-shrink: 0; }
@@ -7,7 +7,7 @@
7
7
  .folio-activity-trace li > span { overflow-wrap: anywhere; }
8
8
  .folio-activity-trace__summary { margin-inline-start: auto; flex-shrink: 0; white-space: nowrap; font-variant-numeric: tabular-nums; }
9
9
  .folio-evidence-panel { min-inline-size: 0; }
10
- .folio-evidence-panel > header { display: flex; align-items: start; justify-content: space-between; gap: 1rem; padding-block-end: 1rem; border-block-end: 1px solid var(--rule-hard); }
10
+ .folio-evidence-panel > header { display: flex; align-items: start; justify-content: space-between; gap: 1rem; padding-block-end: 1rem; border-block-end: 1px solid var(--rule-divider); }
11
11
  .folio-evidence-panel > header h2 { margin-block-start: .375rem; }
12
12
  .folio-evidence-panel > header > .folio-evidence-status { flex-shrink: 0; margin-block-start: .25rem; }
13
13
  .folio-evidence-panel > .folio-activity-trace { border-block-end: 1px solid var(--rule); padding-block: .75rem; }
@@ -34,7 +34,7 @@
34
34
  .folio-media-gallery-caption { display: grid; grid-template-columns: 2rem minmax(0, 1fr); align-items: baseline; gap: 0.25rem 0.625rem; padding-block-start: 0.875rem; }
35
35
  .folio-media-gallery-index,
36
36
  .folio-media-gallery-location { overflow: hidden; color: var(--secondary-text); font-family: var(--label-font); font-size: 0.625rem; letter-spacing: calc(0.1em * var(--label-tracking-scale)); text-overflow: ellipsis; white-space: nowrap; }
37
- .folio-media-gallery-location { grid-column: 2; letter-spacing: 0.14em; }
37
+ .folio-media-gallery-location { grid-column: 2; letter-spacing: calc(0.14em * var(--label-tracking-scale)); }
38
38
  .folio-media-gallery-title { display: -webkit-box; overflow: hidden; color: var(--foreground); font-family: var(--font-heading); font-size: 0.9375rem; font-weight: var(--display-weight); line-height: 1.22; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
39
39
  .folio-media-gallery-mount { block-size: auto; aspect-ratio: 1; }
40
40
 
@@ -125,15 +125,21 @@
125
125
  .folio-media-pager-link[data-direction='next'] { grid-template-columns: minmax(0, 1fr) 4.5rem; padding-inline: 1.5rem 0; }
126
126
  .folio-media-pager-copy { min-inline-size: 0; }
127
127
  .folio-media-pager-link[data-direction='next'] .folio-media-pager-copy { text-align: end; }
128
- .folio-media-pager-image { position: relative; display: block; block-size: 3.25rem; overflow: hidden; background: var(--background-elevated); }
129
- .folio-media-pager-image img { object-fit: contain; }
128
+ .folio-media-pager-image { position: relative; display: block; block-size: 3.25rem; overflow: hidden; border-radius: var(--radius-sm); background: var(--background-elevated); }
129
+ .folio-media-pager-image img { object-fit: cover; }
130
130
  .folio-media-pager-label { display: block; color: var(--secondary-text); font-family: var(--label-font); font-size: 0.625rem; letter-spacing: calc(0.16em * var(--label-tracking-scale)); text-transform: var(--label-case); }
131
131
  .folio-media-pager-title { display: block; overflow: hidden; margin-block-start: 0.3125rem; color: var(--foreground); font-family: var(--font-heading); font-size: 1.0625rem; font-weight: var(--display-weight); line-height: 1.2; text-overflow: ellipsis; white-space: nowrap; }
132
132
  .folio-media-thumbnails { padding-block-start: clamp(1.5rem, 3vw, 2.5rem); border-block-start: 1px solid var(--border-subtle); }
133
133
  .folio-media-thumbnails h2 { margin-block: 0 0.75rem; color: var(--foreground-muted); font-family: var(--label-font); font-size: 0.6875rem; font-weight: 400; letter-spacing: calc(0.13em * var(--label-tracking-scale)); text-transform: var(--label-case); }
134
134
  .folio-media-thumbnail-list { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: clamp(0.4rem, 1vw, 0.8rem); }
135
- .folio-media-thumbnail { position: relative; min-inline-size: 44px; min-block-size: 44px; aspect-ratio: 1; overflow: hidden; background: var(--background-elevated); }
136
- .folio-media-thumbnail img { object-fit: contain; }
135
+ /* Each frame fills its square. `contain` letterboxed every photograph onto an
136
+ elevated plate, so a strip of mixed orientations read as a row of grey
137
+ cards on a linen page — two grounds for one set of images. Cropped, the
138
+ plate only shows for the instant before the image decodes. */
139
+ .folio-media-thumbnail { position: relative; display: block; min-inline-size: 44px; min-block-size: 44px; aspect-ratio: 1; overflow: hidden; border-radius: var(--radius); background: var(--background-elevated); }
140
+ .folio-media-thumbnail img { object-fit: cover; transition: scale var(--duration-base) var(--ease), opacity var(--duration-base) var(--ease); }
141
+ .folio-media-thumbnail:hover img { scale: 1.02; }
142
+ @media (prefers-reduced-motion: reduce) { .folio-media-thumbnail img { transition: none; } .folio-media-thumbnail:hover img { scale: none; } }
137
143
 
138
144
  .folio-media-map-section { border-block: 1px solid var(--border-subtle); background: var(--background-elevated); padding-block: clamp(4rem, 10vw, 8rem); }
139
145
  .folio-media-anchor { display: block; block-size: 0; scroll-margin-block-start: var(--scroll-offset); pointer-events: none; }
@@ -1,6 +1,6 @@
1
1
  .folio-metrics-range { display: flex; flex-wrap: wrap; align-items: center; gap: 1rem 1.25rem; border-block: 1px solid var(--rule); padding-block: 1rem; margin-block-start: 2rem; }
2
2
  .folio-metrics-section { padding-block: clamp(3rem, 6vw, 5rem); }
3
- .folio-metrics-section__header { display: flex; flex-wrap: wrap; align-items: end; justify-content: space-between; gap: 1.5rem; padding-block-end: 1rem; margin-block-end: 1.5rem; border-block-end: 1px solid var(--rule-hard); }
3
+ .folio-metrics-section__header { display: flex; flex-wrap: wrap; align-items: end; justify-content: space-between; gap: 1.5rem; padding-block-end: 1rem; margin-block-end: 1.5rem; border-block-end: 1px solid var(--rule-divider); }
4
4
  .folio-metrics-panel { min-inline-size: 0; }
5
5
  .folio-metrics-panel > header { margin-block-end: 1.25rem; }
6
6
  .folio-metrics-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 3rem; }
@@ -20,7 +20,7 @@
20
20
  .folio-media-collection__copy h3 a { font: inherit; white-space: normal; text-align: start; }
21
21
  .folio-media-collection__metadata { display: flex; flex-wrap: wrap; justify-content: space-between; gap: .5rem; }
22
22
  .folio-music-section { min-inline-size: 0; }
23
- .folio-music-section__header { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: .75rem; padding-block-end: 1rem; border-block-end: 1px solid var(--rule-hard); }
23
+ .folio-music-section__header { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: .75rem; padding-block-end: 1rem; border-block-end: 1px solid var(--rule-divider); }
24
24
  .folio-music-section__collections { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0 clamp(2rem, 5vw, 5rem); }
25
25
  @media (min-width: 640px) { .folio-media-row { gap: 1rem; }.folio-media-collection { grid-template-columns: 9rem minmax(0, 1fr); }.folio-media-collection__copy { padding-inline: 1.5rem; } }
26
26
  @media (min-width: 768px) { .folio-media-feature { grid-template-columns: minmax(15rem, 5fr) minmax(0, 7fr); }.folio-music-section__collections { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@@ -19,7 +19,7 @@
19
19
  .folio-reading-body{min-width:0}
20
20
  .folio-reading-section{padding-block-start:clamp(3rem,7vw,5.5rem);scroll-margin-block-start:var(--scroll-offset,6rem)}
21
21
  .folio-reading-section[data-compact]{padding-block-start:2rem}
22
- .folio-reading-heading{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:1rem;border-block-end:1px solid var(--rule-hard);padding-block-end:1rem;margin-block-end:1.75rem}
22
+ .folio-reading-heading{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:1rem;border-block-end:1px solid var(--rule-divider);padding-block-end:1rem;margin-block-end:1.75rem}
23
23
  .folio-reading-section[data-seamless] .folio-reading-heading{margin-block-end:0}
24
24
  .folio-reading-heading:has(+ .folio-reference-rows, + .folio-feature-pairs){margin-block-end:0}
25
25
  .folio-reading-heading + :is(.folio-reference-rows,.folio-feature-pairs){border-block-start:0}
@@ -47,7 +47,10 @@
47
47
  .folio-toc a[aria-current=true]{font-weight:500;color:var(--ink)}
48
48
  .folio-toc a:hover{color:var(--ink)}
49
49
  .folio-toc a>span{font-family:var(--mono-i18n,var(--font-mono));font-size:.625rem}
50
- .folio-outline-trigger{position:fixed;inset-inline-start:1.5rem;inset-block-end:1.5rem;z-index:var(--z-sticky,30)}
50
+ /* The outline trigger stacks one step above the conversation launcher, which
51
+ owns the same corner (1.5rem in, 3rem tall). Sharing the spot put one
52
+ control on top of the other on every narrow article page. */
53
+ .folio-outline-trigger{position:fixed;inset-inline-start:1.5rem;inset-block-end:calc(1.5rem + 3rem + .75rem);z-index:var(--z-sticky,30)}
51
54
  .folio-outline-sheet .folio-toc a{min-height:44px}
52
55
  .folio-reading-progress{position:fixed;inset-block-start:0;inset-inline:0;z-index:var(--z-sticky,30);height:2px;pointer-events:none}
53
56
  .folio-reading-progress>div{height:100%;background:var(--accent)}
@@ -67,12 +70,12 @@
67
70
  .folio-media-playback{position:relative;width:100%;height:100%}
68
71
  .folio-media-playback video{display:block;width:100%;height:100%;object-fit:cover}
69
72
  .folio-media-playback button{position:absolute;inset-inline-end:.75rem;inset-block-end:.75rem;background:var(--paper)}
70
- .folio-action-band{display:grid;grid-template-columns:minmax(0,1fr);gap:1.5rem;border-block:1px solid var(--rule-hard);padding-block:clamp(2.5rem,6vw,4.5rem);margin-block-start:clamp(3.5rem,8vw,6rem)}
73
+ .folio-action-band{display:grid;grid-template-columns:minmax(0,1fr);gap:1.5rem;border-block:1px solid var(--rule-divider);padding-block:clamp(2.5rem,6vw,4.5rem);margin-block-start:clamp(3.5rem,8vw,6rem)}
71
74
  .folio-action-band__eyebrow{font-family:var(--mono-i18n,var(--label-font));font-size:.6875rem;letter-spacing:calc(.1em * var(--label-tracking-scale));color:var(--ink-3-aa)}
72
75
  .folio-action-band h2{font-family:var(--serif-i18n,var(--font-heading));font-size:var(--fs-lead);font-weight:var(--display-weight);line-height:1.15;margin:0;text-wrap:balance}
73
76
  .folio-action-band p{max-width:46ch;color:var(--ink-3-aa);line-height:1.7;margin:1rem 0 0}
74
77
  .folio-action-band__actions{display:flex;flex-wrap:wrap;align-items:center;gap:.75rem}
75
- .folio-feature-pairs{display:grid;grid-template-columns:1fr 1fr;gap:0 clamp(2rem,5vw,4.5rem);border-block-start:1px solid var(--rule-hard)}
78
+ .folio-feature-pairs{display:grid;grid-template-columns:1fr 1fr;gap:0 clamp(2rem,5vw,4.5rem);border-block-start:1px solid var(--rule-divider)}
76
79
  .folio-reading-section[data-seamless] .folio-feature-pairs{border-block-start:0}
77
80
  .folio-feature-pairs>div{padding-block:clamp(1.5rem,3vw,2rem);border-block-end:1px solid var(--rule)}
78
81
  .folio-feature-pairs span{font-family:var(--mono-i18n,var(--font-mono));font-size:.6875rem;color:var(--ink-3-aa)}
@@ -86,9 +89,17 @@
86
89
  .folio-browser-bar>span:last-child{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
87
90
  .folio-device-screen{position:relative;aspect-ratio:16/10;overflow:hidden}
88
91
  .folio-device-screen img{width:100%;height:100%;object-fit:cover}
89
- .folio-device-preview[data-kind=phone]{max-width:16.625rem}
90
- .folio-device-preview[data-kind=phone]>div{border-width:.5rem;border-radius:calc(var(--radius) * 1.5)}
91
- .folio-device-preview[data-kind=phone] .folio-device-screen{aspect-ratio:9/19}
92
+ /* The phone is an iPhone as the Simulator draws it: a dark bezel with a
93
+ hairline rim, a screen whose corner follows the glass, and the Dynamic Island
94
+ over the status bar. It used to be the browser frame with thicker borders —
95
+ a rounded rectangle that read as a box around a screenshot, not a device. */
96
+ .folio-device-preview[data-kind=phone]{max-width:16.5rem;justify-self:center}
97
+ .folio-device-preview[data-kind=phone]>div{position:relative;padding:.5rem;border:0;border-radius:2.75rem;background:var(--device-bezel);box-shadow:0 0 0 1px var(--device-rim),0 1.5rem 3rem color-mix(in srgb,var(--device-island) 22%,transparent)}
98
+ .folio-device-preview[data-kind=phone]>div::before{content:'';position:absolute;z-index:1;inset-block-start:1.125rem;inset-inline-start:50%;inline-size:31%;block-size:1.6rem;translate:-50% 0;border-radius:var(--radius-pill);background:var(--device-island)}
99
+ .folio-device-preview[data-kind=phone] .folio-device-screen{aspect-ratio:393/852;overflow:hidden;border-radius:2.25rem;background:var(--device-island)}
100
+ .folio-device-preview[data-kind=phone] figcaption{text-align:center}
101
+ .folio-device-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,12.5rem),1fr));gap:clamp(1.5rem,4vw,3rem);align-items:end;padding-block:1.75rem .5rem}
102
+ @media(min-width:48rem){.folio-device-row>:nth-child(3n+2){margin-block-end:2.5rem}}
92
103
  .folio-device-preview figcaption{font-family:var(--mono-i18n,var(--font-mono));font-size:.6875rem;color:var(--ink-3-aa);margin-block-start:.75rem}
93
104
  .folio-command-tabs{min-width:0}
94
105
  .folio-command-tabs [role=tablist]{flex-wrap:wrap}