@edoxen/browser 0.1.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 (69) hide show
  1. package/README.md +45 -0
  2. package/dist/cli.js +4475 -0
  3. package/package.json +82 -0
  4. package/src/astro/components/ActionTypeBadge.astro +28 -0
  5. package/src/astro/components/AgendaTable.astro +49 -0
  6. package/src/astro/components/BodyBadge.astro +28 -0
  7. package/src/astro/components/CommitteeCard.astro +58 -0
  8. package/src/astro/components/CountryFlag.astro +34 -0
  9. package/src/astro/components/DecadeTimeline.astro +52 -0
  10. package/src/astro/components/DecisionList.astro +66 -0
  11. package/src/astro/components/EmptyState.astro +19 -0
  12. package/src/astro/components/LocaleTabs.astro +44 -0
  13. package/src/astro/components/MeetingList.astro +63 -0
  14. package/src/astro/components/MinutesSection.astro +59 -0
  15. package/src/astro/components/OfficerList.astro +49 -0
  16. package/src/astro/components/PageHero.astro +42 -0
  17. package/src/astro/components/PrevNextNav.astro +62 -0
  18. package/src/astro/components/ReferenceDocuments.astro +38 -0
  19. package/src/astro/components/UrnBar.astro +49 -0
  20. package/src/astro/layouts/BaseLayout.astro +79 -0
  21. package/src/astro/layouts/DecisionLayout.astro +21 -0
  22. package/src/astro/layouts/MeetingLayout.astro +21 -0
  23. package/src/astro/pages/404.astro +10 -0
  24. package/src/astro/pages/[lang]/about.astro +65 -0
  25. package/src/astro/pages/[lang]/decisions/[urn].astro +126 -0
  26. package/src/astro/pages/[lang]/decisions/index.astro +27 -0
  27. package/src/astro/pages/[lang]/index.astro +50 -0
  28. package/src/astro/pages/[lang]/meetings/[urn].astro +114 -0
  29. package/src/astro/pages/[lang]/meetings/index.astro +27 -0
  30. package/src/astro/pages/about.astro +65 -0
  31. package/src/astro/pages/decisions/[urn].astro +122 -0
  32. package/src/astro/pages/decisions/index.astro +67 -0
  33. package/src/astro/pages/index.astro +50 -0
  34. package/src/astro/pages/meetings/[urn].astro +110 -0
  35. package/src/astro/pages/meetings/index.astro +20 -0
  36. package/src/cli/index.ts +195 -0
  37. package/src/config/index.ts +33 -0
  38. package/src/config/paths.spec.ts +59 -0
  39. package/src/config/paths.ts +26 -0
  40. package/src/config/schema.spec.ts +263 -0
  41. package/src/config/schema.ts +132 -0
  42. package/src/config/tokens.spec.ts +57 -0
  43. package/src/config/tokens.ts +48 -0
  44. package/src/data/index.ts +36 -0
  45. package/src/data/lint.spec.ts +79 -0
  46. package/src/data/lint.ts +77 -0
  47. package/src/data/load.spec.ts +58 -0
  48. package/src/data/load.ts +50 -0
  49. package/src/data/prepare.spec.ts +137 -0
  50. package/src/data/prepare.ts +199 -0
  51. package/src/data/project.ts +11 -0
  52. package/src/data/validate.ts +38 -0
  53. package/src/errors.ts +25 -0
  54. package/src/i18n/index.spec.ts +121 -0
  55. package/src/i18n/index.ts +90 -0
  56. package/src/index.ts +74 -0
  57. package/src/integration.ts +233 -0
  58. package/src/islands/decade-scroller.ts +18 -0
  59. package/src/islands/print-button.ts +12 -0
  60. package/src/islands/search-filter-core.spec.ts +84 -0
  61. package/src/islands/search-filter-core.ts +89 -0
  62. package/src/islands/search-filter.ts +166 -0
  63. package/src/islands/section-tabs.ts +35 -0
  64. package/src/islands/theme-toggle.ts +33 -0
  65. package/src/islands/urn-copy.ts +29 -0
  66. package/src/seo/index.spec.ts +76 -0
  67. package/src/seo/index.ts +85 -0
  68. package/src/virtual.d.ts +11 -0
  69. package/styles/base.css +117 -0
