@burdenoff/microfe-theculturepost 2026.704.2 → 2026.705.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/theculturepost/TheCulturePostRoot.js +63 -45
- package/dist/theculturepost/TheCulturePostRoot.js.map +1 -1
- package/dist/theculturepost/TheCulturePostRoutes.js +17 -9
- package/dist/theculturepost/TheCulturePostRoutes.js.map +1 -1
- package/dist/theculturepost/components/AddCitationDialog.js +131 -102
- package/dist/theculturepost/components/AddCitationDialog.js.map +1 -1
- package/dist/theculturepost/components/ArchiveItemCard.js +56 -43
- package/dist/theculturepost/components/ArchiveItemCard.js.map +1 -1
- package/dist/theculturepost/components/AudioPlayer.js +74 -0
- package/dist/theculturepost/components/AudioPlayer.js.map +1 -0
- package/dist/theculturepost/components/CitationList.js +215 -105
- package/dist/theculturepost/components/CitationList.js.map +1 -1
- package/dist/theculturepost/components/CollectionCard.js +32 -32
- package/dist/theculturepost/components/CollectionCard.js.map +1 -1
- package/dist/theculturepost/components/CreateArchiveItemDialog.js +133 -93
- package/dist/theculturepost/components/CreateArchiveItemDialog.js.map +1 -1
- package/dist/theculturepost/components/CreateCollectionDialog.js +91 -70
- package/dist/theculturepost/components/CreateCollectionDialog.js.map +1 -1
- package/dist/theculturepost/components/CreateStoryDialog.js +311 -132
- package/dist/theculturepost/components/CreateStoryDialog.js.map +1 -1
- package/dist/theculturepost/components/DecorativeCover.js +78 -0
- package/dist/theculturepost/components/DecorativeCover.js.map +1 -0
- package/dist/theculturepost/components/DecorativeDivider.js +45 -0
- package/dist/theculturepost/components/DecorativeDivider.js.map +1 -0
- package/dist/theculturepost/components/DiscussionCard.js +75 -60
- package/dist/theculturepost/components/DiscussionCard.js.map +1 -1
- package/dist/theculturepost/components/HistoryTimeline.js +115 -0
- package/dist/theculturepost/components/HistoryTimeline.js.map +1 -0
- package/dist/theculturepost/components/LifecycleStepper.js +33 -0
- package/dist/theculturepost/components/LifecycleStepper.js.map +1 -0
- package/dist/theculturepost/components/OpenDiscussionDialog.js +66 -56
- package/dist/theculturepost/components/OpenDiscussionDialog.js.map +1 -1
- package/dist/theculturepost/components/OralHistoryCard.js +6 -73
- package/dist/theculturepost/components/OverlayBadge.js +22 -0
- package/dist/theculturepost/components/OverlayBadge.js.map +1 -0
- package/dist/theculturepost/components/PageHero.js +62 -0
- package/dist/theculturepost/components/PageHero.js.map +1 -0
- package/dist/theculturepost/components/RecordOralHistoryDialog.js +306 -131
- package/dist/theculturepost/components/RecordOralHistoryDialog.js.map +1 -1
- package/dist/theculturepost/components/ReplyThread.js +268 -139
- package/dist/theculturepost/components/ReplyThread.js.map +1 -1
- package/dist/theculturepost/components/StatusBadge.js +9 -9
- package/dist/theculturepost/components/StatusBadge.js.map +1 -1
- package/dist/theculturepost/components/StoryCard.js +54 -49
- package/dist/theculturepost/components/StoryCard.js.map +1 -1
- package/dist/theculturepost/components/TopicCard.js +60 -41
- package/dist/theculturepost/components/TopicCard.js.map +1 -1
- package/dist/theculturepost/components/Waveform.js +31 -0
- package/dist/theculturepost/components/Waveform.js.map +1 -0
- package/dist/theculturepost/components/categoryMeta.js +20 -0
- package/dist/theculturepost/components/categoryMeta.js.map +1 -0
- package/dist/theculturepost/components/index.js +41 -0
- package/dist/theculturepost/components/viz/BarChart.js +53 -0
- package/dist/theculturepost/components/viz/BarChart.js.map +1 -0
- package/dist/theculturepost/components/viz/Donut.js +58 -0
- package/dist/theculturepost/components/viz/Donut.js.map +1 -0
- package/dist/theculturepost/components/viz/StatTile.js +35 -0
- package/dist/theculturepost/components/viz/StatTile.js.map +1 -0
- package/dist/theculturepost/components/viz/index.js +3 -0
- package/dist/theculturepost/hooks/useFiles.js +1 -1
- package/dist/theculturepost/hooks/useFiles.js.map +1 -1
- package/dist/theculturepost/pages/HomePage.js +247 -124
- package/dist/theculturepost/pages/HomePage.js.map +1 -1
- package/dist/theculturepost/pages/archive/ArchiveItemDetailPage.js +319 -127
- package/dist/theculturepost/pages/archive/ArchiveItemDetailPage.js.map +1 -1
- package/dist/theculturepost/pages/archive/ArchivePage.js +265 -180
- package/dist/theculturepost/pages/archive/ArchivePage.js.map +1 -1
- package/dist/theculturepost/pages/archive/CollectionDetailPage.js +158 -143
- package/dist/theculturepost/pages/archive/CollectionDetailPage.js.map +1 -1
- package/dist/theculturepost/pages/discussions/DiscussionDetailPage.js +155 -93
- package/dist/theculturepost/pages/discussions/DiscussionDetailPage.js.map +1 -1
- package/dist/theculturepost/pages/discussions/DiscussionsPage.js +16 -16
- package/dist/theculturepost/pages/discussions/DiscussionsPage.js.map +1 -1
- package/dist/theculturepost/pages/governance/GovernancePage.js +293 -0
- package/dist/theculturepost/pages/governance/GovernancePage.js.map +1 -0
- package/dist/theculturepost/pages/stories/StoriesPage.js +18 -18
- package/dist/theculturepost/pages/stories/StoriesPage.js.map +1 -1
- package/dist/theculturepost/pages/stories/StoryDetailPage.js +603 -285
- package/dist/theculturepost/pages/stories/StoryDetailPage.js.map +1 -1
- package/dist/theculturepost/pages/timeline/TimelinePage.js +162 -0
- package/dist/theculturepost/pages/timeline/TimelinePage.js.map +1 -0
- package/dist/theculturepost/pages/topics/TopicDetailPage.js +239 -163
- package/dist/theculturepost/pages/topics/TopicDetailPage.js.map +1 -1
- package/dist/theculturepost/pages/topics/TopicsPage.js +76 -74
- package/dist/theculturepost/pages/topics/TopicsPage.js.map +1 -1
- package/dist/theculturepost/styles/injectDisplayFont.js +11 -0
- package/dist/theculturepost/styles/injectDisplayFont.js.map +1 -0
- package/dist/theculturepost/utils/formatters.js +6 -1
- package/dist/theculturepost/utils/formatters.js.map +1 -1
- package/package.json +1 -1
- package/dist/theculturepost/components/OralHistoryCard.js.map +0 -1
|
@@ -1,81 +1,86 @@
|
|
|
1
1
|
import { CoverImage as e } from "./CoverImage.js";
|
|
2
2
|
import { formatEnumLabel as t, formatNumber as n, formatRelativeTime as r } from "../utils/formatters.js";
|
|
3
3
|
import { StatusBadge as i } from "./StatusBadge.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
4
|
+
import { OverlayBadge as a } from "./OverlayBadge.js";
|
|
5
|
+
import { AuthorLabel as o } from "./AuthorLabel.js";
|
|
6
|
+
import { categoryIcon as s } from "./categoryMeta.js";
|
|
7
|
+
import { createElement as c } from "react";
|
|
8
|
+
import { BookOpen as l, Clock as u, Languages as d } from "lucide-react";
|
|
9
|
+
import { jsx as f, jsxs as p } from "react/jsx-runtime";
|
|
10
|
+
import { GlassCard as m } from "@burdenoff/fe-libs/ui";
|
|
8
11
|
//#region src/theculturepost/components/StoryCard.tsx
|
|
9
|
-
function
|
|
10
|
-
let
|
|
11
|
-
return /* @__PURE__ */
|
|
12
|
+
function h({ story: h, onOpen: g, hideStatus: _ = !1 }) {
|
|
13
|
+
let v = h.publishedAt ?? h.updatedAt, y = h.topic?.category ?? null;
|
|
14
|
+
return /* @__PURE__ */ p(m, {
|
|
12
15
|
treatment: "plain",
|
|
13
16
|
className: "flex flex-col overflow-hidden",
|
|
14
|
-
children: [/* @__PURE__ */
|
|
17
|
+
children: [/* @__PURE__ */ p("button", {
|
|
15
18
|
type: "button",
|
|
16
|
-
onClick:
|
|
19
|
+
onClick: g,
|
|
17
20
|
className: "group relative block aspect-[16/9] w-full overflow-hidden text-left",
|
|
18
|
-
"aria-label": `Open ${
|
|
19
|
-
children:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
"aria-label": `Open ${h.title}`,
|
|
22
|
+
children: [
|
|
23
|
+
/* @__PURE__ */ f(e, {
|
|
24
|
+
fileId: h.cover?.id,
|
|
25
|
+
alt: h.title,
|
|
26
|
+
rounded: "rounded-none",
|
|
27
|
+
className: "h-full w-full transition-transform duration-500 group-hover:scale-[1.03]",
|
|
28
|
+
icon: /* @__PURE__ */ f(l, { className: "h-8 w-8" })
|
|
29
|
+
}),
|
|
30
|
+
/* @__PURE__ */ f(a, {
|
|
31
|
+
position: "top-left",
|
|
32
|
+
children: t(h.format)
|
|
33
|
+
}),
|
|
34
|
+
!_ && /* @__PURE__ */ f(a, {
|
|
35
|
+
position: "top-right",
|
|
36
|
+
children: t(h.status)
|
|
37
|
+
})
|
|
38
|
+
]
|
|
39
|
+
}), /* @__PURE__ */ p("div", {
|
|
27
40
|
className: "flex flex-1 flex-col p-5",
|
|
28
41
|
children: [
|
|
29
|
-
/* @__PURE__ */
|
|
30
|
-
className: "mb-2
|
|
31
|
-
children:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
className: "inline-flex items-center gap-1 rounded-full bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary",
|
|
36
|
-
children: [
|
|
37
|
-
/* @__PURE__ */ u(l, { className: "h-3 w-3" }),
|
|
38
|
-
" ",
|
|
39
|
-
p.topic.name
|
|
40
|
-
]
|
|
41
|
-
})
|
|
42
|
-
]
|
|
42
|
+
h.topic && /* @__PURE__ */ f("div", {
|
|
43
|
+
className: "mb-2",
|
|
44
|
+
children: /* @__PURE__ */ f(i, {
|
|
45
|
+
status: y ?? "CULTURE_AND_IDENTITY",
|
|
46
|
+
icon: c(s(y), { className: "h-3 w-3" })
|
|
47
|
+
})
|
|
43
48
|
}),
|
|
44
|
-
/* @__PURE__ */
|
|
49
|
+
/* @__PURE__ */ f("button", {
|
|
45
50
|
type: "button",
|
|
46
|
-
onClick:
|
|
47
|
-
className: "text-left text-
|
|
48
|
-
children:
|
|
51
|
+
onClick: g,
|
|
52
|
+
className: "font-display text-left text-lg font-semibold leading-snug text-text-primary hover:underline",
|
|
53
|
+
children: h.title
|
|
49
54
|
}),
|
|
50
|
-
|
|
55
|
+
h.summary && /* @__PURE__ */ f("p", {
|
|
51
56
|
className: "mt-1.5 line-clamp-2 text-sm text-text-secondary",
|
|
52
|
-
children:
|
|
57
|
+
children: h.summary
|
|
53
58
|
}),
|
|
54
|
-
/* @__PURE__ */
|
|
59
|
+
/* @__PURE__ */ p("div", {
|
|
55
60
|
className: "mt-auto flex flex-wrap items-center gap-x-3 gap-y-1 pt-4 text-xs text-text-tertiary",
|
|
56
61
|
children: [
|
|
57
|
-
/* @__PURE__ */
|
|
58
|
-
actorId:
|
|
62
|
+
/* @__PURE__ */ f(o, {
|
|
63
|
+
actorId: h.authorId,
|
|
59
64
|
prefix: "By"
|
|
60
65
|
}),
|
|
61
|
-
|
|
66
|
+
h.readingMins > 0 && /* @__PURE__ */ p("span", {
|
|
62
67
|
className: "inline-flex items-center gap-1",
|
|
63
68
|
children: [
|
|
64
|
-
/* @__PURE__ */ u
|
|
69
|
+
/* @__PURE__ */ f(u, { className: "h-3.5 w-3.5" }),
|
|
65
70
|
" ",
|
|
66
|
-
n(
|
|
71
|
+
n(h.readingMins),
|
|
67
72
|
" min read"
|
|
68
73
|
]
|
|
69
74
|
}),
|
|
70
|
-
|
|
75
|
+
h.languages.length > 0 && /* @__PURE__ */ p("span", {
|
|
71
76
|
className: "inline-flex items-center gap-1",
|
|
72
77
|
children: [
|
|
73
|
-
/* @__PURE__ */
|
|
78
|
+
/* @__PURE__ */ f(d, { className: "h-3.5 w-3.5" }),
|
|
74
79
|
" ",
|
|
75
|
-
|
|
80
|
+
h.languages.map(t).join(", ")
|
|
76
81
|
]
|
|
77
82
|
}),
|
|
78
|
-
/* @__PURE__ */
|
|
83
|
+
/* @__PURE__ */ f("span", { children: r(v) })
|
|
79
84
|
]
|
|
80
85
|
})
|
|
81
86
|
]
|
|
@@ -83,6 +88,6 @@ function p({ story: p, onOpen: m, hideStatus: h = !1 }) {
|
|
|
83
88
|
});
|
|
84
89
|
}
|
|
85
90
|
//#endregion
|
|
86
|
-
export {
|
|
91
|
+
export { h as StoryCard };
|
|
87
92
|
|
|
88
93
|
//# sourceMappingURL=StoryCard.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StoryCard.js","names":[],"sources":["../../../src/theculturepost/components/StoryCard.tsx"],"sourcesContent":["/**\n * StoryCard — an editorial tile for a story.\n * @module theculturepost/components\n *\n * Cover image (via file id), title, summary,
|
|
1
|
+
{"version":3,"file":"StoryCard.js","names":[],"sources":["../../../src/theculturepost/components/StoryCard.tsx"],"sourcesContent":["/**\n * StoryCard — an editorial tile for a story.\n * @module theculturepost/components\n *\n * Cover image (via file id) with the format badge overlaid top-left and the\n * lifecycle status top-right; a colored (semantic-token) category pill, serif\n * title, summary, reading-time + language chips, author + updated time. Links to\n * the story detail page. Only real schema fields — no fabricated metrics.\n */\n\nimport { createElement } from 'react';\nimport { BookOpen, Clock, Languages } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { CoverImage } from './CoverImage';\nimport { StatusBadge } from './StatusBadge';\nimport { OverlayBadge } from './OverlayBadge';\nimport { AuthorLabel } from './AuthorLabel';\nimport { categoryIcon } from './categoryMeta';\nimport { formatEnumLabel, formatNumber, formatRelativeTime } from '../utils/formatters';\nimport type { TcpStory } from '../types';\n\ninterface StoryCardProps {\n story: TcpStory;\n onOpen: () => void;\n /** Hide the status badge (e.g. on published-only surfaces). */\n hideStatus?: boolean;\n}\n\nexport function StoryCard({ story, onOpen, hideStatus = false }: StoryCardProps) {\n const dateLabel = story.publishedAt ?? story.updatedAt;\n const category = story.topic?.category ?? null;\n\n return (\n <GlassCard treatment=\"plain\" className=\"flex flex-col overflow-hidden\">\n <button\n type=\"button\"\n onClick={onOpen}\n className=\"group relative block aspect-[16/9] w-full overflow-hidden text-left\"\n aria-label={`Open ${story.title}`}\n >\n <CoverImage\n fileId={story.cover?.id}\n alt={story.title}\n rounded=\"rounded-none\"\n className=\"h-full w-full transition-transform duration-500 group-hover:scale-[1.03]\"\n icon={<BookOpen className=\"h-8 w-8\" />}\n />\n <OverlayBadge position=\"top-left\">{formatEnumLabel(story.format)}</OverlayBadge>\n {!hideStatus && (\n <OverlayBadge position=\"top-right\">{formatEnumLabel(story.status)}</OverlayBadge>\n )}\n </button>\n\n <div className=\"flex flex-1 flex-col p-5\">\n {story.topic && (\n <div className=\"mb-2\">\n <StatusBadge\n status={category ?? 'CULTURE_AND_IDENTITY'}\n icon={createElement(categoryIcon(category), { className: 'h-3 w-3' })}\n />\n </div>\n )}\n\n <button\n type=\"button\"\n onClick={onOpen}\n className=\"font-display text-left text-lg font-semibold leading-snug text-text-primary hover:underline\"\n >\n {story.title}\n </button>\n\n {story.summary && (\n <p className=\"mt-1.5 line-clamp-2 text-sm text-text-secondary\">{story.summary}</p>\n )}\n\n <div className=\"mt-auto flex flex-wrap items-center gap-x-3 gap-y-1 pt-4 text-xs text-text-tertiary\">\n <AuthorLabel actorId={story.authorId} prefix=\"By\" />\n {story.readingMins > 0 && (\n <span className=\"inline-flex items-center gap-1\">\n <Clock className=\"h-3.5 w-3.5\" /> {formatNumber(story.readingMins)} min read\n </span>\n )}\n {story.languages.length > 0 && (\n <span className=\"inline-flex items-center gap-1\">\n <Languages className=\"h-3.5 w-3.5\" />{' '}\n {story.languages.map(formatEnumLabel).join(', ')}\n </span>\n )}\n <span>{formatRelativeTime(dateLabel)}</span>\n </div>\n </div>\n </GlassCard>\n );\n}\n"],"mappings":";;;;;;;;;;;AA4BA,SAAgB,EAAU,EAAE,UAAO,WAAQ,gBAAa,MAAyB;CAC/E,IAAM,IAAY,EAAM,eAAe,EAAM,WACvC,IAAW,EAAM,OAAO,YAAY;AAE1C,QACE,kBAAC,GAAD;EAAW,WAAU;EAAQ,WAAU;YAAvC,CACE,kBAAC,UAAD;GACE,MAAK;GACL,SAAS;GACT,WAAU;GACV,cAAY,QAAQ,EAAM;aAJ5B;IAME,kBAAC,GAAD;KACE,QAAQ,EAAM,OAAO;KACrB,KAAK,EAAM;KACX,SAAQ;KACR,WAAU;KACV,MAAM,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;KACtC,CAAA;IACF,kBAAC,GAAD;KAAc,UAAS;eAAY,EAAgB,EAAM,OAAO;KAAgB,CAAA;IAC/E,CAAC,KACA,kBAAC,GAAD;KAAc,UAAS;eAAa,EAAgB,EAAM,OAAO;KAAgB,CAAA;IAE5E;MAET,kBAAC,OAAD;GAAK,WAAU;aAAf;IACG,EAAM,SACL,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD;MACE,QAAQ,KAAY;MACpB,MAAM,EAAc,EAAa,EAAS,EAAE,EAAE,WAAW,WAAW,CAAC;MACrE,CAAA;KACE,CAAA;IAGR,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,WAAU;eAET,EAAM;KACA,CAAA;IAER,EAAM,WACL,kBAAC,KAAD;KAAG,WAAU;eAAmD,EAAM;KAAY,CAAA;IAGpF,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD;OAAa,SAAS,EAAM;OAAU,QAAO;OAAO,CAAA;MACnD,EAAM,cAAc,KACnB,kBAAC,QAAD;OAAM,WAAU;iBAAhB;QACE,kBAAC,GAAD,EAAO,WAAU,eAAgB,CAAA;;QAAE,EAAa,EAAM,YAAY;QAAC;QAC9D;;MAER,EAAM,UAAU,SAAS,KACxB,kBAAC,QAAD;OAAM,WAAU;iBAAhB;QACE,kBAAC,GAAD,EAAW,WAAU,eAAgB,CAAA;QAAC;QACrC,EAAM,UAAU,IAAI,EAAgB,CAAC,KAAK,KAAK;QAC3C;;MAET,kBAAC,QAAD,EAAA,UAAO,EAAmB,EAAU,EAAQ,CAAA;MACxC;;IACF;KACI"}
|
|
@@ -1,50 +1,69 @@
|
|
|
1
1
|
import { StatusBadge as e } from "./StatusBadge.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { OverlayBadge as t } from "./OverlayBadge.js";
|
|
3
|
+
import { categoryIcon as n } from "./categoryMeta.js";
|
|
4
|
+
import { DecorativeCover as r } from "./DecorativeCover.js";
|
|
5
|
+
import { createElement as i } from "react";
|
|
6
|
+
import { Sparkles as a } from "lucide-react";
|
|
7
|
+
import { jsx as o, jsxs as s } from "react/jsx-runtime";
|
|
8
|
+
import { GlassCard as c } from "@burdenoff/fe-libs/ui";
|
|
5
9
|
//#region src/theculturepost/components/TopicCard.tsx
|
|
6
|
-
function
|
|
7
|
-
return /* @__PURE__ */
|
|
10
|
+
function l({ topic: l, onOpen: u }) {
|
|
11
|
+
return /* @__PURE__ */ s(c, {
|
|
8
12
|
treatment: "plain",
|
|
9
|
-
className: "flex flex-col
|
|
10
|
-
children: [
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}),
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
className: "mt-auto flex items-center justify-between gap-2 pt-4",
|
|
37
|
-
children: [/* @__PURE__ */ r(e, { status: o.category }), /* @__PURE__ */ r("button", {
|
|
13
|
+
className: "flex flex-col overflow-hidden",
|
|
14
|
+
children: [/* @__PURE__ */ s("button", {
|
|
15
|
+
type: "button",
|
|
16
|
+
onClick: u,
|
|
17
|
+
className: "group relative block w-full text-left",
|
|
18
|
+
"aria-label": `Open ${l.name}`,
|
|
19
|
+
children: [/* @__PURE__ */ o(r, {
|
|
20
|
+
seed: l.id || l.slug || l.name,
|
|
21
|
+
category: l.category,
|
|
22
|
+
aspect: "aspect-[16/9]",
|
|
23
|
+
className: "w-full"
|
|
24
|
+
}), l.isFeatured && /* @__PURE__ */ o(t, {
|
|
25
|
+
position: "top-right",
|
|
26
|
+
icon: /* @__PURE__ */ o(a, { className: "h-3 w-3" }),
|
|
27
|
+
children: "Featured"
|
|
28
|
+
})]
|
|
29
|
+
}), /* @__PURE__ */ s("div", {
|
|
30
|
+
className: "flex flex-1 flex-col p-5",
|
|
31
|
+
children: [
|
|
32
|
+
/* @__PURE__ */ o("div", {
|
|
33
|
+
className: "mb-2",
|
|
34
|
+
children: /* @__PURE__ */ o(e, {
|
|
35
|
+
status: l.category,
|
|
36
|
+
icon: i(n(l.category), { className: "h-3 w-3" })
|
|
37
|
+
})
|
|
38
|
+
}),
|
|
39
|
+
/* @__PURE__ */ o("button", {
|
|
38
40
|
type: "button",
|
|
39
|
-
onClick:
|
|
40
|
-
className: "text-
|
|
41
|
-
children:
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
onClick: u,
|
|
42
|
+
className: "font-display text-left text-lg font-semibold text-text-primary hover:underline",
|
|
43
|
+
children: l.name
|
|
44
|
+
}),
|
|
45
|
+
l.tradition && /* @__PURE__ */ o("p", {
|
|
46
|
+
className: "mt-0.5 text-sm text-text-secondary",
|
|
47
|
+
children: l.tradition
|
|
48
|
+
}),
|
|
49
|
+
l.description && /* @__PURE__ */ o("p", {
|
|
50
|
+
className: "mt-2 line-clamp-3 text-sm text-text-secondary",
|
|
51
|
+
children: l.description
|
|
52
|
+
}),
|
|
53
|
+
/* @__PURE__ */ o("div", {
|
|
54
|
+
className: "mt-auto flex items-center justify-end pt-4",
|
|
55
|
+
children: /* @__PURE__ */ o("button", {
|
|
56
|
+
type: "button",
|
|
57
|
+
onClick: u,
|
|
58
|
+
className: "text-xs font-medium text-accent hover:underline",
|
|
59
|
+
children: "Explore →"
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
]
|
|
63
|
+
})]
|
|
45
64
|
});
|
|
46
65
|
}
|
|
47
66
|
//#endregion
|
|
48
|
-
export {
|
|
67
|
+
export { l as TopicCard };
|
|
49
68
|
|
|
50
69
|
//# sourceMappingURL=TopicCard.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TopicCard.js","names":[],"sources":["../../../src/theculturepost/components/TopicCard.tsx"],"sourcesContent":["/**\n * TopicCard — a discovery tile for a curated topic.\n * @module theculturepost/components\n *\n *
|
|
1
|
+
{"version":3,"file":"TopicCard.js","names":[],"sources":["../../../src/theculturepost/components/TopicCard.tsx"],"sourcesContent":["/**\n * TopicCard — a discovery tile for a curated topic.\n * @module theculturepost/components\n *\n * The schema exposes NO topic imagery or per-list story count, so the banner is\n * a token-only {@link DecorativeCover} keyed to the category motif (never a fake\n * photo), with a Featured overlay. Body shows the category pill, tradition,\n * description, and an Explore link. Links to the topic detail page.\n */\n\nimport { createElement } from 'react';\nimport { Sparkles } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { StatusBadge } from './StatusBadge';\nimport { DecorativeCover } from './DecorativeCover';\nimport { OverlayBadge } from './OverlayBadge';\nimport { categoryIcon } from './categoryMeta';\nimport type { TcpTopic } from '../types';\n\ninterface TopicCardProps {\n topic: TcpTopic;\n onOpen: () => void;\n}\n\nexport function TopicCard({ topic, onOpen }: TopicCardProps) {\n return (\n <GlassCard treatment=\"plain\" className=\"flex flex-col overflow-hidden\">\n <button\n type=\"button\"\n onClick={onOpen}\n className=\"group relative block w-full text-left\"\n aria-label={`Open ${topic.name}`}\n >\n <DecorativeCover\n seed={topic.id || topic.slug || topic.name}\n category={topic.category}\n aspect=\"aspect-[16/9]\"\n className=\"w-full\"\n />\n {topic.isFeatured && (\n <OverlayBadge position=\"top-right\" icon={<Sparkles className=\"h-3 w-3\" />}>\n Featured\n </OverlayBadge>\n )}\n </button>\n\n <div className=\"flex flex-1 flex-col p-5\">\n <div className=\"mb-2\">\n <StatusBadge\n status={topic.category}\n icon={createElement(categoryIcon(topic.category), { className: 'h-3 w-3' })}\n />\n </div>\n\n <button\n type=\"button\"\n onClick={onOpen}\n className=\"font-display text-left text-lg font-semibold text-text-primary hover:underline\"\n >\n {topic.name}\n </button>\n\n {topic.tradition && <p className=\"mt-0.5 text-sm text-text-secondary\">{topic.tradition}</p>}\n\n {topic.description && (\n <p className=\"mt-2 line-clamp-3 text-sm text-text-secondary\">{topic.description}</p>\n )}\n\n <div className=\"mt-auto flex items-center justify-end pt-4\">\n <button\n type=\"button\"\n onClick={onOpen}\n className=\"text-xs font-medium text-accent hover:underline\"\n >\n Explore →\n </button>\n </div>\n </div>\n </GlassCard>\n );\n}\n"],"mappings":";;;;;;;;;AAwBA,SAAgB,EAAU,EAAE,UAAO,aAA0B;AAC3D,QACE,kBAAC,GAAD;EAAW,WAAU;EAAQ,WAAU;YAAvC,CACE,kBAAC,UAAD;GACE,MAAK;GACL,SAAS;GACT,WAAU;GACV,cAAY,QAAQ,EAAM;aAJ5B,CAME,kBAAC,GAAD;IACE,MAAM,EAAM,MAAM,EAAM,QAAQ,EAAM;IACtC,UAAU,EAAM;IAChB,QAAO;IACP,WAAU;IACV,CAAA,EACD,EAAM,cACL,kBAAC,GAAD;IAAc,UAAS;IAAY,MAAM,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;cAAE;IAE5D,CAAA,CAEV;MAET,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD;MACE,QAAQ,EAAM;MACd,MAAM,EAAc,EAAa,EAAM,SAAS,EAAE,EAAE,WAAW,WAAW,CAAC;MAC3E,CAAA;KACE,CAAA;IAEN,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,WAAU;eAET,EAAM;KACA,CAAA;IAER,EAAM,aAAa,kBAAC,KAAD;KAAG,WAAU;eAAsC,EAAM;KAAc,CAAA;IAE1F,EAAM,eACL,kBAAC,KAAD;KAAG,WAAU;eAAiD,EAAM;KAAgB,CAAA;IAGtF,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,UAAD;MACE,MAAK;MACL,SAAS;MACT,WAAU;gBACX;MAEQ,CAAA;KACL,CAAA;IACF;KACI"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { cn as e } from "../utils/cn.js";
|
|
2
|
+
import { useMemo as t } from "react";
|
|
3
|
+
import { jsx as n } from "react/jsx-runtime";
|
|
4
|
+
//#region src/theculturepost/components/Waveform.tsx
|
|
5
|
+
function r(e, t) {
|
|
6
|
+
let n = 2166136261;
|
|
7
|
+
for (let t = 0; t < e.length; t++) n ^= e.charCodeAt(t), n = Math.imul(n, 16777619);
|
|
8
|
+
let r = [];
|
|
9
|
+
for (let e = 0; e < t; e++) {
|
|
10
|
+
n ^= n << 13, n ^= n >>> 17, n ^= n << 5;
|
|
11
|
+
let i = (n >>> 0) % 1e3 / 1e3, a = 1 - Math.abs(e / (t - 1) - .5) * 1.4;
|
|
12
|
+
r.push(Math.max(.18, i * .75 + a * .25));
|
|
13
|
+
}
|
|
14
|
+
return r;
|
|
15
|
+
}
|
|
16
|
+
function i({ seed: i, bars: a = 48, progress: o = 0, className: s }) {
|
|
17
|
+
let c = t(() => r(i, a), [i, a]), l = Math.round(o * a);
|
|
18
|
+
return /* @__PURE__ */ n("div", {
|
|
19
|
+
className: e("flex h-8 w-full items-center gap-[2px]", s),
|
|
20
|
+
role: "img",
|
|
21
|
+
"aria-label": "Audio waveform",
|
|
22
|
+
children: c.map((t, r) => /* @__PURE__ */ n("span", {
|
|
23
|
+
className: e("w-full rounded-full transition-colors", r < l ? "bg-accent" : "bg-[var(--color-accent-soft)]"),
|
|
24
|
+
style: { height: `${Math.round(t * 100)}%` }
|
|
25
|
+
}, r))
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { i as Waveform };
|
|
30
|
+
|
|
31
|
+
//# sourceMappingURL=Waveform.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Waveform.js","names":[],"sources":["../../../src/theculturepost/components/Waveform.tsx"],"sourcesContent":["/**\n * Waveform — a decorative, deterministic audio waveform rendered as tokenized SVG.\n * @module theculturepost/components\n *\n * fe-libs ships no audio-visualisation primitive, so this is a self-contained,\n * theme-token-only bar waveform used to give oral-history recordings a warm,\n * \"listenable\" feel. The bar heights are derived deterministically from a seed\n * string (the recording id/title) so a given recording always renders the same\n * shape — no randomness (which would also break SSR/hydration), no real audio\n * decode. A `progress` (0–1) fills bars up to the playhead in the accent colour.\n */\n\nimport { useMemo } from 'react';\nimport { cn } from '../utils/cn';\n\ninterface WaveformProps {\n /** Stable seed so the same recording always draws the same shape. */\n seed: string;\n /** Number of bars to draw. */\n bars?: number;\n /** 0–1 playback progress; bars before it render \"played\". */\n progress?: number;\n className?: string;\n}\n\n/** Cheap deterministic hash → 0..1 sequence from a seed string. */\nfunction heights(seed: string, bars: number): number[] {\n let h = 2166136261;\n for (let i = 0; i < seed.length; i++) {\n h ^= seed.charCodeAt(i);\n h = Math.imul(h, 16777619);\n }\n const out: number[] = [];\n for (let i = 0; i < bars; i++) {\n // xorshift step per bar\n h ^= h << 13;\n h ^= h >>> 17;\n h ^= h << 5;\n const unit = ((h >>> 0) % 1000) / 1000; // 0..1\n // shape: fuller in the middle, tapered at the ends, min floor 0.18\n const centre = 1 - Math.abs(i / (bars - 1) - 0.5) * 1.4;\n out.push(Math.max(0.18, unit * 0.75 + centre * 0.25));\n }\n return out;\n}\n\nexport function Waveform({ seed, bars = 48, progress = 0, className }: WaveformProps) {\n const values = useMemo(() => heights(seed, bars), [seed, bars]);\n const playedUpto = Math.round(progress * bars);\n\n return (\n <div\n className={cn('flex h-8 w-full items-center gap-[2px]', className)}\n role=\"img\"\n aria-label=\"Audio waveform\"\n >\n {values.map((v, i) => (\n <span\n key={i}\n className={cn(\n 'w-full rounded-full transition-colors',\n i < playedUpto ? 'bg-accent' : 'bg-[var(--color-accent-soft)]'\n )}\n style={{ height: `${Math.round(v * 100)}%` }}\n />\n ))}\n </div>\n );\n}\n"],"mappings":";;;;AA0BA,SAAS,EAAQ,GAAc,GAAwB;CACrD,IAAI,IAAI;AACR,MAAK,IAAI,IAAI,GAAG,IAAI,EAAK,QAAQ,IAE/B,CADA,KAAK,EAAK,WAAW,EAAE,EACvB,IAAI,KAAK,KAAK,GAAG,SAAS;CAE5B,IAAM,IAAgB,EAAE;AACxB,MAAK,IAAI,IAAI,GAAG,IAAI,GAAM,KAAK;AAI7B,EAFA,KAAK,KAAK,IACV,KAAK,MAAM,IACX,KAAK,KAAK;EACV,IAAM,KAAS,MAAM,KAAK,MAAQ,KAE5B,IAAS,IAAI,KAAK,IAAI,KAAK,IAAO,KAAK,GAAI,GAAG;AACpD,IAAI,KAAK,KAAK,IAAI,KAAM,IAAO,MAAO,IAAS,IAAK,CAAC;;AAEvD,QAAO;;AAGT,SAAgB,EAAS,EAAE,SAAM,UAAO,IAAI,cAAW,GAAG,gBAA4B;CACpF,IAAM,IAAS,QAAc,EAAQ,GAAM,EAAK,EAAE,CAAC,GAAM,EAAK,CAAC,EACzD,IAAa,KAAK,MAAM,IAAW,EAAK;AAE9C,QACE,kBAAC,OAAD;EACE,WAAW,EAAG,0CAA0C,EAAU;EAClE,MAAK;EACL,cAAW;YAEV,EAAO,KAAK,GAAG,MACd,kBAAC,QAAD;GAEE,WAAW,EACT,yCACA,IAAI,IAAa,cAAc,gCAChC;GACD,OAAO,EAAE,QAAQ,GAAG,KAAK,MAAM,IAAI,IAAI,CAAC,IAAI;GAC5C,EANK,EAML,CACF;EACE,CAAA"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Church as e, Compass as t, Hourglass as n, Landmark as r, Lightbulb as i, Mic as a, Route as o, Scale as s, Sparkles as c, Users as l } from "lucide-react";
|
|
2
|
+
//#region src/theculturepost/components/categoryMeta.tsx
|
|
3
|
+
var u = {
|
|
4
|
+
CULTURE_AND_IDENTITY: l,
|
|
5
|
+
FESTIVALS_AND_RITUALS: c,
|
|
6
|
+
HERITAGE_AND_ARCHIVES: r,
|
|
7
|
+
HISTORY: n,
|
|
8
|
+
MIGRATION_AND_DIASPORA: o,
|
|
9
|
+
ORAL_HISTORIES: a,
|
|
10
|
+
PHILOSOPHY_AND_THOUGHT: i,
|
|
11
|
+
RELIGION_AND_FAITH: e,
|
|
12
|
+
VALUES_AND_ETHICS: s
|
|
13
|
+
};
|
|
14
|
+
function d(e) {
|
|
15
|
+
return e && u[e] || t;
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
export { u as CATEGORY_ICON, d as categoryIcon };
|
|
19
|
+
|
|
20
|
+
//# sourceMappingURL=categoryMeta.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"categoryMeta.js","names":[],"sources":["../../../src/theculturepost/components/categoryMeta.tsx"],"sourcesContent":["/**\n * categoryMeta — map a topic category to a lucide motif icon + a semantic accent.\n * @module theculturepost/components\n *\n * Icons distinguish categories by MOTIF (per the design system's \"vary by icon,\n * not by raw hue\" rule). The accent is a semantic-token variant name only — never\n * a raw colour — so decorative surfaces (covers, badges) stay theme-safe.\n */\n\nimport {\n Users,\n Sparkles,\n Landmark,\n Hourglass,\n Route,\n Mic,\n Lightbulb,\n Church,\n Scale,\n Compass,\n type LucideIcon,\n} from 'lucide-react';\nimport type { TcpTopicCategory } from '../types';\n\n/** Semantic badge/accent variant names (map to tokens in StatusBadge). */\nexport type CategoryAccent = 'info' | 'success' | 'warning' | 'secondary';\n\nexport const CATEGORY_ICON: Record<TcpTopicCategory, LucideIcon> = {\n CULTURE_AND_IDENTITY: Users,\n FESTIVALS_AND_RITUALS: Sparkles,\n HERITAGE_AND_ARCHIVES: Landmark,\n HISTORY: Hourglass,\n MIGRATION_AND_DIASPORA: Route,\n ORAL_HISTORIES: Mic,\n PHILOSOPHY_AND_THOUGHT: Lightbulb,\n RELIGION_AND_FAITH: Church,\n VALUES_AND_ETHICS: Scale,\n};\n\n/**\n * Per-category semantic accent. Distinct across the palette so categories read\n * apart, but every value is a semantic-token variant (never a raw hue).\n */\nexport const CATEGORY_ACCENT: Record<TcpTopicCategory, CategoryAccent> = {\n CULTURE_AND_IDENTITY: 'info',\n FESTIVALS_AND_RITUALS: 'warning',\n HERITAGE_AND_ARCHIVES: 'secondary',\n HISTORY: 'warning',\n MIGRATION_AND_DIASPORA: 'info',\n ORAL_HISTORIES: 'success',\n PHILOSOPHY_AND_THOUGHT: 'info',\n RELIGION_AND_FAITH: 'warning',\n VALUES_AND_ETHICS: 'success',\n};\n\n/** Resolve a category's motif icon, falling back to a neutral Compass. */\nexport function categoryIcon(category?: TcpTopicCategory | null): LucideIcon {\n return (category && CATEGORY_ICON[category]) || Compass;\n}\n"],"mappings":";;AA2BA,IAAa,IAAsD;CACjE,sBAAsB;CACtB,uBAAuB;CACvB,uBAAuB;CACvB,SAAS;CACT,wBAAwB;CACxB,gBAAgB;CAChB,wBAAwB;CACxB,oBAAoB;CACpB,mBAAmB;CACpB;AAmBD,SAAgB,EAAa,GAAgD;AAC3E,QAAQ,KAAY,EAAc,MAAc"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import "./FileImage.js";
|
|
2
|
+
import "./CoverImage.js";
|
|
3
|
+
import "./PageHero.js";
|
|
4
|
+
import "./StateViews.js";
|
|
5
|
+
import "./StatusBadge.js";
|
|
6
|
+
import "./OverlayBadge.js";
|
|
7
|
+
import "./AuthorLabel.js";
|
|
8
|
+
import "./categoryMeta.js";
|
|
9
|
+
import "./StoryCard.js";
|
|
10
|
+
import "./DecorativeCover.js";
|
|
11
|
+
import "./TopicCard.js";
|
|
12
|
+
import "./EmptyState.js";
|
|
13
|
+
import "./DecorativeDivider.js";
|
|
14
|
+
import "./kindIcon.js";
|
|
15
|
+
import "./Tabs.js";
|
|
16
|
+
import "./Waveform.js";
|
|
17
|
+
import "./AudioPlayer.js";
|
|
18
|
+
import "./CollectionCard.js";
|
|
19
|
+
import "./ArchiveItemCard.js";
|
|
20
|
+
import "./RecordOralHistoryDialog.js";
|
|
21
|
+
import "./CreateCollectionDialog.js";
|
|
22
|
+
import "./TagInput.js";
|
|
23
|
+
import "./CreateArchiveItemDialog.js";
|
|
24
|
+
import "./Pagination.js";
|
|
25
|
+
import "./PageLayout.js";
|
|
26
|
+
import "./ReplyThread.js";
|
|
27
|
+
import "./FilterTabs.js";
|
|
28
|
+
import "./DiscussionCard.js";
|
|
29
|
+
import "./OpenDiscussionDialog.js";
|
|
30
|
+
import "./HistoryTimeline.js";
|
|
31
|
+
import "./LifecycleStepper.js";
|
|
32
|
+
import "./viz/StatTile.js";
|
|
33
|
+
import "./viz/BarChart.js";
|
|
34
|
+
import "./viz/Donut.js";
|
|
35
|
+
import "./viz/index.js";
|
|
36
|
+
import "./FileUploadField.js";
|
|
37
|
+
import "./OralHistoryCard.js";
|
|
38
|
+
import "./CitationList.js";
|
|
39
|
+
import "./CreateStoryDialog.js";
|
|
40
|
+
import "./CreateTopicDialog.js";
|
|
41
|
+
import "./AddCitationDialog.js";
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { cn as e } from "../../utils/cn.js";
|
|
2
|
+
import { useId as t } from "react";
|
|
3
|
+
import { jsx as n, jsxs as r } from "react/jsx-runtime";
|
|
4
|
+
//#region src/theculturepost/components/viz/BarChart.tsx
|
|
5
|
+
function i({ data: i, ariaLabel: a, height: o = 160, className: s }) {
|
|
6
|
+
let c = t(), l = Math.max(1, ...i.map((e) => e.value));
|
|
7
|
+
return i.length === 0 ? /* @__PURE__ */ n("p", {
|
|
8
|
+
className: e("text-sm text-text-tertiary", s),
|
|
9
|
+
children: "No data to display."
|
|
10
|
+
}) : /* @__PURE__ */ r("figure", {
|
|
11
|
+
className: e("w-full", s),
|
|
12
|
+
role: "group",
|
|
13
|
+
"aria-labelledby": c,
|
|
14
|
+
children: [/* @__PURE__ */ n("figcaption", {
|
|
15
|
+
id: c,
|
|
16
|
+
className: "sr-only",
|
|
17
|
+
children: a
|
|
18
|
+
}), /* @__PURE__ */ r("div", {
|
|
19
|
+
className: "overflow-x-auto",
|
|
20
|
+
children: [/* @__PURE__ */ n("div", {
|
|
21
|
+
className: "flex min-w-full items-end gap-3",
|
|
22
|
+
style: { height: o },
|
|
23
|
+
role: "list",
|
|
24
|
+
children: i.map((e) => {
|
|
25
|
+
let t = Math.round(e.value / l * 100);
|
|
26
|
+
return /* @__PURE__ */ r("div", {
|
|
27
|
+
role: "listitem",
|
|
28
|
+
"aria-label": `${e.label}: ${e.value}`,
|
|
29
|
+
className: "flex min-w-[2.5rem] flex-1 flex-col items-center justify-end gap-1",
|
|
30
|
+
children: [/* @__PURE__ */ n("span", {
|
|
31
|
+
className: "text-xs font-semibold tabular-nums text-text-secondary",
|
|
32
|
+
children: e.value
|
|
33
|
+
}), /* @__PURE__ */ n("div", {
|
|
34
|
+
className: "w-full rounded-t-md bg-accent transition-[height] duration-500",
|
|
35
|
+
style: { height: `${Math.max(2, t)}%` }
|
|
36
|
+
})]
|
|
37
|
+
}, e.label);
|
|
38
|
+
})
|
|
39
|
+
}), /* @__PURE__ */ n("div", {
|
|
40
|
+
className: "mt-2 flex min-w-full gap-3",
|
|
41
|
+
children: i.map((e) => /* @__PURE__ */ n("span", {
|
|
42
|
+
className: "min-w-[2.5rem] flex-1 truncate text-center text-xs text-text-tertiary",
|
|
43
|
+
title: e.label,
|
|
44
|
+
children: e.label
|
|
45
|
+
}, e.label))
|
|
46
|
+
})]
|
|
47
|
+
})]
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
export { i as BarChart };
|
|
52
|
+
|
|
53
|
+
//# sourceMappingURL=BarChart.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BarChart.js","names":[],"sources":["../../../../src/theculturepost/components/viz/BarChart.tsx"],"sourcesContent":["/**\n * BarChart — a token-only labelled vertical bar chart (pure SVG/flex, no deps).\n * @module theculturepost/components/viz\n *\n * fe-libs ships no chart kit. This renders real values (e.g. stories per status)\n * as accent bars with value labels + rotated-safe x labels. Responsive: the bar\n * row scrolls horizontally on narrow screens rather than overflowing the page.\n */\n\nimport { useId } from 'react';\nimport { cn } from '../../utils/cn';\n\nexport interface BarDatum {\n label: string;\n value: number;\n}\n\ninterface BarChartProps {\n data: BarDatum[];\n /** Accessible description of what the chart shows. */\n ariaLabel: string;\n /** Chart plotting height in px (bar area only). */\n height?: number;\n className?: string;\n}\n\nexport function BarChart({ data, ariaLabel, height = 160, className }: BarChartProps) {\n const captionId = useId();\n const max = Math.max(1, ...data.map((d) => d.value));\n\n if (data.length === 0) {\n return <p className={cn('text-sm text-text-tertiary', className)}>No data to display.</p>;\n }\n\n return (\n <figure className={cn('w-full', className)} role=\"group\" aria-labelledby={captionId}>\n <figcaption id={captionId} className=\"sr-only\">\n {ariaLabel}\n </figcaption>\n <div className=\"overflow-x-auto\">\n <div className=\"flex min-w-full items-end gap-3\" style={{ height }} role=\"list\">\n {data.map((d) => {\n const pct = Math.round((d.value / max) * 100);\n return (\n <div\n key={d.label}\n role=\"listitem\"\n aria-label={`${d.label}: ${d.value}`}\n className=\"flex min-w-[2.5rem] flex-1 flex-col items-center justify-end gap-1\"\n >\n <span className=\"text-xs font-semibold tabular-nums text-text-secondary\">\n {d.value}\n </span>\n <div\n className=\"w-full rounded-t-md bg-accent transition-[height] duration-500\"\n style={{ height: `${Math.max(2, pct)}%` }}\n />\n </div>\n );\n })}\n </div>\n <div className=\"mt-2 flex min-w-full gap-3\">\n {data.map((d) => (\n <span\n key={d.label}\n className=\"min-w-[2.5rem] flex-1 truncate text-center text-xs text-text-tertiary\"\n title={d.label}\n >\n {d.label}\n </span>\n ))}\n </div>\n </div>\n </figure>\n );\n}\n"],"mappings":";;;;AA0BA,SAAgB,EAAS,EAAE,SAAM,cAAW,YAAS,KAAK,gBAA4B;CACpF,IAAM,IAAY,GAAO,EACnB,IAAM,KAAK,IAAI,GAAG,GAAG,EAAK,KAAK,MAAM,EAAE,MAAM,CAAC;AAMpD,QAJI,EAAK,WAAW,IACX,kBAAC,KAAD;EAAG,WAAW,EAAG,8BAA8B,EAAU;YAAE;EAAuB,CAAA,GAIzF,kBAAC,UAAD;EAAQ,WAAW,EAAG,UAAU,EAAU;EAAE,MAAK;EAAQ,mBAAiB;YAA1E,CACE,kBAAC,cAAD;GAAY,IAAI;GAAW,WAAU;aAClC;GACU,CAAA,EACb,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;IAAkC,OAAO,EAAE,WAAQ;IAAE,MAAK;cACtE,EAAK,KAAK,MAAM;KACf,IAAM,IAAM,KAAK,MAAO,EAAE,QAAQ,IAAO,IAAI;AAC7C,YACE,kBAAC,OAAD;MAEE,MAAK;MACL,cAAY,GAAG,EAAE,MAAM,IAAI,EAAE;MAC7B,WAAU;gBAJZ,CAME,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAE;OACE,CAAA,EACP,kBAAC,OAAD;OACE,WAAU;OACV,OAAO,EAAE,QAAQ,GAAG,KAAK,IAAI,GAAG,EAAI,CAAC,IAAI;OACzC,CAAA,CACE;QAZC,EAAE,MAYH;MAER;IACE,CAAA,EACN,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAK,KAAK,MACT,kBAAC,QAAD;KAEE,WAAU;KACV,OAAO,EAAE;eAER,EAAE;KACE,EALA,EAAE,MAKF,CACP;IACE,CAAA,CACF;KACC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { cn as e } from "../../utils/cn.js";
|
|
2
|
+
import { useId as t } from "react";
|
|
3
|
+
import { jsx as n, jsxs as r } from "react/jsx-runtime";
|
|
4
|
+
//#region src/theculturepost/components/viz/Donut.tsx
|
|
5
|
+
function i({ value: i, total: a, centerLabel: o, caption: s, size: c = 120, thickness: l = 12, ariaLabel: u, className: d }) {
|
|
6
|
+
let f = t(), p = Math.max(0, Math.min(1, i / (a > 0 ? a : 1))), m = Math.round(p * 100), h = (c - l) / 2, g = 2 * Math.PI * h, _ = c / 2;
|
|
7
|
+
return /* @__PURE__ */ r("div", {
|
|
8
|
+
className: e("inline-flex flex-col items-center gap-1", d),
|
|
9
|
+
children: [/* @__PURE__ */ r("svg", {
|
|
10
|
+
width: c,
|
|
11
|
+
height: c,
|
|
12
|
+
viewBox: `0 0 ${c} ${c}`,
|
|
13
|
+
role: "img",
|
|
14
|
+
"aria-labelledby": f,
|
|
15
|
+
children: [
|
|
16
|
+
/* @__PURE__ */ n("title", {
|
|
17
|
+
id: f,
|
|
18
|
+
children: u ?? `${i} of ${a} (${m}%)`
|
|
19
|
+
}),
|
|
20
|
+
/* @__PURE__ */ n("circle", {
|
|
21
|
+
cx: _,
|
|
22
|
+
cy: _,
|
|
23
|
+
r: h,
|
|
24
|
+
fill: "none",
|
|
25
|
+
strokeWidth: l,
|
|
26
|
+
className: "stroke-bg-sunken"
|
|
27
|
+
}),
|
|
28
|
+
/* @__PURE__ */ n("circle", {
|
|
29
|
+
cx: _,
|
|
30
|
+
cy: _,
|
|
31
|
+
r: h,
|
|
32
|
+
fill: "none",
|
|
33
|
+
strokeWidth: l,
|
|
34
|
+
strokeLinecap: "round",
|
|
35
|
+
strokeDasharray: g,
|
|
36
|
+
strokeDashoffset: g * (1 - p),
|
|
37
|
+
transform: `rotate(-90 ${_} ${_})`,
|
|
38
|
+
className: "stroke-accent transition-[stroke-dashoffset] duration-700"
|
|
39
|
+
}),
|
|
40
|
+
/* @__PURE__ */ n("text", {
|
|
41
|
+
x: _,
|
|
42
|
+
y: _,
|
|
43
|
+
textAnchor: "middle",
|
|
44
|
+
dominantBaseline: "central",
|
|
45
|
+
className: "fill-text-primary font-display text-lg font-semibold",
|
|
46
|
+
children: o ?? `${m}%`
|
|
47
|
+
})
|
|
48
|
+
]
|
|
49
|
+
}), s && /* @__PURE__ */ n("span", {
|
|
50
|
+
className: "text-xs text-text-tertiary",
|
|
51
|
+
children: s
|
|
52
|
+
})]
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
export { i as Donut };
|
|
57
|
+
|
|
58
|
+
//# sourceMappingURL=Donut.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Donut.js","names":[],"sources":["../../../../src/theculturepost/components/viz/Donut.tsx"],"sourcesContent":["/**\n * Donut — a token-only single-value ring gauge with a centered label.\n * @module theculturepost/components/viz\n *\n * Draws `value / total` as an accent arc over a sunken track. Center shows the\n * value (or a caller-supplied `centerLabel`). Pure SVG, no deps. Use for\n * completion / share-of ratios computed from real counts.\n */\n\nimport { useId } from 'react';\nimport { cn } from '../../utils/cn';\n\ninterface DonutProps {\n value: number;\n total: number;\n /** Center text; defaults to a rounded percentage. */\n centerLabel?: string;\n /** Small line under the center label. */\n caption?: string;\n /** Diameter in px. */\n size?: number;\n /** Ring thickness in px. */\n thickness?: number;\n ariaLabel?: string;\n className?: string;\n}\n\nexport function Donut({\n value,\n total,\n centerLabel,\n caption,\n size = 120,\n thickness = 12,\n ariaLabel,\n className,\n}: DonutProps) {\n const labelId = useId();\n const safeTotal = total > 0 ? total : 1;\n const fraction = Math.max(0, Math.min(1, value / safeTotal));\n const pct = Math.round(fraction * 100);\n const r = (size - thickness) / 2;\n const c = 2 * Math.PI * r;\n const center = size / 2;\n\n return (\n <div className={cn('inline-flex flex-col items-center gap-1', className)}>\n <svg\n width={size}\n height={size}\n viewBox={`0 0 ${size} ${size}`}\n role=\"img\"\n aria-labelledby={labelId}\n >\n <title id={labelId}>{ariaLabel ?? `${value} of ${total} (${pct}%)`}</title>\n <circle\n cx={center}\n cy={center}\n r={r}\n fill=\"none\"\n strokeWidth={thickness}\n className=\"stroke-bg-sunken\"\n />\n <circle\n cx={center}\n cy={center}\n r={r}\n fill=\"none\"\n strokeWidth={thickness}\n strokeLinecap=\"round\"\n strokeDasharray={c}\n strokeDashoffset={c * (1 - fraction)}\n transform={`rotate(-90 ${center} ${center})`}\n className=\"stroke-accent transition-[stroke-dashoffset] duration-700\"\n />\n <text\n x={center}\n y={center}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n className=\"fill-text-primary font-display text-lg font-semibold\"\n >\n {centerLabel ?? `${pct}%`}\n </text>\n </svg>\n {caption && <span className=\"text-xs text-text-tertiary\">{caption}</span>}\n </div>\n );\n}\n"],"mappings":";;;;AA2BA,SAAgB,EAAM,EACpB,UACA,UACA,gBACA,YACA,UAAO,KACP,eAAY,IACZ,cACA,gBACa;CACb,IAAM,IAAU,GAAO,EAEjB,IAAW,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KADvB,IAAQ,IAAI,IAAQ,GACqB,CAAC,EACtD,IAAM,KAAK,MAAM,IAAW,IAAI,EAChC,KAAK,IAAO,KAAa,GACzB,IAAI,IAAI,KAAK,KAAK,GAClB,IAAS,IAAO;AAEtB,QACE,kBAAC,OAAD;EAAK,WAAW,EAAG,2CAA2C,EAAU;YAAxE,CACE,kBAAC,OAAD;GACE,OAAO;GACP,QAAQ;GACR,SAAS,OAAO,EAAK,GAAG;GACxB,MAAK;GACL,mBAAiB;aALnB;IAOE,kBAAC,SAAD;KAAO,IAAI;eAAU,KAAa,GAAG,EAAM,MAAM,EAAM,IAAI,EAAI;KAAY,CAAA;IAC3E,kBAAC,UAAD;KACE,IAAI;KACJ,IAAI;KACD;KACH,MAAK;KACL,aAAa;KACb,WAAU;KACV,CAAA;IACF,kBAAC,UAAD;KACE,IAAI;KACJ,IAAI;KACD;KACH,MAAK;KACL,aAAa;KACb,eAAc;KACd,iBAAiB;KACjB,kBAAkB,KAAK,IAAI;KAC3B,WAAW,cAAc,EAAO,GAAG,EAAO;KAC1C,WAAU;KACV,CAAA;IACF,kBAAC,QAAD;KACE,GAAG;KACH,GAAG;KACH,YAAW;KACX,kBAAiB;KACjB,WAAU;eAET,KAAe,GAAG,EAAI;KAClB,CAAA;IACH;MACL,KAAW,kBAAC,QAAD;GAAM,WAAU;aAA8B;GAAe,CAAA,CACrE"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { cn as e } from "../../utils/cn.js";
|
|
2
|
+
import { jsx as t, jsxs as n } from "react/jsx-runtime";
|
|
3
|
+
import { GlassCard as r } from "@burdenoff/fe-libs/ui";
|
|
4
|
+
//#region src/theculturepost/components/viz/StatTile.tsx
|
|
5
|
+
function i({ label: i, value: a, icon: o, caption: s, className: c }) {
|
|
6
|
+
return /* @__PURE__ */ n(r, {
|
|
7
|
+
treatment: "metric",
|
|
8
|
+
className: e("flex items-start gap-3 p-4", c),
|
|
9
|
+
children: [o && /* @__PURE__ */ t("span", {
|
|
10
|
+
className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-[var(--color-accent-soft)] text-text-primary",
|
|
11
|
+
"aria-hidden": "true",
|
|
12
|
+
children: o
|
|
13
|
+
}), /* @__PURE__ */ n("div", {
|
|
14
|
+
className: "min-w-0",
|
|
15
|
+
children: [
|
|
16
|
+
/* @__PURE__ */ t("p", {
|
|
17
|
+
className: "truncate text-xs font-medium uppercase tracking-wide text-text-tertiary",
|
|
18
|
+
children: i
|
|
19
|
+
}),
|
|
20
|
+
/* @__PURE__ */ t("p", {
|
|
21
|
+
className: "font-display mt-0.5 text-2xl font-semibold leading-tight text-text-primary",
|
|
22
|
+
children: a
|
|
23
|
+
}),
|
|
24
|
+
s && /* @__PURE__ */ t("p", {
|
|
25
|
+
className: "mt-0.5 text-xs text-text-secondary",
|
|
26
|
+
children: s
|
|
27
|
+
})
|
|
28
|
+
]
|
|
29
|
+
})]
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { i as StatTile };
|
|
34
|
+
|
|
35
|
+
//# sourceMappingURL=StatTile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StatTile.js","names":[],"sources":["../../../../src/theculturepost/components/viz/StatTile.tsx"],"sourcesContent":["/**\n * StatTile — a single headline metric: icon + label + big value + optional caption.\n * @module theculturepost/components/viz\n *\n * Token-only. NO trend deltas (the schema exposes no historical series to\n * compute a delta from — never fabricate one). Use inside a responsive grid.\n */\n\nimport type { ReactNode } from 'react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../../utils/cn';\n\ninterface StatTileProps {\n label: string;\n value: ReactNode;\n /** Leading icon (already sized, e.g. `<BookOpen className=\"h-5 w-5\" />`). */\n icon?: ReactNode;\n /** Small muted supporting line under the value. */\n caption?: string;\n className?: string;\n}\n\nexport function StatTile({ label, value, icon, caption, className }: StatTileProps) {\n return (\n <GlassCard treatment=\"metric\" className={cn('flex items-start gap-3 p-4', className)}>\n {icon && (\n <span\n className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-[var(--color-accent-soft)] text-text-primary\"\n aria-hidden=\"true\"\n >\n {icon}\n </span>\n )}\n <div className=\"min-w-0\">\n <p className=\"truncate text-xs font-medium uppercase tracking-wide text-text-tertiary\">\n {label}\n </p>\n <p className=\"font-display mt-0.5 text-2xl font-semibold leading-tight text-text-primary\">\n {value}\n </p>\n {caption && <p className=\"mt-0.5 text-xs text-text-secondary\">{caption}</p>}\n </div>\n </GlassCard>\n );\n}\n"],"mappings":";;;;AAsBA,SAAgB,EAAS,EAAE,UAAO,UAAO,SAAM,YAAS,gBAA4B;AAClF,QACE,kBAAC,GAAD;EAAW,WAAU;EAAS,WAAW,EAAG,8BAA8B,EAAU;YAApF,CACG,KACC,kBAAC,QAAD;GACE,WAAU;GACV,eAAY;aAEX;GACI,CAAA,EAET,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,KAAD;KAAG,WAAU;eACV;KACC,CAAA;IACJ,kBAAC,KAAD;KAAG,WAAU;eACV;KACC,CAAA;IACH,KAAW,kBAAC,KAAD;KAAG,WAAU;eAAsC;KAAY,CAAA;IACvE;KACI"}
|
|
@@ -4,7 +4,7 @@ import { useQuery as t } from "@tanstack/react-query";
|
|
|
4
4
|
import { getWorkspaceClient as n, gql as r, graphqlFetch as i } from "@burdenoff/fe-libs/shared/graphql";
|
|
5
5
|
import { uploadBlobToFiles as a } from "@burdenoff/fe-libs/storage";
|
|
6
6
|
//#region src/theculturepost/hooks/useFiles.ts
|
|
7
|
-
var o =
|
|
7
|
+
var o = 21600, s = {
|
|
8
8
|
covers: "/TheCulturePost/Covers",
|
|
9
9
|
artifacts: "/TheCulturePost/Artifacts",
|
|
10
10
|
recordings: "/TheCulturePost/Recordings"
|