@burdenoff/microfe-theculturepost 2026.704.2 → 2026.705.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/dist/theculturepost/TheCulturePostRoot.js +63 -45
  2. package/dist/theculturepost/TheCulturePostRoot.js.map +1 -1
  3. package/dist/theculturepost/TheCulturePostRoutes.js +17 -9
  4. package/dist/theculturepost/TheCulturePostRoutes.js.map +1 -1
  5. package/dist/theculturepost/components/AddCitationDialog.js +131 -102
  6. package/dist/theculturepost/components/AddCitationDialog.js.map +1 -1
  7. package/dist/theculturepost/components/ArchiveItemCard.js +56 -43
  8. package/dist/theculturepost/components/ArchiveItemCard.js.map +1 -1
  9. package/dist/theculturepost/components/AudioPlayer.js +74 -0
  10. package/dist/theculturepost/components/AudioPlayer.js.map +1 -0
  11. package/dist/theculturepost/components/CitationList.js +215 -105
  12. package/dist/theculturepost/components/CitationList.js.map +1 -1
  13. package/dist/theculturepost/components/CollectionCard.js +32 -32
  14. package/dist/theculturepost/components/CollectionCard.js.map +1 -1
  15. package/dist/theculturepost/components/CreateArchiveItemDialog.js +133 -93
  16. package/dist/theculturepost/components/CreateArchiveItemDialog.js.map +1 -1
  17. package/dist/theculturepost/components/CreateCollectionDialog.js +91 -70
  18. package/dist/theculturepost/components/CreateCollectionDialog.js.map +1 -1
  19. package/dist/theculturepost/components/CreateStoryDialog.js +311 -132
  20. package/dist/theculturepost/components/CreateStoryDialog.js.map +1 -1
  21. package/dist/theculturepost/components/DecorativeCover.js +78 -0
  22. package/dist/theculturepost/components/DecorativeCover.js.map +1 -0
  23. package/dist/theculturepost/components/DecorativeDivider.js +45 -0
  24. package/dist/theculturepost/components/DecorativeDivider.js.map +1 -0
  25. package/dist/theculturepost/components/DiscussionCard.js +75 -60
  26. package/dist/theculturepost/components/DiscussionCard.js.map +1 -1
  27. package/dist/theculturepost/components/HistoryTimeline.js +115 -0
  28. package/dist/theculturepost/components/HistoryTimeline.js.map +1 -0
  29. package/dist/theculturepost/components/LifecycleStepper.js +33 -0
  30. package/dist/theculturepost/components/LifecycleStepper.js.map +1 -0
  31. package/dist/theculturepost/components/OpenDiscussionDialog.js +66 -56
  32. package/dist/theculturepost/components/OpenDiscussionDialog.js.map +1 -1
  33. package/dist/theculturepost/components/OralHistoryCard.js +6 -73
  34. package/dist/theculturepost/components/OverlayBadge.js +22 -0
  35. package/dist/theculturepost/components/OverlayBadge.js.map +1 -0
  36. package/dist/theculturepost/components/PageHero.js +62 -0
  37. package/dist/theculturepost/components/PageHero.js.map +1 -0
  38. package/dist/theculturepost/components/RecordOralHistoryDialog.js +306 -131
  39. package/dist/theculturepost/components/RecordOralHistoryDialog.js.map +1 -1
  40. package/dist/theculturepost/components/ReplyThread.js +268 -139
  41. package/dist/theculturepost/components/ReplyThread.js.map +1 -1
  42. package/dist/theculturepost/components/StatusBadge.js +9 -9
  43. package/dist/theculturepost/components/StatusBadge.js.map +1 -1
  44. package/dist/theculturepost/components/StoryCard.js +54 -49
  45. package/dist/theculturepost/components/StoryCard.js.map +1 -1
  46. package/dist/theculturepost/components/TopicCard.js +60 -41
  47. package/dist/theculturepost/components/TopicCard.js.map +1 -1
  48. package/dist/theculturepost/components/Waveform.js +31 -0
  49. package/dist/theculturepost/components/Waveform.js.map +1 -0
  50. package/dist/theculturepost/components/categoryMeta.js +20 -0
  51. package/dist/theculturepost/components/categoryMeta.js.map +1 -0
  52. package/dist/theculturepost/components/index.js +41 -0
  53. package/dist/theculturepost/components/viz/BarChart.js +53 -0
  54. package/dist/theculturepost/components/viz/BarChart.js.map +1 -0
  55. package/dist/theculturepost/components/viz/Donut.js +58 -0
  56. package/dist/theculturepost/components/viz/Donut.js.map +1 -0
  57. package/dist/theculturepost/components/viz/StatTile.js +35 -0
  58. package/dist/theculturepost/components/viz/StatTile.js.map +1 -0
  59. package/dist/theculturepost/components/viz/index.js +3 -0
  60. package/dist/theculturepost/pages/HomePage.js +247 -124
  61. package/dist/theculturepost/pages/HomePage.js.map +1 -1
  62. package/dist/theculturepost/pages/archive/ArchiveItemDetailPage.js +319 -127
  63. package/dist/theculturepost/pages/archive/ArchiveItemDetailPage.js.map +1 -1
  64. package/dist/theculturepost/pages/archive/ArchivePage.js +265 -180
  65. package/dist/theculturepost/pages/archive/ArchivePage.js.map +1 -1
  66. package/dist/theculturepost/pages/archive/CollectionDetailPage.js +158 -143
  67. package/dist/theculturepost/pages/archive/CollectionDetailPage.js.map +1 -1
  68. package/dist/theculturepost/pages/discussions/DiscussionDetailPage.js +155 -93
  69. package/dist/theculturepost/pages/discussions/DiscussionDetailPage.js.map +1 -1
  70. package/dist/theculturepost/pages/discussions/DiscussionsPage.js +16 -16
  71. package/dist/theculturepost/pages/discussions/DiscussionsPage.js.map +1 -1
  72. package/dist/theculturepost/pages/governance/GovernancePage.js +293 -0
  73. package/dist/theculturepost/pages/governance/GovernancePage.js.map +1 -0
  74. package/dist/theculturepost/pages/stories/StoriesPage.js +18 -18
  75. package/dist/theculturepost/pages/stories/StoriesPage.js.map +1 -1
  76. package/dist/theculturepost/pages/stories/StoryDetailPage.js +603 -285
  77. package/dist/theculturepost/pages/stories/StoryDetailPage.js.map +1 -1
  78. package/dist/theculturepost/pages/timeline/TimelinePage.js +162 -0
  79. package/dist/theculturepost/pages/timeline/TimelinePage.js.map +1 -0
  80. package/dist/theculturepost/pages/topics/TopicDetailPage.js +239 -163
  81. package/dist/theculturepost/pages/topics/TopicDetailPage.js.map +1 -1
  82. package/dist/theculturepost/pages/topics/TopicsPage.js +76 -74
  83. package/dist/theculturepost/pages/topics/TopicsPage.js.map +1 -1
  84. package/dist/theculturepost/styles/injectDisplayFont.js +11 -0
  85. package/dist/theculturepost/styles/injectDisplayFont.js.map +1 -0
  86. package/dist/theculturepost/utils/formatters.js +6 -1
  87. package/dist/theculturepost/utils/formatters.js.map +1 -1
  88. package/package.json +1 -1
  89. package/dist/theculturepost/components/OralHistoryCard.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"AddCitationDialog.js","names":[],"sources":["../../../src/theculturepost/components/AddCitationDialog.tsx"],"sourcesContent":["/**\n * AddCitationDialog — attach a verifiable source to a story.\n * @module theculturepost/components\n *\n * Writes `tcpAddCitation`. An optional source scan is uploaded to\n * wspace-files-svc (only the id is stored).\n */\n\nimport { useState } from 'react';\nimport {\n Button,\n Input,\n Label,\n Textarea,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { FileUploadField } from './FileUploadField';\nimport { useTcpAddCitation, FILE_FOLDERS } from '../hooks';\nimport { formatEnumLabel } from '../utils/formatters';\nimport { notify, describeError } from '../lib/toast';\nimport type { TcpCitation, TcpSourceType } from '../types';\n\nconst SOURCE_TYPES: TcpSourceType[] = [\n 'WEB',\n 'BOOK',\n 'ACADEMIC',\n 'PERIODICAL',\n 'INTERVIEW',\n 'PRIMARY_DOCUMENT',\n 'ARCHIVAL_RECORD',\n 'ARTIFACT',\n];\n\nconst dateToIso = (value: string): string | null => {\n if (!value) return null;\n const d = new Date(value);\n return Number.isNaN(d.getTime()) ? null : d.toISOString();\n};\n\ninterface AddCitationDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n storyId: string;\n onCreated?: (citation: TcpCitation) => void;\n}\n\nexport function AddCitationDialog({\n open,\n onOpenChange,\n storyId,\n onCreated,\n}: AddCitationDialogProps) {\n const addCitation = useTcpAddCitation();\n const [title, setTitle] = useState('');\n const [author, setAuthor] = useState('');\n const [publisher, setPublisher] = useState('');\n const [publishedOn, setPublishedOn] = useState('');\n const [url, setUrl] = useState('');\n const [excerpt, setExcerpt] = useState('');\n const [provenance, setProvenance] = useState('');\n const [sourceType, setSourceType] = useState<TcpSourceType>('WEB');\n const [sourceFileId, setSourceFileId] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n const reset = () => {\n setTitle('');\n setAuthor('');\n setPublisher('');\n setPublishedOn('');\n setUrl('');\n setExcerpt('');\n setProvenance('');\n setSourceType('WEB');\n setSourceFileId(null);\n setError(null);\n };\n\n const submit = async () => {\n if (!title.trim()) {\n setError('A citation title is required.');\n return;\n }\n try {\n const created = await addCitation.mutateAsync({\n storyId,\n title: title.trim(),\n author: author.trim() || null,\n publisher: publisher.trim() || null,\n publishedOn: dateToIso(publishedOn),\n url: url.trim() || null,\n excerpt: excerpt.trim() || null,\n provenance: provenance.trim() || null,\n sourceType,\n sourceFileId,\n });\n notify.success('Citation added', created.title);\n onOpenChange(false);\n reset();\n onCreated?.(created);\n } catch (err) {\n setError(describeError(err));\n }\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"sm:max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Add a citation</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Ground this story in the record with a verifiable source.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"flex max-h-[62vh] flex-col gap-4 overflow-y-auto py-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"cite-title\">Title</Label>\n <Input\n id=\"cite-title\"\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n placeholder=\"Source title\"\n />\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"cite-author\">Author</Label>\n <Input id=\"cite-author\" value={author} onChange={(e) => setAuthor(e.target.value)} />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"cite-publisher\">Publisher</Label>\n <Input\n id=\"cite-publisher\"\n value={publisher}\n onChange={(e) => setPublisher(e.target.value)}\n />\n </div>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div className=\"grid gap-1.5\">\n <Label>Source type</Label>\n <Select value={sourceType} onValueChange={(v) => setSourceType(v as TcpSourceType)}>\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {SOURCE_TYPES.map((s) => (\n <SelectItem key={s} value={s}>\n {formatEnumLabel(s)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"cite-published\">Published on</Label>\n <Input\n id=\"cite-published\"\n type=\"date\"\n value={publishedOn}\n onChange={(e) => setPublishedOn(e.target.value)}\n />\n </div>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"cite-url\">URL</Label>\n <Input\n id=\"cite-url\"\n value={url}\n onChange={(e) => setUrl(e.target.value)}\n placeholder=\"https://…\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"cite-excerpt\">Excerpt</Label>\n <Textarea\n id=\"cite-excerpt\"\n value={excerpt}\n onChange={(e) => setExcerpt(e.target.value)}\n placeholder=\"A short quoted passage\"\n rows={2}\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"cite-provenance\">Provenance</Label>\n <Textarea\n id=\"cite-provenance\"\n value={provenance}\n onChange={(e) => setProvenance(e.target.value)}\n rows={2}\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label>Source scan</Label>\n <FileUploadField\n folder={FILE_FOLDERS.artifacts}\n accept=\"image/*,application/pdf\"\n label=\"Upload source scan\"\n hint=\"A scan of the primary source (optional).\"\n fileId={sourceFileId}\n onUploaded={setSourceFileId}\n onCleared={() => setSourceFileId(null)}\n />\n </div>\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n </div>\n\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => onOpenChange(false)}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={addCitation.isPending}>\n Add citation\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AddCitationDialog.js","names":[],"sources":["../../../src/theculturepost/components/AddCitationDialog.tsx"],"sourcesContent":["/**\n * AddCitationDialog — attach a verifiable source to a story.\n * @module theculturepost/components\n *\n * Writes `tcpAddCitation`. An optional source scan is uploaded to\n * wspace-files-svc (only the id is stored).\n */\n\nimport { createElement, useState } from 'react';\nimport { ShieldCheck } from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Textarea,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { FileUploadField } from './FileUploadField';\nimport { sourceTypeIcon } from './CitationList';\nimport { useTcpAddCitation, FILE_FOLDERS } from '../hooks';\nimport { formatEnumLabel } from '../utils/formatters';\nimport { notify, describeError } from '../lib/toast';\nimport type { TcpCitation, TcpSourceType } from '../types';\n\nconst SOURCE_TYPES: TcpSourceType[] = [\n 'WEB',\n 'BOOK',\n 'ACADEMIC',\n 'PERIODICAL',\n 'INTERVIEW',\n 'PRIMARY_DOCUMENT',\n 'ARCHIVAL_RECORD',\n 'ARTIFACT',\n];\n\nconst dateToIso = (value: string): string | null => {\n if (!value) return null;\n const d = new Date(value);\n return Number.isNaN(d.getTime()) ? null : d.toISOString();\n};\n\ninterface AddCitationDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n storyId: string;\n onCreated?: (citation: TcpCitation) => void;\n}\n\nexport function AddCitationDialog({\n open,\n onOpenChange,\n storyId,\n onCreated,\n}: AddCitationDialogProps) {\n const addCitation = useTcpAddCitation();\n const [title, setTitle] = useState('');\n const [author, setAuthor] = useState('');\n const [publisher, setPublisher] = useState('');\n const [publishedOn, setPublishedOn] = useState('');\n const [url, setUrl] = useState('');\n const [excerpt, setExcerpt] = useState('');\n const [provenance, setProvenance] = useState('');\n const [sourceType, setSourceType] = useState<TcpSourceType>('WEB');\n const [sourceFileId, setSourceFileId] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n const reset = () => {\n setTitle('');\n setAuthor('');\n setPublisher('');\n setPublishedOn('');\n setUrl('');\n setExcerpt('');\n setProvenance('');\n setSourceType('WEB');\n setSourceFileId(null);\n setError(null);\n };\n\n const submit = async () => {\n if (!title.trim()) {\n setError('A citation title is required.');\n return;\n }\n try {\n const created = await addCitation.mutateAsync({\n storyId,\n title: title.trim(),\n author: author.trim() || null,\n publisher: publisher.trim() || null,\n publishedOn: dateToIso(publishedOn),\n url: url.trim() || null,\n excerpt: excerpt.trim() || null,\n provenance: provenance.trim() || null,\n sourceType,\n sourceFileId,\n });\n notify.success('Citation added', created.title);\n onOpenChange(false);\n reset();\n onCreated?.(created);\n } catch (err) {\n setError(describeError(err));\n }\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"sm:max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Add a citation</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Ground this story in the record with a verifiable source.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"flex max-h-[62vh] flex-col gap-5 overflow-y-auto py-2\">\n {/* Source identity */}\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"cite-title\">\n Title <span className=\"text-status-error-text\">*</span>\n </Label>\n <Input\n id=\"cite-title\"\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n placeholder=\"e.g. The Penguin History of Early India\"\n />\n </div>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label>Source type</Label>\n <Select value={sourceType} onValueChange={(v) => setSourceType(v as TcpSourceType)}>\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {SOURCE_TYPES.map((s) => (\n <SelectItem key={s} value={s}>\n <span className=\"inline-flex items-center gap-2\">\n {createElement(sourceTypeIcon(s), {\n className: 'h-3.5 w-3.5 text-text-tertiary',\n })}\n {formatEnumLabel(s)}\n </span>\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"cite-published\">Published on</Label>\n <Input\n id=\"cite-published\"\n type=\"date\"\n value={publishedOn}\n onChange={(e) => setPublishedOn(e.target.value)}\n />\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"cite-author\">Author</Label>\n <Input\n id=\"cite-author\"\n value={author}\n onChange={(e) => setAuthor(e.target.value)}\n placeholder=\"e.g. Romila Thapar\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"cite-publisher\">Publisher</Label>\n <Input\n id=\"cite-publisher\"\n value={publisher}\n onChange={(e) => setPublisher(e.target.value)}\n placeholder=\"e.g. Penguin Books\"\n />\n </div>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"cite-url\">URL</Label>\n <Input\n id=\"cite-url\"\n type=\"url\"\n inputMode=\"url\"\n value={url}\n onChange={(e) => setUrl(e.target.value)}\n placeholder=\"https://…\"\n />\n </div>\n\n {/* Grounding detail */}\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"cite-excerpt\">Excerpt</Label>\n <Textarea\n id=\"cite-excerpt\"\n value={excerpt}\n onChange={(e) => setExcerpt(e.target.value)}\n placeholder=\"A short quoted passage that supports the story.\"\n rows={2}\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"cite-provenance\">Provenance</Label>\n <Textarea\n id=\"cite-provenance\"\n value={provenance}\n onChange={(e) => setProvenance(e.target.value)}\n placeholder=\"Where the source is held, reference numbers, chain of custody…\"\n rows={2}\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label>Source scan</Label>\n <FileUploadField\n folder={FILE_FOLDERS.artifacts}\n accept=\"image/*,application/pdf\"\n label=\"Upload source scan\"\n hint=\"A scan of the primary source (optional).\"\n fileId={sourceFileId}\n onUploaded={setSourceFileId}\n onCleared={() => setSourceFileId(null)}\n />\n </div>\n\n <p className=\"flex items-start gap-2 rounded-lg bg-bg-sunken/60 px-3 py-2 text-xs text-text-tertiary\">\n <ShieldCheck className=\"mt-0.5 h-3.5 w-3.5 shrink-0\" aria-hidden=\"true\" />\n New sources start as <span className=\"font-medium text-text-secondary\">\n Unverified\n </span>{' '}\n and are reviewed for accuracy, relevance, and cultural sensitivity before verification.\n </p>\n\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n </div>\n\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => onOpenChange(false)}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={addCitation.isPending}>\n Add citation\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
@@ -1,68 +1,81 @@
1
1
  import { CoverImage as e } from "./CoverImage.js";
2
- import { formatDate as t } from "../utils/formatters.js";
3
- import { StatusBadge as n } from "./StatusBadge.js";
4
- import { KIND_ICON as r } from "./kindIcon.js";
5
- import { createElement as i } from "react";
6
- import { CalendarClock as a, MapPin as o } from "lucide-react";
7
- import { jsx as s, jsxs as c } from "react/jsx-runtime";
8
- import { GlassCard as l } from "@burdenoff/fe-libs/ui";
2
+ import { formatDate as t, formatEnumLabel as n } from "../utils/formatters.js";
3
+ import { StatusBadge as r } from "./StatusBadge.js";
4
+ import { OverlayBadge as i } from "./OverlayBadge.js";
5
+ import { KIND_ICON as a } from "./kindIcon.js";
6
+ import { createElement as o } from "react";
7
+ import { CalendarClock as s, Languages as c, MapPin as l } from "lucide-react";
8
+ import { jsx as u, jsxs as d } from "react/jsx-runtime";
9
+ import { GlassCard as f } from "@burdenoff/fe-libs/ui";
9
10
  //#region src/theculturepost/components/ArchiveItemCard.tsx
10
- function u({ item: u, onOpen: d }) {
11
- let f = r[u.kind];
12
- return /* @__PURE__ */ c(l, {
11
+ function p({ item: p, onOpen: m }) {
12
+ let h = a[p.kind], g = p.languages[0];
13
+ return /* @__PURE__ */ d(f, {
13
14
  treatment: "plain",
14
15
  className: "flex flex-col overflow-hidden",
15
- children: [/* @__PURE__ */ c("button", {
16
+ children: [/* @__PURE__ */ d("button", {
16
17
  type: "button",
17
- onClick: d,
18
+ onClick: m,
18
19
  className: "group relative block aspect-[4/3] w-full overflow-hidden text-left",
19
- "aria-label": `Open ${u.title}`,
20
- children: [/* @__PURE__ */ s(e, {
21
- fileId: u.file?.id,
22
- alt: u.title,
20
+ "aria-label": `Open ${p.title}`,
21
+ children: [/* @__PURE__ */ u(e, {
22
+ fileId: p.file?.id,
23
+ alt: p.title,
23
24
  rounded: "rounded-none",
24
25
  className: "h-full w-full transition-transform duration-500 group-hover:scale-[1.03]",
25
- icon: /* @__PURE__ */ s(f, { className: "h-8 w-8" })
26
- }), /* @__PURE__ */ c("span", {
27
- className: "absolute left-2 top-2 inline-flex items-center gap-1 rounded-full bg-bg-canvas/85 px-2 py-0.5 text-xs font-medium text-text-primary",
28
- children: [i(f, { className: "h-3 w-3" }), u.kind.charAt(0) + u.kind.slice(1).toLowerCase()]
26
+ icon: /* @__PURE__ */ u(h, { className: "h-8 w-8" })
27
+ }), /* @__PURE__ */ u(i, {
28
+ position: "top-left",
29
+ icon: o(h, { className: "h-3 w-3" }),
30
+ children: n(p.kind)
29
31
  })]
30
- }), /* @__PURE__ */ c("div", {
32
+ }), /* @__PURE__ */ d("div", {
31
33
  className: "flex flex-1 flex-col p-4",
32
34
  children: [
33
- /* @__PURE__ */ s("div", {
35
+ /* @__PURE__ */ u("div", {
34
36
  className: "mb-1.5",
35
- children: /* @__PURE__ */ s(n, { status: u.accessLevel })
37
+ children: /* @__PURE__ */ u(r, { status: p.accessLevel })
36
38
  }),
37
- /* @__PURE__ */ s("button", {
39
+ /* @__PURE__ */ u("button", {
38
40
  type: "button",
39
- onClick: d,
41
+ onClick: m,
40
42
  className: "text-left text-sm font-semibold leading-snug text-text-primary hover:underline",
41
- children: u.title
43
+ children: p.title
42
44
  }),
43
- /* @__PURE__ */ c("div", {
45
+ /* @__PURE__ */ d("div", {
44
46
  className: "mt-auto flex flex-col gap-1 pt-3 text-xs text-text-tertiary",
45
- children: [u.origin && /* @__PURE__ */ c("span", {
46
- className: "inline-flex items-center gap-1 truncate",
47
- children: [
48
- /* @__PURE__ */ s(o, { className: "h-3.5 w-3.5 shrink-0" }),
49
- " ",
50
- u.origin
51
- ]
52
- }), u.datedAt && /* @__PURE__ */ c("span", {
53
- className: "inline-flex items-center gap-1",
54
- children: [
55
- /* @__PURE__ */ s(a, { className: "h-3.5 w-3.5 shrink-0" }),
56
- " ",
57
- t(u.datedAt)
58
- ]
59
- })]
47
+ children: [
48
+ p.origin && /* @__PURE__ */ d("span", {
49
+ className: "inline-flex items-center gap-1 truncate",
50
+ children: [
51
+ /* @__PURE__ */ u(l, { className: "h-3.5 w-3.5 shrink-0" }),
52
+ " ",
53
+ p.origin
54
+ ]
55
+ }),
56
+ g && /* @__PURE__ */ d("span", {
57
+ className: "inline-flex items-center gap-1",
58
+ children: [
59
+ /* @__PURE__ */ u(c, { className: "h-3.5 w-3.5 shrink-0" }),
60
+ " ",
61
+ n(g)
62
+ ]
63
+ }),
64
+ p.datedAt && /* @__PURE__ */ d("span", {
65
+ className: "inline-flex items-center gap-1",
66
+ children: [
67
+ /* @__PURE__ */ u(s, { className: "h-3.5 w-3.5 shrink-0" }),
68
+ " ",
69
+ t(p.datedAt)
70
+ ]
71
+ })
72
+ ]
60
73
  })
61
74
  ]
62
75
  })]
63
76
  });
64
77
  }
65
78
  //#endregion
66
- export { u as ArchiveItemCard };
79
+ export { p as ArchiveItemCard };
67
80
 
68
81
  //# sourceMappingURL=ArchiveItemCard.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ArchiveItemCard.js","names":[],"sources":["../../../src/theculturepost/components/ArchiveItemCard.tsx"],"sourcesContent":["/**\n * ArchiveItemCard — a tile for an archived artifact.\n * @module theculturepost/components\n *\n * Kind + access-level badges, title, provenance/origin, dated date, tags. Image\n * kinds show a thumbnail (resolved from the file id). Links to the item detail.\n */\n\nimport { createElement } from 'react';\nimport { MapPin, CalendarClock } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { CoverImage } from './CoverImage';\nimport { StatusBadge } from './StatusBadge';\nimport { KIND_ICON } from './kindIcon';\nimport { formatDate } from '../utils/formatters';\nimport type { TcpArchiveItem } from '../types';\n\ninterface ArchiveItemCardProps {\n item: TcpArchiveItem;\n onOpen: () => void;\n}\n\nexport function ArchiveItemCard({ item, onOpen }: ArchiveItemCardProps) {\n const Icon = KIND_ICON[item.kind];\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-[4/3] w-full overflow-hidden text-left\"\n aria-label={`Open ${item.title}`}\n >\n <CoverImage\n fileId={item.file?.id}\n alt={item.title}\n rounded=\"rounded-none\"\n className=\"h-full w-full transition-transform duration-500 group-hover:scale-[1.03]\"\n icon={<Icon className=\"h-8 w-8\" />}\n />\n <span className=\"absolute left-2 top-2 inline-flex items-center gap-1 rounded-full bg-bg-canvas/85 px-2 py-0.5 text-xs font-medium text-text-primary\">\n {createElement(Icon, { className: 'h-3 w-3' })}\n {item.kind.charAt(0) + item.kind.slice(1).toLowerCase()}\n </span>\n </button>\n\n <div className=\"flex flex-1 flex-col p-4\">\n <div className=\"mb-1.5\">\n <StatusBadge status={item.accessLevel} />\n </div>\n <button\n type=\"button\"\n onClick={onOpen}\n className=\"text-left text-sm font-semibold leading-snug text-text-primary hover:underline\"\n >\n {item.title}\n </button>\n\n <div className=\"mt-auto flex flex-col gap-1 pt-3 text-xs text-text-tertiary\">\n {item.origin && (\n <span className=\"inline-flex items-center gap-1 truncate\">\n <MapPin className=\"h-3.5 w-3.5 shrink-0\" /> {item.origin}\n </span>\n )}\n {item.datedAt && (\n <span className=\"inline-flex items-center gap-1\">\n <CalendarClock className=\"h-3.5 w-3.5 shrink-0\" /> {formatDate(item.datedAt)}\n </span>\n )}\n </div>\n </div>\n </GlassCard>\n );\n}\n"],"mappings":";;;;;;;;;AAsBA,SAAgB,EAAgB,EAAE,SAAM,aAAgC;CACtE,IAAM,IAAO,EAAU,EAAK;AAE5B,QACE,kBAAC,GAAD;EAAW,WAAU;EAAQ,WAAU;YAAvC,CACE,kBAAC,UAAD;GACE,MAAK;GACL,SAAS;GACT,WAAU;GACV,cAAY,QAAQ,EAAK;aAJ3B,CAME,kBAAC,GAAD;IACE,QAAQ,EAAK,MAAM;IACnB,KAAK,EAAK;IACV,SAAQ;IACR,WAAU;IACV,MAAM,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;IAClC,CAAA,EACF,kBAAC,QAAD;IAAM,WAAU;cAAhB,CACG,EAAc,GAAM,EAAE,WAAW,WAAW,CAAC,EAC7C,EAAK,KAAK,OAAO,EAAE,GAAG,EAAK,KAAK,MAAM,EAAE,CAAC,aAAa,CAClD;MACA;MAET,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD,EAAa,QAAQ,EAAK,aAAe,CAAA;KACrC,CAAA;IACN,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,WAAU;eAET,EAAK;KACC,CAAA;IAET,kBAAC,OAAD;KAAK,WAAU;eAAf,CACG,EAAK,UACJ,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OACE,kBAAC,GAAD,EAAQ,WAAU,wBAAyB,CAAA;;OAAE,EAAK;OAC7C;SAER,EAAK,WACJ,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OACE,kBAAC,GAAD,EAAe,WAAU,wBAAyB,CAAA;;OAAE,EAAW,EAAK,QAAQ;OACvE;QAEL;;IACF;KACI"}
1
+ {"version":3,"file":"ArchiveItemCard.js","names":[],"sources":["../../../src/theculturepost/components/ArchiveItemCard.tsx"],"sourcesContent":["/**\n * ArchiveItemCard — a tile for an archived artifact.\n * @module theculturepost/components\n *\n * Kind-icon badge overlaid on the cover, access-level badge, title, provenance/\n * origin, primary language + dated chips. Image-like kinds resolve a thumbnail\n * from the file id; others fall back to the kind icon. The schema exposes a\n * single `file` (no galleries) — never render more than one. Links to detail.\n */\n\nimport { createElement } from 'react';\nimport { MapPin, CalendarClock, Languages } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { CoverImage } from './CoverImage';\nimport { StatusBadge } from './StatusBadge';\nimport { OverlayBadge } from './OverlayBadge';\nimport { KIND_ICON } from './kindIcon';\nimport { formatDate, formatEnumLabel } from '../utils/formatters';\nimport type { TcpArchiveItem } from '../types';\n\ninterface ArchiveItemCardProps {\n item: TcpArchiveItem;\n onOpen: () => void;\n}\n\nexport function ArchiveItemCard({ item, onOpen }: ArchiveItemCardProps) {\n const Icon = KIND_ICON[item.kind];\n const primaryLanguage = item.languages[0];\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-[4/3] w-full overflow-hidden text-left\"\n aria-label={`Open ${item.title}`}\n >\n <CoverImage\n fileId={item.file?.id}\n alt={item.title}\n rounded=\"rounded-none\"\n className=\"h-full w-full transition-transform duration-500 group-hover:scale-[1.03]\"\n icon={<Icon className=\"h-8 w-8\" />}\n />\n <OverlayBadge position=\"top-left\" icon={createElement(Icon, { className: 'h-3 w-3' })}>\n {formatEnumLabel(item.kind)}\n </OverlayBadge>\n </button>\n\n <div className=\"flex flex-1 flex-col p-4\">\n <div className=\"mb-1.5\">\n <StatusBadge status={item.accessLevel} />\n </div>\n <button\n type=\"button\"\n onClick={onOpen}\n className=\"text-left text-sm font-semibold leading-snug text-text-primary hover:underline\"\n >\n {item.title}\n </button>\n\n <div className=\"mt-auto flex flex-col gap-1 pt-3 text-xs text-text-tertiary\">\n {item.origin && (\n <span className=\"inline-flex items-center gap-1 truncate\">\n <MapPin className=\"h-3.5 w-3.5 shrink-0\" /> {item.origin}\n </span>\n )}\n {primaryLanguage && (\n <span className=\"inline-flex items-center gap-1\">\n <Languages className=\"h-3.5 w-3.5 shrink-0\" /> {formatEnumLabel(primaryLanguage)}\n </span>\n )}\n {item.datedAt && (\n <span className=\"inline-flex items-center gap-1\">\n <CalendarClock className=\"h-3.5 w-3.5 shrink-0\" /> {formatDate(item.datedAt)}\n </span>\n )}\n </div>\n </div>\n </GlassCard>\n );\n}\n"],"mappings":";;;;;;;;;;AAyBA,SAAgB,EAAgB,EAAE,SAAM,aAAgC;CACtE,IAAM,IAAO,EAAU,EAAK,OACtB,IAAkB,EAAK,UAAU;AAEvC,QACE,kBAAC,GAAD;EAAW,WAAU;EAAQ,WAAU;YAAvC,CACE,kBAAC,UAAD;GACE,MAAK;GACL,SAAS;GACT,WAAU;GACV,cAAY,QAAQ,EAAK;aAJ3B,CAME,kBAAC,GAAD;IACE,QAAQ,EAAK,MAAM;IACnB,KAAK,EAAK;IACV,SAAQ;IACR,WAAU;IACV,MAAM,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;IAClC,CAAA,EACF,kBAAC,GAAD;IAAc,UAAS;IAAW,MAAM,EAAc,GAAM,EAAE,WAAW,WAAW,CAAC;cAClF,EAAgB,EAAK,KAAK;IACd,CAAA,CACR;MAET,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD,EAAa,QAAQ,EAAK,aAAe,CAAA;KACrC,CAAA;IACN,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,WAAU;eAET,EAAK;KACC,CAAA;IAET,kBAAC,OAAD;KAAK,WAAU;eAAf;MACG,EAAK,UACJ,kBAAC,QAAD;OAAM,WAAU;iBAAhB;QACE,kBAAC,GAAD,EAAQ,WAAU,wBAAyB,CAAA;;QAAE,EAAK;QAC7C;;MAER,KACC,kBAAC,QAAD;OAAM,WAAU;iBAAhB;QACE,kBAAC,GAAD,EAAW,WAAU,wBAAyB,CAAA;;QAAE,EAAgB,EAAgB;QAC3E;;MAER,EAAK,WACJ,kBAAC,QAAD;OAAM,WAAU;iBAAhB;QACE,kBAAC,GAAD,EAAe,WAAU,wBAAyB,CAAA;;QAAE,EAAW,EAAK,QAAQ;QACvE;;MAEL;;IACF;KACI"}
@@ -0,0 +1,74 @@
1
+ import { cn as e } from "../utils/cn.js";
2
+ import { Waveform as t } from "./Waveform.js";
3
+ import { useCallback as n, useRef as r, useState as i } from "react";
4
+ import { Pause as a, Play as o } from "lucide-react";
5
+ import { jsx as s, jsxs as c } from "react/jsx-runtime";
6
+ //#region src/theculturepost/components/AudioPlayer.tsx
7
+ function l(e) {
8
+ if (!Number.isFinite(e) || e < 0) return "0:00";
9
+ let t = Math.floor(e), n = Math.floor(t / 3600), r = Math.floor(t % 3600 / 60), i = t % 60, a = n > 0 ? String(r).padStart(2, "0") : String(r), o = String(i).padStart(2, "0");
10
+ return n > 0 ? `${n}:${a}:${o}` : `${a}:${o}`;
11
+ }
12
+ function u({ src: u, seed: d, durationSecs: f = 0, className: p }) {
13
+ let m = r(null), [h, g] = i(!1), [_, v] = i(0), [y, b] = i(f), [x, S] = i(u);
14
+ x !== u && (S(u), g(!1), v(0), b(f));
15
+ let C = !u, w = y > 0 ? Math.min(1, _ / y) : 0, T = n(() => {
16
+ let e = m.current;
17
+ !e || C || (e.paused ? e.play() : e.pause());
18
+ }, [C]), E = n((e) => {
19
+ let t = m.current;
20
+ !t || !Number.isFinite(t.duration) || t.duration <= 0 || (t.currentTime = Math.max(0, Math.min(1, e)) * t.duration, v(t.currentTime));
21
+ }, []), D = n((e) => {
22
+ let t = e.currentTarget.getBoundingClientRect();
23
+ t.width <= 0 || E((e.clientX - t.left) / t.width);
24
+ }, [E]);
25
+ return /* @__PURE__ */ c("div", {
26
+ className: e("flex items-center gap-3 rounded-xl border border-border-seam bg-bg-surface p-3", p),
27
+ children: [
28
+ /* @__PURE__ */ s("button", {
29
+ type: "button",
30
+ onClick: T,
31
+ disabled: C,
32
+ "aria-label": h ? "Pause recording" : "Play recording",
33
+ className: e("flex h-11 w-11 shrink-0 items-center justify-center rounded-full transition-colors", C ? "cursor-not-allowed bg-bg-sunken text-text-tertiary" : "bg-action-primary-bg text-action-primary-text hover:opacity-90"),
34
+ children: s(h ? a : o, { className: "h-5 w-5 fill-current" })
35
+ }),
36
+ /* @__PURE__ */ c("div", {
37
+ className: "min-w-0 flex-1",
38
+ children: [/* @__PURE__ */ s("button", {
39
+ type: "button",
40
+ onClick: D,
41
+ disabled: C,
42
+ "aria-label": "Seek within recording",
43
+ className: e("block w-full rounded-md focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-accent)]", C ? "cursor-not-allowed opacity-60" : "cursor-pointer"),
44
+ children: /* @__PURE__ */ s(t, {
45
+ seed: d,
46
+ bars: 56,
47
+ progress: w
48
+ })
49
+ }), /* @__PURE__ */ c("div", {
50
+ className: "mt-1 flex items-center justify-between text-xs tabular-nums text-text-tertiary",
51
+ children: [/* @__PURE__ */ s("span", { children: l(_) }), /* @__PURE__ */ s("span", { children: y > 0 ? l(y) : "—" })]
52
+ })]
53
+ }),
54
+ u && /* @__PURE__ */ s("audio", {
55
+ ref: m,
56
+ src: u,
57
+ preload: "metadata",
58
+ onPlay: () => g(!0),
59
+ onPause: () => g(!1),
60
+ onEnded: () => {
61
+ g(!1), v(0);
62
+ },
63
+ onTimeUpdate: (e) => v(e.currentTarget.currentTime),
64
+ onLoadedMetadata: (e) => {
65
+ Number.isFinite(e.currentTarget.duration) && b(e.currentTarget.duration);
66
+ }
67
+ })
68
+ ]
69
+ });
70
+ }
71
+ //#endregion
72
+ export { u as AudioPlayer };
73
+
74
+ //# sourceMappingURL=AudioPlayer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AudioPlayer.js","names":[],"sources":["../../../src/theculturepost/components/AudioPlayer.tsx"],"sourcesContent":["/**\n * AudioPlayer — a token-only HTML5 <audio> player for oral-history recordings.\n * @module theculturepost/components\n *\n * fe-libs ships no audio player, so this wraps a native <audio> element with a\n * play/pause control, the deterministic {@link Waveform} as a clickable seek bar\n * (click position → seek), and current / duration timers. Degrades gracefully\n * when `src` is null (renders a muted, disabled shell). No external audio decode\n * — the waveform is decorative and driven by real playback progress.\n */\n\nimport { useCallback, useRef, useState } from 'react';\nimport { Play, Pause } from 'lucide-react';\nimport { cn } from '../utils/cn';\nimport { Waveform } from './Waveform';\n\n/** Clock timer format (m:ss / h:mm:ss) for the player read-outs. */\nfunction clock(totalSeconds: number): string {\n if (!Number.isFinite(totalSeconds) || totalSeconds < 0) return '0:00';\n const secs = Math.floor(totalSeconds);\n const h = Math.floor(secs / 3600);\n const m = Math.floor((secs % 3600) / 60);\n const s = secs % 60;\n const mm = h > 0 ? String(m).padStart(2, '0') : String(m);\n const ss = String(s).padStart(2, '0');\n return h > 0 ? `${h}:${mm}:${ss}` : `${mm}:${ss}`;\n}\n\ninterface AudioPlayerProps {\n /** Resolved playable URL, or null when the recording is unavailable. */\n src?: string | null;\n /** Stable seed so the waveform shape is stable for a given recording. */\n seed: string;\n /** Fallback duration (secs) shown before metadata loads. */\n durationSecs?: number;\n className?: string;\n}\n\nexport function AudioPlayer({ src, seed, durationSecs = 0, className }: AudioPlayerProps) {\n const audioRef = useRef<HTMLAudioElement | null>(null);\n const [isPlaying, setIsPlaying] = useState(false);\n const [current, setCurrent] = useState(0);\n const [duration, setDuration] = useState(durationSecs);\n\n // Reset playback state when the source changes — React's documented\n // \"store info from previous renders\" pattern (adjust state during render,\n // no setState-in-effect, no ref mutation during render).\n const [trackedSrc, setTrackedSrc] = useState(src);\n if (trackedSrc !== src) {\n setTrackedSrc(src);\n setIsPlaying(false);\n setCurrent(0);\n setDuration(durationSecs);\n }\n\n const disabled = !src;\n const progress = duration > 0 ? Math.min(1, current / duration) : 0;\n\n const toggle = useCallback(() => {\n const el = audioRef.current;\n if (!el || disabled) return;\n if (el.paused) {\n void el.play();\n } else {\n el.pause();\n }\n }, [disabled]);\n\n const seekToFraction = useCallback((fraction: number) => {\n const el = audioRef.current;\n if (!el || !Number.isFinite(el.duration) || el.duration <= 0) return;\n el.currentTime = Math.max(0, Math.min(1, fraction)) * el.duration;\n setCurrent(el.currentTime);\n }, []);\n\n const onWaveClick = useCallback(\n (event: React.MouseEvent<HTMLButtonElement>) => {\n const rect = event.currentTarget.getBoundingClientRect();\n if (rect.width <= 0) return;\n seekToFraction((event.clientX - rect.left) / rect.width);\n },\n [seekToFraction]\n );\n\n return (\n <div\n className={cn(\n 'flex items-center gap-3 rounded-xl border border-border-seam bg-bg-surface p-3',\n className\n )}\n >\n <button\n type=\"button\"\n onClick={toggle}\n disabled={disabled}\n aria-label={isPlaying ? 'Pause recording' : 'Play recording'}\n className={cn(\n 'flex h-11 w-11 shrink-0 items-center justify-center rounded-full transition-colors',\n disabled\n ? 'cursor-not-allowed bg-bg-sunken text-text-tertiary'\n : 'bg-action-primary-bg text-action-primary-text hover:opacity-90'\n )}\n >\n {isPlaying ? (\n <Pause className=\"h-5 w-5 fill-current\" />\n ) : (\n <Play className=\"h-5 w-5 fill-current\" />\n )}\n </button>\n\n <div className=\"min-w-0 flex-1\">\n <button\n type=\"button\"\n onClick={onWaveClick}\n disabled={disabled}\n aria-label=\"Seek within recording\"\n className={cn(\n 'block w-full rounded-md focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-accent)]',\n disabled ? 'cursor-not-allowed opacity-60' : 'cursor-pointer'\n )}\n >\n <Waveform seed={seed} bars={56} progress={progress} />\n </button>\n <div className=\"mt-1 flex items-center justify-between text-xs tabular-nums text-text-tertiary\">\n <span>{clock(current)}</span>\n <span>{duration > 0 ? clock(duration) : '—'}</span>\n </div>\n </div>\n\n {src && (\n <audio\n ref={audioRef}\n src={src}\n preload=\"metadata\"\n onPlay={() => setIsPlaying(true)}\n onPause={() => setIsPlaying(false)}\n onEnded={() => {\n setIsPlaying(false);\n setCurrent(0);\n }}\n onTimeUpdate={(e) => setCurrent(e.currentTarget.currentTime)}\n onLoadedMetadata={(e) => {\n if (Number.isFinite(e.currentTarget.duration)) {\n setDuration(e.currentTarget.duration);\n }\n }}\n />\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;AAiBA,SAAS,EAAM,GAA8B;AAC3C,KAAI,CAAC,OAAO,SAAS,EAAa,IAAI,IAAe,EAAG,QAAO;CAC/D,IAAM,IAAO,KAAK,MAAM,EAAa,EAC/B,IAAI,KAAK,MAAM,IAAO,KAAK,EAC3B,IAAI,KAAK,MAAO,IAAO,OAAQ,GAAG,EAClC,IAAI,IAAO,IACX,IAAK,IAAI,IAAI,OAAO,EAAE,CAAC,SAAS,GAAG,IAAI,GAAG,OAAO,EAAE,EACnD,IAAK,OAAO,EAAE,CAAC,SAAS,GAAG,IAAI;AACrC,QAAO,IAAI,IAAI,GAAG,EAAE,GAAG,EAAG,GAAG,MAAO,GAAG,EAAG,GAAG;;AAa/C,SAAgB,EAAY,EAAE,QAAK,SAAM,kBAAe,GAAG,gBAA+B;CACxF,IAAM,IAAW,EAAgC,KAAK,EAChD,CAAC,GAAW,KAAgB,EAAS,GAAM,EAC3C,CAAC,GAAS,KAAc,EAAS,EAAE,EACnC,CAAC,GAAU,KAAe,EAAS,EAAa,EAKhD,CAAC,GAAY,KAAiB,EAAS,EAAI;AACjD,CAAI,MAAe,MACjB,EAAc,EAAI,EAClB,EAAa,GAAM,EACnB,EAAW,EAAE,EACb,EAAY,EAAa;CAG3B,IAAM,IAAW,CAAC,GACZ,IAAW,IAAW,IAAI,KAAK,IAAI,GAAG,IAAU,EAAS,GAAG,GAE5D,IAAS,QAAkB;EAC/B,IAAM,IAAK,EAAS;AAChB,GAAC,KAAM,MACP,EAAG,SACA,EAAG,MAAM,GAEd,EAAG,OAAO;IAEX,CAAC,EAAS,CAAC,EAER,IAAiB,GAAa,MAAqB;EACvD,IAAM,IAAK,EAAS;AAChB,GAAC,KAAM,CAAC,OAAO,SAAS,EAAG,SAAS,IAAI,EAAG,YAAY,MAC3D,EAAG,cAAc,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,EAAS,CAAC,GAAG,EAAG,UACzD,EAAW,EAAG,YAAY;IACzB,EAAE,CAAC,EAEA,IAAc,GACjB,MAA+C;EAC9C,IAAM,IAAO,EAAM,cAAc,uBAAuB;AACpD,IAAK,SAAS,KAClB,GAAgB,EAAM,UAAU,EAAK,QAAQ,EAAK,MAAM;IAE1D,CAAC,EAAe,CACjB;AAED,QACE,kBAAC,OAAD;EACE,WAAW,EACT,kFACA,EACD;YAJH;GAME,kBAAC,UAAD;IACE,MAAK;IACL,SAAS;IACC;IACV,cAAY,IAAY,oBAAoB;IAC5C,WAAW,EACT,sFACA,IACI,uDACA,iEACL;cAGC,EADD,IACE,IAEA,GAFD,EAAO,WAAU,wBAAyB,CAED;IAEpC,CAAA;GAET,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACC;KACV,cAAW;KACX,WAAW,EACT,oJACA,IAAW,kCAAkC,iBAC9C;eAED,kBAAC,GAAD;MAAgB;MAAM,MAAM;MAAc;MAAY,CAAA;KAC/C,CAAA,EACT,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD,EAAA,UAAO,EAAM,EAAQ,EAAQ,CAAA,EAC7B,kBAAC,QAAD,EAAA,UAAO,IAAW,IAAI,EAAM,EAAS,GAAG,KAAW,CAAA,CAC/C;OACF;;GAEL,KACC,kBAAC,SAAD;IACE,KAAK;IACA;IACL,SAAQ;IACR,cAAc,EAAa,GAAK;IAChC,eAAe,EAAa,GAAM;IAClC,eAAe;AAEb,KADA,EAAa,GAAM,EACnB,EAAW,EAAE;;IAEf,eAAe,MAAM,EAAW,EAAE,cAAc,YAAY;IAC5D,mBAAmB,MAAM;AACvB,KAAI,OAAO,SAAS,EAAE,cAAc,SAAS,IAC3C,EAAY,EAAE,cAAc,SAAS;;IAGzC,CAAA;GAEA"}