package/package.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "@edoxen/browser",
3
+ "version": "0.1.0",
4
+ "description": "Astro-based browser for edoxen YAML data — drop in your data and ship a resolutions archive site.",
5
+ "type": "module",
6
+ "license": "BSD-2-Clause",
7
+ "author": "Edoxen Project contributors",
8
+ "homepage": "https://github.com/edoxen/browser",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/edoxen/browser.git",
12
+ "directory": "packages/browser"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/edoxen/browser/issues"
16
+ },
17
+ "keywords": [
18
+ "astro",
19
+ "edoxen",
20
+ "resolutions",
21
+ "standards",
22
+ "iso",
23
+ "oiml"
24
+ ],
25
+ "sideEffects": false,
26
+ "bin": {
27
+ "edoxen-browser": "./dist/cli.js"
28
+ },
29
+ "exports": {
30
+ ".": "./src/index.ts",
31
+ "./config": "./src/config/index.ts",
32
+ "./data": "./src/data/index.ts",
33
+ "./seo": "./src/seo/index.ts",
34
+ "./i18n": "./src/i18n/index.ts",
35
+ "./integration": "./src/integration.ts",
36
+ "./package.json": "./package.json"
37
+ },
38
+ "main": "./src/index.ts",
39
+ "types": "./src/index.ts",
40
+ "files": [
41
+ "src/",
42
+ "styles/",
43
+ "dist/cli.js",
44
+ "README.md",
45
+ "LICENSE"
46
+ ],
47
+ "scripts": {
48
+ "dev": "astro dev",
49
+ "build": "astro build",
50
+ "build:cli": "tsup",
51
+ "prepublishOnly": "pnpm build:cli",
52
+ "test": "vitest run",
53
+ "test:watch": "vitest",
54
+ "typecheck": "tsc --noEmit"
55
+ },
56
+ "peerDependencies": {
57
+ "@edoxen/edoxen": "^0.2.0",
58
+ "ajv": "^8.17.0",
59
+ "astro": "^4.0.0 || ^5.0.0"
60
+ },
61
+ "devDependencies": {
62
+ "@edoxen/edoxen": "link:../../../edoxen-js/packages/edoxen",
63
+ "@types/node": "^22.10.0",
64
+ "ajv": "^8.17.0",
65
+ "astro": "^5.0.0",
66
+ "tsup": "^8.3.5",
67
+ "typescript": "^5.6.3",
68
+ "vitest": "^2.1.0",
69
+ "yaml": "^2.6.0",
70
+ "zod": "^3.23.8"
71
+ },
72
+ "publishConfig": {
73
+ "access": "public"
74
+ },
75
+ "engines": {
76
+ "node": ">=20"
77
+ },
78
+ "dependencies": {
79
+ "@astrojs/sitemap": "^3.7.3",
80
+ "jiti": "^2.4.0"
81
+ }
82
+ }
@@ -0,0 +1,28 @@
1
+ ---
2
+ import { actionTypeColor, actionTypeLabel } from '@edoxen/edoxen'
3
+
4
+ interface Props {
5
+ kind: string
6
+ }
7
+ const { kind } = Astro.props
8
+ const color = actionTypeColor(kind)
9
+ const label = actionTypeLabel(kind)
10
+ const style = color ? `background:${color};color:#fff` : undefined
11
+ ---
12
+
13
+ {kind && (
14
+ <span class="edoxen-action-badge" style={style}>{label}</span>
15
+ )}
16
+
17
+ <style>
18
+ .edoxen-action-badge {
19
+ display: inline-block;
20
+ padding: 0.15rem 0.55rem;
21
+ border-radius: var(--edoxen-radius-sm);
22
+ font-size: 0.8rem;
23
+ font-weight: 500;
24
+ background: var(--edoxen-color-muted);
25
+ color: #fff;
26
+ text-transform: capitalize;
27
+ }
28
+ </style>
@@ -0,0 +1,49 @@
1
+ ---
2
+ import type { AgendaItem } from '@edoxen/edoxen'
3
+ import { pickLocalizedValue } from '../../i18n/index.js'
4
+
5
+ interface Props {
6
+ items: readonly AgendaItem[]
7
+ locale: string
8
+ }
9
+ const { items, locale } = Astro.props
10
+ ---
11
+
12
+ <table class="edoxen-agenda">
13
+ <thead>
14
+ <tr><th scope="col">#</th><th scope="col">Title</th><th scope="col">Outcome</th></tr>
15
+ </thead>
16
+ <tbody>
17
+ {items.map((item) => (
18
+ <tr>
19
+ <td><code>{item.label}</code></td>
20
+ <td>{pickLocalizedValue(item.title, locale, item.label ?? '')}</td>
21
+ <td>{item.outcome ?? ''}</td>
22
+ </tr>
23
+ ))}
24
+ </tbody>
25
+ </table>
26
+
27
+ <style>
28
+ .edoxen-agenda {
29
+ width: 100%;
30
+ border-collapse: collapse;
31
+ font-size: 0.95rem;
32
+ }
33
+ .edoxen-agenda th,
34
+ .edoxen-agenda td {
35
+ text-align: left;
36
+ padding: 0.5rem 0.75rem;
37
+ border-bottom: 1px solid var(--edoxen-color-border);
38
+ }
39
+ .edoxen-agenda th {
40
+ font-weight: 600;
41
+ color: var(--edoxen-color-muted);
42
+ text-transform: uppercase;
43
+ font-size: 0.75rem;
44
+ letter-spacing: 0.05em;
45
+ }
46
+ .edoxen-agenda code {
47
+ font-family: var(--edoxen-font-mono, ui-monospace, monospace);
48
+ }
49
+ </style>
@@ -0,0 +1,28 @@
1
+ ---
2
+ import cfg from 'virtual:edoxen-config'
3
+
4
+ interface Props {
5
+ code?: string
6
+ }
7
+ const { code } = Astro.props
8
+ const body = code ? cfg.bodies.find((b) => b.code === code) : undefined
9
+ const style = body?.color
10
+ ? `background:${body.color};color:${body.textColor ?? '#ffffff'}`
11
+ : undefined
12
+ ---
13
+
14
+ {code && (
15
+ <span class="edoxen-badge" style={style}>{body?.name ?? code}</span>
16
+ )}
17
+
18
+ <style>
19
+ .edoxen-badge {
20
+ display: inline-block;
21
+ padding: 0.15rem 0.55rem;
22
+ border-radius: var(--edoxen-radius-sm);
23
+ font-size: 0.8rem;
24
+ font-weight: 500;
25
+ background: var(--edoxen-color-muted);
26
+ color: #fff;
27
+ }
28
+ </style>
@@ -0,0 +1,58 @@
1
+ ---
2
+ import type { MeetingSeries } from '@edoxen/edoxen'
3
+ import { pickLocalizedValue } from '../../i18n/index.js'
4
+
5
+ interface Props {
6
+ committee?: MeetingSeries | null
7
+ locale: string
8
+ }
9
+ const { committee, locale } = Astro.props
10
+ const name = committee ? pickLocalizedValue(committee.name, locale, committee.urn ?? 'Committee') : ''
11
+ const description = committee ? pickLocalizedValue(committee.description, locale) : ''
12
+ const identifiers = committee?.identifier ?? []
13
+ ---
14
+
15
+ {committee && (
16
+ <article class="edoxen-committee-card">
17
+ {name && <h2 class="edoxen-committee-card__name">{name}</h2>}
18
+ {description && <p class="edoxen-committee-card__desc">{description}</p>}
19
+ {identifiers.length > 0 && (
20
+ <dl class="edoxen-committee-card__ids">
21
+ {identifiers.map((id) => (
22
+ <>
23
+ <dt>{id.prefix}</dt>
24
+ <dd>{id.number}</dd>
25
+ </>
26
+ ))}
27
+ </dl>
28
+ )}
29
+ </article>
30
+ )}
31
+
32
+ <style>
33
+ .edoxen-committee-card {
34
+ border: 1px solid var(--edoxen-color-border);
35
+ border-radius: var(--edoxen-radius-sm);
36
+ background: var(--edoxen-color-surface);
37
+ padding: 1rem 1.25rem;
38
+ }
39
+ .edoxen-committee-card__name {
40
+ margin: 0 0 0.5rem;
41
+ font-size: 1.1rem;
42
+ }
43
+ .edoxen-committee-card__desc {
44
+ margin: 0 0 0.75rem;
45
+ color: var(--edoxen-color-text);
46
+ }
47
+ .edoxen-committee-card__ids {
48
+ display: grid;
49
+ grid-template-columns: auto 1fr;
50
+ gap: 0.25rem 0.75rem;
51
+ margin: 0;
52
+ font-size: 0.85rem;
53
+ color: var(--edoxen-color-muted);
54
+ }
55
+ .edoxen-committee-card__ids dt {
56
+ font-weight: 500;
57
+ }
58
+ </style>
@@ -0,0 +1,34 @@
1
+ ---
2
+ import { countryFlag } from '@edoxen/edoxen'
3
+
4
+ interface Props {
5
+ countryCode?: string
6
+ city?: string
7
+ }
8
+ const { countryCode, city } = Astro.props
9
+ const flag = countryCode ? countryFlag(countryCode) : ''
10
+ ---
11
+
12
+ {(city || countryCode) && (
13
+ <span class="edoxen-venue">
14
+ {flag && <span class="edoxen-venue__flag" aria-hidden="true">{flag}</span>}
15
+ {city && <span class="edoxen-venue__city">{city}</span>}
16
+ {countryCode && <span class="edoxen-venue__code">{countryCode}</span>}
17
+ </span>
18
+ )}
19
+
20
+ <style>
21
+ .edoxen-venue {
22
+ display: inline-flex;
23
+ gap: 0.4rem;
24
+ align-items: center;
25
+ }
26
+ .edoxen-venue__flag {
27
+ font-size: 1.2em;
28
+ }
29
+ .edoxen-venue__code {
30
+ color: var(--edoxen-color-muted);
31
+ font-size: 0.85em;
32
+ font-family: var(--edoxen-font-mono, ui-monospace, monospace);
33
+ }
34
+ </style>
@@ -0,0 +1,52 @@
1
+ ---
2
+ interface Props {
3
+ decades: readonly number[]
4
+ active?: number
5
+ }
6
+ const { decades, active } = Astro.props
7
+ const sorted = [...decades].sort((a, b) => b - a)
8
+ ---
9
+
10
+ {sorted.length > 0 && (
11
+ <nav class="edoxen-decade-timeline" aria-label="Browse by decade">
12
+ {sorted.map((d) => (
13
+ <a
14
+ href={`#decade-${d}`}
15
+ class={`edoxen-decade-timeline__link${active === d ? ' edoxen-decade-timeline__link--active' : ''}`}
16
+ >
17
+ <span class="edoxen-decade-timeline__decade">{d}s</span>
18
+ </a>
19
+ ))}
20
+ </nav>
21
+ )}
22
+
23
+ <style>
24
+ .edoxen-decade-timeline {
25
+ display: flex;
26
+ flex-wrap: wrap;
27
+ gap: 0.5rem;
28
+ margin-bottom: 1.5rem;
29
+ font-size: 0.85rem;
30
+ }
31
+ .edoxen-decade-timeline__link {
32
+ padding: 0.3rem 0.7rem;
33
+ border: 1px solid var(--edoxen-color-border);
34
+ border-radius: var(--edoxen-radius-sm);
35
+ text-decoration: none;
36
+ color: var(--edoxen-color-text);
37
+ background: var(--edoxen-color-surface);
38
+ }
39
+ .edoxen-decade-timeline__link:hover {
40
+ background: var(--edoxen-color-accent);
41
+ color: #fff;
42
+ border-color: var(--edoxen-color-accent);
43
+ }
44
+ .edoxen-decade-timeline__link--active {
45
+ background: var(--edoxen-color-accent);
46
+ color: #fff;
47
+ border-color: var(--edoxen-color-accent);
48
+ }
49
+ .edoxen-decade-timeline__decade {
50
+ font-weight: 500;
51
+ }
52
+ </style>
@@ -0,0 +1,66 @@
1
+ ---
2
+ import type { DecisionListItem } from '../../data/index.js'
3
+
4
+ interface Props {
5
+ items: readonly DecisionListItem[]
6
+ compact?: boolean
7
+ }
8
+ const { items, compact = false } = Astro.props
9
+ const listClass = compact ? 'edoxen-decision-list edoxen-decision-list--compact' : 'edoxen-decision-list'
10
+ ---
11
+
12
+ {items.length === 0 ? (
13
+ <p class="edoxen-empty">No decisions.</p>
14
+ ) : (
15
+ <ul class={listClass}>
16
+ {items.map((d) => (
17
+ <li class="edoxen-decision-list__item">
18
+ <a href={`/decisions/${encodeURIComponent(d.urn)}`}>{d.title}</a>
19
+ {!compact && (
20
+ <dl class="edoxen-decision-list__meta">
21
+ <dt>URN</dt>
22
+ <dd><code>{d.urn}</code></dd>
23
+ {d.bodyType && <><dt>Body</dt><dd>{d.bodyType}</dd></>}
24
+ {d.kind && <><dt>Kind</dt><dd>{d.kind}</dd></>}
25
+ {d.dates[0]?.date && <><dt>Date</dt><dd>{d.dates[0].date}</dd></>}
26
+ </dl>
27
+ )}
28
+ </li>
29
+ ))}
30
+ </ul>
31
+ )}
32
+
33
+ <style>
34
+ .edoxen-decision-list {
35
+ list-style: none;
36
+ padding: 0;
37
+ margin: 0;
38
+ display: flex;
39
+ flex-direction: column;
40
+ gap: 0.75rem;
41
+ }
42
+ .edoxen-decision-list__item {
43
+ padding: 0.75rem 1rem;
44
+ border: 1px solid var(--edoxen-color-border);
45
+ border-radius: var(--edoxen-radius-sm);
46
+ background: var(--edoxen-color-surface);
47
+ }
48
+ .edoxen-decision-list__item a {
49
+ font-weight: 500;
50
+ color: var(--edoxen-color-text);
51
+ }
52
+ .edoxen-decision-list__meta {
53
+ display: grid;
54
+ grid-template-columns: auto 1fr;
55
+ gap: 0.25rem 1rem;
56
+ margin: 0.5rem 0 0;
57
+ font-size: 0.85rem;
58
+ color: var(--edoxen-color-muted);
59
+ }
60
+ .edoxen-decision-list__meta dt {
61
+ font-weight: 500;
62
+ }
63
+ .edoxen-decision-list--compact .edoxen-decision-list__item {
64
+ padding: 0.5rem 0.75rem;
65
+ }
66
+ </style>
@@ -0,0 +1,19 @@
1
+ ---
2
+ interface Props {
3
+ message?: string
4
+ }
5
+ const { message = 'Nothing here.' } = Astro.props
6
+ ---
7
+
8
+ <p class="edoxen-empty-state">{message}</p>
9
+
10
+ <style>
11
+ .edoxen-empty-state {
12
+ padding: 2rem 1rem;
13
+ text-align: center;
14
+ color: var(--edoxen-color-muted);
15
+ border: 1px dashed var(--edoxen-color-border);
16
+ border-radius: var(--edoxen-radius-sm);
17
+ background: var(--edoxen-color-surface);
18
+ }
19
+ </style>
@@ -0,0 +1,44 @@
1
+ ---
2
+ interface Props {
3
+ spellings: readonly string[]
4
+ active: string
5
+ field: string
6
+ }
7
+ const { spellings, active, field } = Astro.props
8
+ ---
9
+
10
+ {spellings.length > 1 && (
11
+ <nav class="edoxen-locale-tabs" aria-label={`Available languages for ${field}`} data-field={field}>
12
+ {spellings.map((sp) => (
13
+ <button type="button" class="edoxen-locale-tabs__tab" data-spelling={sp} aria-pressed={sp === active}>
14
+ {sp}
15
+ </button>
16
+ ))}
17
+ </nav>
18
+ )}
19
+
20
+ <style>
21
+ .edoxen-locale-tabs {
22
+ display: inline-flex;
23
+ gap: 0.25rem;
24
+ margin: 0.5rem 0 1rem;
25
+ padding: 0.25rem;
26
+ border: 1px solid var(--edoxen-color-border);
27
+ border-radius: var(--edoxen-radius-sm);
28
+ background: var(--edoxen-color-surface);
29
+ }
30
+ .edoxen-locale-tabs__tab {
31
+ border: 0;
32
+ background: transparent;
33
+ color: var(--edoxen-color-muted);
34
+ padding: 0.25rem 0.6rem;
35
+ border-radius: calc(var(--edoxen-radius-sm) * 0.75);
36
+ cursor: pointer;
37
+ font-size: 0.8rem;
38
+ font-family: var(--edoxen-font-mono, ui-monospace, monospace);
39
+ }
40
+ .edoxen-locale-tabs__tab[aria-pressed='true'] {
41
+ background: var(--edoxen-color-accent);
42
+ color: #fff;
43
+ }
44
+ </style>
@@ -0,0 +1,63 @@
1
+ ---
2
+ import type { MeetingListItem } from '../../data/index.js'
3
+
4
+ interface Props {
5
+ items: readonly MeetingListItem[]
6
+ compact?: boolean
7
+ }
8
+ const { items, compact = false } = Astro.props
9
+ const listClass = compact ? 'edoxen-meeting-list edoxen-meeting-list--compact' : 'edoxen-meeting-list'
10
+ ---
11
+
12
+ {items.length === 0 ? (
13
+ <p class="edoxen-empty">No meetings.</p>
14
+ ) : (
15
+ <ul class={listClass}>
16
+ {items.map((m) => (
17
+ <li class="edoxen-meeting-list__item">
18
+ <a href={`/meetings/${encodeURIComponent(m.urn)}`}>{m.title}</a>
19
+ {!compact && (
20
+ <dl class="edoxen-meeting-list__meta">
21
+ <dt>URN</dt>
22
+ <dd><code>{m.urn}</code></dd>
23
+ {m.startDate && <><dt>Start</dt><dd>{m.startDate}</dd></>}
24
+ {m.city && <><dt>Venue</dt><dd>{m.city}{m.countryCode && ` (${m.countryCode})`}</dd></>}
25
+ {m.bodyType && <><dt>Body</dt><dd>{m.bodyType}</dd></>}
26
+ </dl>
27
+ )}
28
+ </li>
29
+ ))}
30
+ </ul>
31
+ )}
32
+
33
+ <style>
34
+ .edoxen-meeting-list {
35
+ list-style: none;
36
+ padding: 0;
37
+ margin: 0;
38
+ display: flex;
39
+ flex-direction: column;
40
+ gap: 0.75rem;
41
+ }
42
+ .edoxen-meeting-list__item {
43
+ padding: 0.75rem 1rem;
44
+ border: 1px solid var(--edoxen-color-border);
45
+ border-radius: var(--edoxen-radius-sm);
46
+ background: var(--edoxen-color-surface);
47
+ }
48
+ .edoxen-meeting-list__item a {
49
+ font-weight: 500;
50
+ color: var(--edoxen-color-text);
51
+ }
52
+ .edoxen-meeting-list__meta {
53
+ display: grid;
54
+ grid-template-columns: auto 1fr;
55
+ gap: 0.25rem 1rem;
56
+ margin: 0.5rem 0 0;
57
+ font-size: 0.85rem;
58
+ color: var(--edoxen-color-muted);
59
+ }
60
+ .edoxen-meeting-list__meta dt {
61
+ font-weight: 500;
62
+ }
63
+ </style>
@@ -0,0 +1,59 @@
1
+ ---
2
+ import type { Minutes} from '@edoxen/edoxen'
3
+ import { pickLocalizedValue } from '../../i18n/index.js'
4
+
5
+ interface Props {
6
+ minutes: Minutes
7
+ locale: string
8
+ }
9
+ const { minutes, locale } = Astro.props
10
+ const sections = minutes.sections ?? []
11
+ const spelling = minutes.spelling ?? locale
12
+ ---
13
+
14
+ <article class="edoxen-minutes" data-spelling={spelling}>
15
+ <header>
16
+ <h3>Minutes{spelling ? ` — ${spelling}` : ''}</h3>
17
+ {minutes.urn && <p><code>{minutes.urn}</code></p>}
18
+ </header>
19
+ {sections.map((section) => (
20
+ <section class="edoxen-minutes__section">
21
+ <h4>{section.number}. {pickLocalizedValue(section.title, locale, '(untitled)')}</h4>
22
+ <div class="edoxen-minutes__narrative">
23
+ {pickLocalizedValue(section.narrative, locale)}
24
+ </div>
25
+ </section>
26
+ ))}
27
+ </article>
28
+
29
+ <style>
30
+ .edoxen-minutes {
31
+ border: 1px solid var(--edoxen-color-border);
32
+ border-radius: var(--edoxen-radius-sm);
33
+ padding: 1rem 1.25rem;
34
+ background: var(--edoxen-color-surface);
35
+ margin-bottom: 1rem;
36
+ }
37
+ .edoxen-minutes h3 {
38
+ margin: 0 0 0.5rem;
39
+ font-size: 1.05rem;
40
+ }
41
+ .edoxen-minutes__section {
42
+ margin-top: 1rem;
43
+ }
44
+ .edoxen-minutes__section h4 {
45
+ margin: 0 0 0.5rem;
46
+ font-size: 0.95rem;
47
+ font-weight: 600;
48
+ }
49
+ .edoxen-minutes__narrative {
50
+ font-size: 0.95rem;
51
+ line-height: 1.5;
52
+ white-space: pre-wrap;
53
+ color: var(--edoxen-color-text);
54
+ }
55
+ .edoxen-minutes code {
56
+ font-family: var(--edoxen-font-mono, ui-monospace, monospace);
57
+ font-size: 0.85rem;
58
+ }
59
+ </style>
@@ -0,0 +1,49 @@
1
+ ---
2
+ import type { Officer } from '@edoxen/edoxen'
3
+
4
+ interface Props {
5
+ officers: readonly Officer[]
6
+ }
7
+ const { officers } = Astro.props
8
+ ---
9
+
10
+ {officers.length > 0 && (
11
+ <ul class="edoxen-officer-list">
12
+ {officers.map((o) => (
13
+ <li class="edoxen-officer-list__item">
14
+ <span class="edoxen-officer-list__role">{o.role}</span>
15
+ {o.person && (
16
+ <span class="edoxen-officer-list__name">
17
+ {o.person.name?.map((n) => n.value).filter(Boolean).join(' ') || ''}
18
+ </span>
19
+ )}
20
+ </li>
21
+ ))}
22
+ </ul>
23
+ )}
24
+
25
+ <style>
26
+ .edoxen-officer-list {
27
+ list-style: none;
28
+ padding: 0;
29
+ margin: 0;
30
+ display: flex;
31
+ flex-direction: column;
32
+ gap: 0.4rem;
33
+ }
34
+ .edoxen-officer-list__item {
35
+ display: flex;
36
+ gap: 0.75rem;
37
+ padding: 0.4rem 0;
38
+ border-bottom: 1px solid var(--edoxen-color-border);
39
+ }
40
+ .edoxen-officer-list__role {
41
+ font-weight: 500;
42
+ text-transform: capitalize;
43
+ min-width: 8rem;
44
+ color: var(--edoxen-color-muted);
45
+ }
46
+ .edoxen-officer-list__name {
47
+ color: var(--edoxen-color-text);
48
+ }
49
+ </style>
@@ -0,0 +1,42 @@
1
+ ---
2
+ interface Props {
3
+ title: string
4
+ subtitle?: string
5
+ logo?: string
6
+ }
7
+ const { title, subtitle, logo } = Astro.props
8
+ ---
9
+
10
+ <header class="edoxen-page-hero">
11
+ {logo && <img src={logo} alt="" class="edoxen-page-hero__logo" />}
12
+ <div>
13
+ <h1 class="edoxen-page-hero__title">{title}</h1>
14
+ {subtitle && <p class="edoxen-page-hero__subtitle">{subtitle}</p>}
15
+ </div>
16
+ </header>
17
+
18
+ <style>
19
+ .edoxen-page-hero {
20
+ display: flex;
21
+ align-items: center;
22
+ gap: 1.5rem;
23
+ padding: 2rem 0;
24
+ margin-bottom: 1.5rem;
25
+ border-bottom: 1px solid var(--edoxen-color-border);
26
+ }
27
+ .edoxen-page-hero__logo {
28
+ max-height: 4rem;
29
+ width: auto;
30
+ }
31
+ .edoxen-page-hero__title {
32
+ margin: 0;
33
+ font-size: 2rem;
34
+ font-weight: 700;
35
+ letter-spacing: -0.01em;
36
+ }
37
+ .edoxen-page-hero__subtitle {
38
+ margin: 0.25rem 0 0;
39
+ color: var(--edoxen-color-muted);
40
+ font-size: 1.05rem;
41
+ }
42
+ </style>