@vit-foundation/ui 0.24.2 → 0.26.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 (82) hide show
  1. package/README.md +11 -11
  2. package/dist/community.d.ts +12 -3
  3. package/dist/community.js +7 -3
  4. package/dist/components/pages/GetInvolvedPage.svelte +139 -0
  5. package/dist/components/pages/GetInvolvedPage.svelte.d.ts +36 -0
  6. package/dist/components/pages/HomePage.svelte +256 -0
  7. package/dist/components/pages/HomePage.svelte.d.ts +36 -0
  8. package/dist/components/pages/LegalPage.svelte +39 -0
  9. package/dist/components/pages/LegalPage.svelte.d.ts +14 -0
  10. package/dist/components/pages/ProjectPage.svelte +122 -0
  11. package/dist/components/pages/ProjectPage.svelte.d.ts +15 -0
  12. package/dist/components/pages/TransparencyPage.svelte +164 -0
  13. package/dist/components/pages/TransparencyPage.svelte.d.ts +40 -0
  14. package/dist/components/pages/WeekliesPage.svelte +184 -0
  15. package/dist/components/pages/WeekliesPage.svelte.d.ts +46 -0
  16. package/dist/components/pages/WeeklyPage.svelte +198 -0
  17. package/dist/components/pages/WeeklyPage.svelte.d.ts +33 -0
  18. package/dist/components/pages/WhatWeDoPage.svelte +107 -0
  19. package/dist/components/pages/WhatWeDoPage.svelte.d.ts +27 -0
  20. package/dist/components/pages/WhoWeArePage.svelte +108 -0
  21. package/dist/components/pages/WhoWeArePage.svelte.d.ts +32 -0
  22. package/dist/components/ui/Modal.svelte +32 -11
  23. package/dist/components/ui/Modal.svelte.d.ts +17 -1
  24. package/dist/config/context.d.ts +0 -6
  25. package/dist/config/context.js +1 -1
  26. package/dist/config/index.d.ts +2 -2
  27. package/dist/config/index.js +1 -1
  28. package/dist/config/messages.js +22 -0
  29. package/dist/config/types.d.ts +36 -0
  30. package/dist/content/pages.d.ts +64 -0
  31. package/dist/content/pages.js +71 -0
  32. package/dist/content/types.d.ts +45 -0
  33. package/dist/content/types.js +0 -5
  34. package/dist/content-components.d.ts +22 -1
  35. package/dist/content-components.js +17 -1
  36. package/dist/contract.d.ts +11 -4
  37. package/dist/contract.js +11 -3
  38. package/dist/edit/Editable.svelte +17 -161
  39. package/dist/edit/Editable.svelte.d.ts +2 -50
  40. package/dist/edit/chrome/AddSlot.svelte +11 -78
  41. package/dist/edit/chrome/AddSlot.svelte.d.ts +2 -15
  42. package/dist/edit/chrome/EditFrame.svelte +15 -158
  43. package/dist/edit/chrome/EditFrame.svelte.d.ts +2 -31
  44. package/dist/edit/chrome/EditPanel.svelte +14 -22
  45. package/dist/edit/chrome/EditPanel.svelte.d.ts +2 -13
  46. package/dist/edit/chrome/LinkEdit.svelte +13 -277
  47. package/dist/edit/chrome/LinkEdit.svelte.d.ts +2 -48
  48. package/dist/edit/chrome-props.d.ts +96 -0
  49. package/dist/edit/chrome-props.js +1 -0
  50. package/dist/edit/context.d.ts +38 -5
  51. package/dist/edit/context.js +18 -7
  52. package/dist/edit/index.d.ts +6 -4
  53. package/dist/edit/index.js +4 -3
  54. package/dist/edit/live/AddSlot.svelte +83 -0
  55. package/dist/edit/live/AddSlot.svelte.d.ts +4 -0
  56. package/dist/edit/live/EditFrame.svelte +153 -0
  57. package/dist/edit/live/EditFrame.svelte.d.ts +4 -0
  58. package/dist/edit/live/EditPanel.svelte +25 -0
  59. package/dist/edit/live/EditPanel.svelte.d.ts +4 -0
  60. package/dist/edit/live/Editable.svelte +162 -0
  61. package/dist/edit/live/Editable.svelte.d.ts +4 -0
  62. package/dist/edit/live/LinkEdit.svelte +276 -0
  63. package/dist/edit/live/LinkEdit.svelte.d.ts +4 -0
  64. package/dist/edit/{chrome → live}/PropertyRow.svelte +1 -1
  65. package/dist/edit/{commit.svelte.d.ts → live/commit.svelte.d.ts} +2 -2
  66. package/dist/edit/live/index.d.ts +10 -0
  67. package/dist/edit/live/index.js +14 -0
  68. package/package.json +1 -1
  69. package/dist/components/account/index.d.ts +0 -3
  70. package/dist/components/account/index.js +0 -2
  71. package/dist/components/auth/index.d.ts +0 -7
  72. package/dist/components/auth/index.js +0 -4
  73. package/dist/components/contact/index.d.ts +0 -2
  74. package/dist/components/contact/index.js +0 -1
  75. package/dist/content/index.d.ts +0 -4
  76. package/dist/content/index.js +0 -2
  77. /package/dist/edit/{chrome → live}/ConfirmDialog.svelte +0 -0
  78. /package/dist/edit/{chrome → live}/ConfirmDialog.svelte.d.ts +0 -0
  79. /package/dist/edit/{chrome → live}/EditPopover.svelte +0 -0
  80. /package/dist/edit/{chrome → live}/EditPopover.svelte.d.ts +0 -0
  81. /package/dist/edit/{chrome → live}/PropertyRow.svelte.d.ts +0 -0
  82. /package/dist/edit/{commit.svelte.js → live/commit.svelte.js} +0 -0
@@ -0,0 +1,198 @@
1
+ <script lang="ts">
2
+ import { getUiConfig } from '../../config/context.js';
3
+ import type {
4
+ ArticleEdit,
5
+ CommentThreadData,
6
+ ReactionSummary,
7
+ WeeklyArticleData,
8
+ WeeklyCardData
9
+ } from '../../content/types.js';
10
+ import ActionLabel from '../../edit/ActionLabel.svelte';
11
+ import Editable from '../../edit/Editable.svelte';
12
+ import PageShell from '../layout/PageShell.svelte';
13
+ import DateText from '../ui/DateText.svelte';
14
+ import Link from '../ui/Link.svelte';
15
+ import RichText from '../ui/RichText.svelte';
16
+ import ShareRow from '../ui/ShareRow.svelte';
17
+ import CommentSection, { type CommentFormInstance } from '../weeklies/CommentSection.svelte';
18
+ import ReactionBar, { type ReactionBarForms } from '../weeklies/ReactionBar.svelte';
19
+ import WeeklieCard from '../weeklies/WeeklieCard.svelte';
20
+
21
+ /**
22
+ * A weekly's own page: number and date, title, excerpt, the square hero,
23
+ * the body, Instagram and sources, share, reactions, comments, and the
24
+ * related weeklies. The community forms arrive preflighted by the host —
25
+ * the website's remote forms, a CMS mirror's inert mocks — and
26
+ * `isLoggedIn` is the host's auth answer; those are the props the hosts
27
+ * fill differently. `related` is its own prop because vit-brain loads it
28
+ * apart from the weekly, where the website nests it.
29
+ */
30
+ interface Props {
31
+ weekly: WeeklyArticleData;
32
+ related: WeeklyCardData[];
33
+ comments: CommentThreadData[];
34
+ reactions: ReactionSummary[];
35
+ isLoggedIn: boolean;
36
+ /** The main comment box, preflighted; see CommentSection. */
37
+ commentForm: CommentFormInstance;
38
+ /** One preflighted instance per reply box; see CommentSection. */
39
+ replyFormFor: (threadId: string) => CommentFormInstance;
40
+ reactionForms: ReactionBarForms;
41
+ /**
42
+ * What a CMS may open on a weekly's own page: title, excerpt, and the body
43
+ * as rich text. The «Dades i fonts» and «Segueix explorant» headings and
44
+ * the back link edit through `config.messageEdit` over their own keys.
45
+ */
46
+ edit?: ArticleEdit;
47
+ }
48
+
49
+ let {
50
+ weekly,
51
+ related,
52
+ comments,
53
+ reactions,
54
+ isLoggedIn,
55
+ commentForm,
56
+ replyFormFor,
57
+ reactionForms,
58
+ edit
59
+ }: Props = $props();
60
+
61
+ const config = getUiConfig();
62
+ const msg = $derived(config.messages);
63
+ </script>
64
+
65
+ <PageShell title={weekly.title} description={weekly.excerpt} variant="reading">
66
+ <p class="back">
67
+ <ActionLabel edit={config.messageEdit?.('back_label')} value={msg.back_label()}>
68
+ {#snippet control()}
69
+ <Link href="/weeklies">← {msg.back_label()}</Link>
70
+ {/snippet}
71
+ </ActionLabel>
72
+ </p>
73
+
74
+ <header>
75
+ <p class="meta">
76
+ <span class="number">{msg.weeklie_number({ number: weekly.number })}</span>
77
+ <DateText value={weekly.publishedOn} />
78
+ </p>
79
+ <Editable edit={edit?.title} value={weekly.title}>
80
+ {#snippet children(text, attrs)}<h1 {...attrs}>{text}</h1>{/snippet}
81
+ </Editable>
82
+ <Editable edit={edit?.excerpt} value={weekly.excerpt}>
83
+ {#snippet children(text, attrs)}<p class="excerpt" {...attrs}>{text}</p>{/snippet}
84
+ </Editable>
85
+ </header>
86
+
87
+ <img class="hero" src={weekly.imageUrl} alt="" width="600" height="600" />
88
+
89
+ {#if weekly.body}
90
+ <RichText body={weekly.body} edit={edit?.body} />
91
+ {/if}
92
+
93
+ {#if weekly.instagramUrl}
94
+ <p><a href={weekly.instagramUrl} rel="external noopener">Instagram ↗</a></p>
95
+ {/if}
96
+
97
+ {#if weekly.sources.length > 0}
98
+ <section aria-labelledby="sources-heading">
99
+ <Editable edit={config.messageEdit?.('weeklie_sources')} value={msg.weeklie_sources()}>
100
+ {#snippet children(text, attrs)}
101
+ <h2 class="subsection-heading" id="sources-heading" {...attrs}>{text}</h2>
102
+ {/snippet}
103
+ </Editable>
104
+ <ul>
105
+ {#each weekly.sources as source (source.url)}
106
+ <li><a href={source.url} rel="external noopener">{source.label}</a></li>
107
+ {/each}
108
+ </ul>
109
+ </section>
110
+ {/if}
111
+
112
+ <ShareRow title={weekly.title} />
113
+
114
+ <ReactionBar
115
+ {reactions}
116
+ target={{ kind: 'weekly', slug: weekly.slug }}
117
+ {isLoggedIn}
118
+ forms={reactionForms}
119
+ />
120
+
121
+ <CommentSection
122
+ {comments}
123
+ weeklySlug={weekly.slug}
124
+ {isLoggedIn}
125
+ {commentForm}
126
+ {replyFormFor}
127
+ {reactionForms}
128
+ />
129
+
130
+ {#if related.length > 0}
131
+ <section aria-labelledby="related-heading">
132
+ <Editable
133
+ edit={config.messageEdit?.('weeklie_keepExploring')}
134
+ value={msg.weeklie_keepExploring()}
135
+ >
136
+ {#snippet children(text, attrs)}
137
+ <h2 class="subsection-heading" id="related-heading" {...attrs}>{text}</h2>
138
+ {/snippet}
139
+ </Editable>
140
+ <div class="related">
141
+ {#each related as card (card.slug)}
142
+ <WeeklieCard weekly={card} />
143
+ {/each}
144
+ </div>
145
+ </section>
146
+ {/if}
147
+ </PageShell>
148
+
149
+ <style>
150
+ .back :global(a) {
151
+ text-decoration: none;
152
+ color: var(--color-ink-secondary);
153
+ }
154
+
155
+ .meta {
156
+ display: flex;
157
+ gap: var(--space-3);
158
+ color: var(--color-ink-muted);
159
+ font-size: var(--text-sm);
160
+ margin: 0;
161
+ }
162
+
163
+ .number {
164
+ font-weight: 700;
165
+ color: var(--color-brand);
166
+ }
167
+
168
+ h1 {
169
+ margin: var(--space-2) 0;
170
+ }
171
+
172
+ .excerpt {
173
+ font-size: var(--text-lg);
174
+ color: var(--color-ink-secondary);
175
+ }
176
+
177
+ .hero {
178
+ border-radius: var(--radius-lg);
179
+ margin-block: var(--space-4);
180
+ background: var(--color-band-grey);
181
+ width: 100%;
182
+ max-width: 32rem;
183
+ }
184
+
185
+ ul {
186
+ padding-left: var(--space-4);
187
+ }
188
+
189
+ a {
190
+ color: var(--color-brand);
191
+ }
192
+
193
+ .related {
194
+ display: grid;
195
+ grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
196
+ gap: var(--space-4);
197
+ }
198
+ </style>
@@ -0,0 +1,33 @@
1
+ import type { ArticleEdit, CommentThreadData, ReactionSummary, WeeklyArticleData, WeeklyCardData } from '../../content/types.js';
2
+ import { type CommentFormInstance } from '../weeklies/CommentSection.svelte';
3
+ import { type ReactionBarForms } from '../weeklies/ReactionBar.svelte';
4
+ /**
5
+ * A weekly's own page: number and date, title, excerpt, the square hero,
6
+ * the body, Instagram and sources, share, reactions, comments, and the
7
+ * related weeklies. The community forms arrive preflighted by the host —
8
+ * the website's remote forms, a CMS mirror's inert mocks — and
9
+ * `isLoggedIn` is the host's auth answer; those are the props the hosts
10
+ * fill differently. `related` is its own prop because vit-brain loads it
11
+ * apart from the weekly, where the website nests it.
12
+ */
13
+ interface Props {
14
+ weekly: WeeklyArticleData;
15
+ related: WeeklyCardData[];
16
+ comments: CommentThreadData[];
17
+ reactions: ReactionSummary[];
18
+ isLoggedIn: boolean;
19
+ /** The main comment box, preflighted; see CommentSection. */
20
+ commentForm: CommentFormInstance;
21
+ /** One preflighted instance per reply box; see CommentSection. */
22
+ replyFormFor: (threadId: string) => CommentFormInstance;
23
+ reactionForms: ReactionBarForms;
24
+ /**
25
+ * What a CMS may open on a weekly's own page: title, excerpt, and the body
26
+ * as rich text. The «Dades i fonts» and «Segueix explorant» headings and
27
+ * the back link edit through `config.messageEdit` over their own keys.
28
+ */
29
+ edit?: ArticleEdit;
30
+ }
31
+ declare const WeeklyPage: import("svelte").Component<Props, {}, "">;
32
+ type WeeklyPage = ReturnType<typeof WeeklyPage>;
33
+ export default WeeklyPage;
@@ -0,0 +1,107 @@
1
+ <script module lang="ts">
2
+ import type { CopyEditFor } from '../../content/pages.js';
3
+ import type { ProjectCardData } from '../../content/types.js';
4
+ import type { ProjectEditMap } from '../projects/ProjectCard.svelte';
5
+
6
+ /** What a CMS may open on /what-we-do; `projectFor` answers per card by its `id`. */
7
+ export interface WhatWeDoPageEdit {
8
+ copy?: CopyEditFor<'what-we-do'>;
9
+ projectFor?: (project: ProjectCardData) => ProjectEditMap | undefined;
10
+ }
11
+ </script>
12
+
13
+ <script lang="ts">
14
+ import { getUiConfig } from '../../config/context.js';
15
+ import type { PageCopy } from '../../content/pages.js';
16
+ import Editable from '../../edit/Editable.svelte';
17
+ import PageShell from '../layout/PageShell.svelte';
18
+ import ProjectCard from '../projects/ProjectCard.svelte';
19
+ import CopyIntro from '../ui/CopyIntro.svelte';
20
+
21
+ /**
22
+ * The projects page: a showcase of the latest project, the collaborations
23
+ * as wide rows, the passion projects as a grid. The three groups are the
24
+ * website's load's (`getLatest`, one `list(kind)` each); a host that
25
+ * groups differently (vit-brain's `projectSections`) answers the same
26
+ * three props.
27
+ */
28
+ interface Props {
29
+ content: PageCopy<'what-we-do'>;
30
+ /** The showcase slot; `null`/absent renders no «Últim projecte» band. */
31
+ latest?: ProjectCardData | null;
32
+ collaborations: ProjectCardData[];
33
+ passion: ProjectCardData[];
34
+ edit?: WhatWeDoPageEdit;
35
+ }
36
+
37
+ let { content, latest = null, collaborations, passion, edit }: Props = $props();
38
+
39
+ const config = getUiConfig();
40
+ const msg = $derived(config.messages);
41
+ </script>
42
+
43
+ <PageShell title={msg.nav_whatWeDo()}>
44
+ <Editable edit={config.messageEdit?.('nav_whatWeDo')} value={msg.nav_whatWeDo()}>
45
+ {#snippet children(text, attrs)}<h1 {...attrs}>{text}</h1>{/snippet}
46
+ </Editable>
47
+
48
+ {#if latest}
49
+ <section class="latest" aria-labelledby="latest-heading">
50
+ <Editable edit={edit?.copy?.('latest_heading')} value={content.latest_heading}>
51
+ {#snippet children(text, attrs)}
52
+ <h2 class="section-heading" id="latest-heading" {...attrs}>{text}</h2>
53
+ {/snippet}
54
+ </Editable>
55
+ <ProjectCard project={latest} variant="wide" edit={edit?.projectFor?.(latest)} />
56
+ </section>
57
+ {/if}
58
+
59
+ <section aria-labelledby="collaborations-heading">
60
+ <Editable edit={edit?.copy?.('collaborations_heading')} value={content.collaborations_heading}>
61
+ {#snippet children(text, attrs)}
62
+ <h2 class="section-heading" id="collaborations-heading" {...attrs}>
63
+ {text}
64
+ </h2>
65
+ {/snippet}
66
+ </Editable>
67
+ <CopyIntro text={content.collaborations_intro} edit={edit?.copy?.('collaborations_intro')} />
68
+ <div class="stack">
69
+ {#each collaborations as project (project.slug)}
70
+ <ProjectCard {project} variant="wide" edit={edit?.projectFor?.(project)} />
71
+ {/each}
72
+ </div>
73
+ </section>
74
+
75
+ <section aria-labelledby="passion-heading">
76
+ <Editable edit={edit?.copy?.('passion_heading')} value={content.passion_heading}>
77
+ {#snippet children(text, attrs)}
78
+ <h2 class="section-heading" id="passion-heading" {...attrs}>{text}</h2>
79
+ {/snippet}
80
+ </Editable>
81
+ <CopyIntro text={content.passion_intro} edit={edit?.copy?.('passion_intro')} />
82
+ <div class="grid">
83
+ {#each passion as project (project.slug)}
84
+ <ProjectCard {project} variant="grid" edit={edit?.projectFor?.(project)} />
85
+ {/each}
86
+ </div>
87
+ </section>
88
+ </PageShell>
89
+
90
+ <style>
91
+ .latest {
92
+ padding-block-end: var(--space-4);
93
+ border-bottom: 1px solid var(--color-hairline);
94
+ }
95
+
96
+ .stack {
97
+ display: flex;
98
+ flex-direction: column;
99
+ gap: var(--space-5);
100
+ }
101
+
102
+ .grid {
103
+ display: grid;
104
+ grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
105
+ gap: var(--space-5) var(--space-4);
106
+ }
107
+ </style>
@@ -0,0 +1,27 @@
1
+ import type { CopyEditFor } from '../../content/pages.js';
2
+ import type { ProjectCardData } from '../../content/types.js';
3
+ import type { ProjectEditMap } from '../projects/ProjectCard.svelte';
4
+ /** What a CMS may open on /what-we-do; `projectFor` answers per card by its `id`. */
5
+ export interface WhatWeDoPageEdit {
6
+ copy?: CopyEditFor<'what-we-do'>;
7
+ projectFor?: (project: ProjectCardData) => ProjectEditMap | undefined;
8
+ }
9
+ import type { PageCopy } from '../../content/pages.js';
10
+ /**
11
+ * The projects page: a showcase of the latest project, the collaborations
12
+ * as wide rows, the passion projects as a grid. The three groups are the
13
+ * website's load's (`getLatest`, one `list(kind)` each); a host that
14
+ * groups differently (vit-brain's `projectSections`) answers the same
15
+ * three props.
16
+ */
17
+ interface Props {
18
+ content: PageCopy<'what-we-do'>;
19
+ /** The showcase slot; `null`/absent renders no «Últim projecte» band. */
20
+ latest?: ProjectCardData | null;
21
+ collaborations: ProjectCardData[];
22
+ passion: ProjectCardData[];
23
+ edit?: WhatWeDoPageEdit;
24
+ }
25
+ declare const WhatWeDoPage: import("svelte").Component<Props, {}, "">;
26
+ type WhatWeDoPage = ReturnType<typeof WhatWeDoPage>;
27
+ export default WhatWeDoPage;
@@ -0,0 +1,108 @@
1
+ <script module lang="ts">
2
+ import type { CopyEditFor } from '../../content/pages.js';
3
+ import type { CollaboratorData, TeamMemberData } from '../../content/types.js';
4
+ import type { CollectionRef } from '../../edit/types.js';
5
+ import type { CollaboratorEditMap } from '../team/CollaboratorList.svelte';
6
+ import type { TeamMemberEditMap } from '../team/TeamMemberCard.svelte';
7
+
8
+ /**
9
+ * What a CMS may open on /who-we-are. `memberFor` answers per row, so a
10
+ * host reads the identity it put on `TeamMemberData.id`; `collaborators`
11
+ * names the list's collection, which turns on its add slot and removes.
12
+ */
13
+ export interface WhoWeArePageEdit {
14
+ copy?: CopyEditFor<'who-we-are'>;
15
+ memberFor?: (member: TeamMemberData) => TeamMemberEditMap | undefined;
16
+ collaboratorFor?: (collaborator: CollaboratorData) => CollaboratorEditMap | undefined;
17
+ collaborators?: CollectionRef;
18
+ }
19
+ </script>
20
+
21
+ <script lang="ts">
22
+ import { getUiConfig } from '../../config/context.js';
23
+ import type { PageCopy } from '../../content/pages.js';
24
+ import Editable from '../../edit/Editable.svelte';
25
+ import PageShell from '../layout/PageShell.svelte';
26
+ import CollaboratorList from '../team/CollaboratorList.svelte';
27
+ import TeamMemberCard from '../team/TeamMemberCard.svelte';
28
+ import CopyIntro from '../ui/CopyIntro.svelte';
29
+
30
+ /**
31
+ * The team page: the featured members, the board, the collaborators. The
32
+ * split into `featured` and `board` is the website's load's (one `isBoard`
33
+ * filter each), so the module takes the two lists rather than re-deciding.
34
+ */
35
+ interface Props {
36
+ content: PageCopy<'who-we-are'>;
37
+ featured: TeamMemberData[];
38
+ board: TeamMemberData[];
39
+ collaborators: CollaboratorData[];
40
+ edit?: WhoWeArePageEdit;
41
+ }
42
+
43
+ let { content, featured, board, collaborators, edit }: Props = $props();
44
+
45
+ const config = getUiConfig();
46
+ const msg = $derived(config.messages);
47
+ </script>
48
+
49
+ <PageShell title={msg.nav_whoWeAre()}>
50
+ <Editable edit={config.messageEdit?.('nav_whoWeAre')} value={msg.nav_whoWeAre()}>
51
+ {#snippet children(text, attrs)}<h1 {...attrs}>{text}</h1>{/snippet}
52
+ </Editable>
53
+
54
+ <section aria-labelledby="team-heading">
55
+ <Editable edit={edit?.copy?.('team_heading')} value={content.team_heading}>
56
+ {#snippet children(text, attrs)}
57
+ <h2 class="section-heading" id="team-heading" {...attrs}>{text}</h2>
58
+ {/snippet}
59
+ </Editable>
60
+ <CopyIntro text={content.team_intro} edit={edit?.copy?.('team_intro')} />
61
+ <div class="featured">
62
+ {#each featured as member (member.slug)}
63
+ <TeamMemberCard {member} variant="featured" edit={edit?.memberFor?.(member)} />
64
+ {/each}
65
+ </div>
66
+ </section>
67
+
68
+ <section aria-labelledby="board-heading">
69
+ <Editable edit={edit?.copy?.('board_heading')} value={content.board_heading}>
70
+ {#snippet children(text, attrs)}
71
+ <h2 class="section-heading" id="board-heading" {...attrs}>{text}</h2>
72
+ {/snippet}
73
+ </Editable>
74
+ <CopyIntro text={content.board_intro} edit={edit?.copy?.('board_intro')} />
75
+ <div class="board">
76
+ {#each board as member (member.slug)}
77
+ <TeamMemberCard {member} variant="board" edit={edit?.memberFor?.(member)} />
78
+ {/each}
79
+ </div>
80
+ </section>
81
+
82
+ <section aria-labelledby="collaborators-heading">
83
+ <Editable edit={edit?.copy?.('collaborators_heading')} value={content.collaborators_heading}>
84
+ {#snippet children(text, attrs)}
85
+ <h2 class="section-heading" id="collaborators-heading" {...attrs}>{text}</h2>
86
+ {/snippet}
87
+ </Editable>
88
+ <CollaboratorList
89
+ {collaborators}
90
+ editFor={edit?.collaboratorFor}
91
+ collection={edit?.collaborators}
92
+ />
93
+ </section>
94
+ </PageShell>
95
+
96
+ <style>
97
+ .featured {
98
+ display: grid;
99
+ grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
100
+ gap: var(--space-4);
101
+ }
102
+
103
+ .board {
104
+ display: grid;
105
+ grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
106
+ gap: var(--space-4);
107
+ }
108
+ </style>
@@ -0,0 +1,32 @@
1
+ import type { CopyEditFor } from '../../content/pages.js';
2
+ import type { CollaboratorData, TeamMemberData } from '../../content/types.js';
3
+ import type { CollectionRef } from '../../edit/types.js';
4
+ import type { CollaboratorEditMap } from '../team/CollaboratorList.svelte';
5
+ import type { TeamMemberEditMap } from '../team/TeamMemberCard.svelte';
6
+ /**
7
+ * What a CMS may open on /who-we-are. `memberFor` answers per row, so a
8
+ * host reads the identity it put on `TeamMemberData.id`; `collaborators`
9
+ * names the list's collection, which turns on its add slot and removes.
10
+ */
11
+ export interface WhoWeArePageEdit {
12
+ copy?: CopyEditFor<'who-we-are'>;
13
+ memberFor?: (member: TeamMemberData) => TeamMemberEditMap | undefined;
14
+ collaboratorFor?: (collaborator: CollaboratorData) => CollaboratorEditMap | undefined;
15
+ collaborators?: CollectionRef;
16
+ }
17
+ import type { PageCopy } from '../../content/pages.js';
18
+ /**
19
+ * The team page: the featured members, the board, the collaborators. The
20
+ * split into `featured` and `board` is the website's load's (one `isBoard`
21
+ * filter each), so the module takes the two lists rather than re-deciding.
22
+ */
23
+ interface Props {
24
+ content: PageCopy<'who-we-are'>;
25
+ featured: TeamMemberData[];
26
+ board: TeamMemberData[];
27
+ collaborators: CollaboratorData[];
28
+ edit?: WhoWeArePageEdit;
29
+ }
30
+ declare const WhoWeArePage: import("svelte").Component<Props, {}, "">;
31
+ type WhoWeArePage = ReturnType<typeof WhoWeArePage>;
32
+ export default WhoWeArePage;
@@ -1,7 +1,24 @@
1
1
  <script lang="ts">
2
- import type { Snippet } from 'svelte';
2
+ import { onMount, type Snippet } from 'svelte';
3
3
  import IconButton from './IconButton.svelte';
4
4
 
5
+ /**
6
+ * A modal over the native <dialog>: showModal() traps focus and layers the
7
+ * backdrop, the cancel event covers Escape, a backdrop click closes.
8
+ *
9
+ * The element is NOT part of the server-rendered document. A <dialog> is
10
+ * flow content, so wherever a caller mounts one inside phrasing content —
11
+ * a LinkEdit inside a <p>, which NewsletterSignup does — the HTML parser
12
+ * closes the paragraph early, Svelte reports `node_invalid_placement_ssr`
13
+ * on the way out and a hydration mismatch on the way back in, on every
14
+ * page that renders the caller. No dialog is open at first paint (opening
15
+ * one takes a click), so the server has nothing to say about it; the
16
+ * element is created after mount, by script, which the content model does
17
+ * not police. Placement is this module's to own — a caller cannot know
18
+ * what its parent is — and owning it here fixes every caller at once,
19
+ * ConfirmDialog included.
20
+ */
21
+
5
22
  interface Props {
6
23
  open: boolean;
7
24
  title: string;
@@ -16,9 +33,11 @@
16
33
  let { open, title, onclose, closeLabel = 'Tanca', children }: Props = $props();
17
34
 
18
35
  let dialog: HTMLDialogElement | undefined = $state();
36
+ let mounted = $state(false);
37
+ onMount(() => {
38
+ mounted = true;
39
+ });
19
40
 
20
- // Native <dialog>: showModal() traps focus and layers the backdrop; the
21
- // cancel event covers Escape.
22
41
  $effect(() => {
23
42
  if (!dialog) return;
24
43
  if (open && !dialog.open) dialog.showModal();
@@ -32,15 +51,17 @@
32
51
  }
33
52
  </script>
34
53
 
35
- <dialog bind:this={dialog} oncancel={onclose} {onclick}>
36
- <div class="panel">
37
- <div class="top">
38
- <h2>{title}</h2>
39
- <IconButton icon="close" label={closeLabel} onclick={onclose} />
54
+ {#if mounted}
55
+ <dialog bind:this={dialog} oncancel={onclose} {onclick}>
56
+ <div class="panel">
57
+ <div class="top">
58
+ <h2>{title}</h2>
59
+ <IconButton icon="close" label={closeLabel} onclick={onclose} />
60
+ </div>
61
+ {@render children()}
40
62
  </div>
41
- {@render children()}
42
- </div>
43
- </dialog>
63
+ </dialog>
64
+ {/if}
44
65
 
45
66
  <style>
46
67
  dialog {
@@ -1,4 +1,20 @@
1
- import type { Snippet } from 'svelte';
1
+ import { type Snippet } from 'svelte';
2
+ /**
3
+ * A modal over the native <dialog>: showModal() traps focus and layers the
4
+ * backdrop, the cancel event covers Escape, a backdrop click closes.
5
+ *
6
+ * The element is NOT part of the server-rendered document. A <dialog> is
7
+ * flow content, so wherever a caller mounts one inside phrasing content —
8
+ * a LinkEdit inside a <p>, which NewsletterSignup does — the HTML parser
9
+ * closes the paragraph early, Svelte reports `node_invalid_placement_ssr`
10
+ * on the way out and a hydration mismatch on the way back in, on every
11
+ * page that renders the caller. No dialog is open at first paint (opening
12
+ * one takes a click), so the server has nothing to say about it; the
13
+ * element is created after mount, by script, which the content model does
14
+ * not police. Placement is this module's to own — a caller cannot know
15
+ * what its parent is — and owning it here fixes every caller at once,
16
+ * ConfirmDialog included.
17
+ */
2
18
  interface Props {
3
19
  open: boolean;
4
20
  title: string;
@@ -1,10 +1,4 @@
1
1
  import { type UiConfig, type UiConfigInput } from './types.js';
2
- /**
3
- * The provider-less config: identity hrefs, Catalan, no router. Complete on
4
- * purpose — every component must render without a provider, which is what
5
- * keeps stories and component tests free of app wiring.
6
- */
7
- export declare const DEFAULT_UI_CONFIG: UiConfig;
8
2
  /**
9
3
  * Installs the app's config for the subtree. Takes a *thunk* so a provider
10
4
  * whose input is a reactive prop stays live: the resolved object delegates
@@ -8,7 +8,7 @@ const KEY = Symbol.for('@vit-foundation/ui:config');
8
8
  * purpose — every component must render without a provider, which is what
9
9
  * keeps stories and component tests free of app wiring.
10
10
  */
11
- export const DEFAULT_UI_CONFIG = {
11
+ const DEFAULT_UI_CONFIG = {
12
12
  href: (path) => path,
13
13
  locale: () => BASE_LOCALE,
14
14
  locales: LOCALES,
@@ -1,7 +1,7 @@
1
- export { DEFAULT_UI_CONFIG, getUiConfig, setUiConfig } from './context.js';
1
+ export { getUiConfig, setUiConfig } from './context.js';
2
2
  export { defaultEditMessages } from './edit-messages.js';
3
3
  export type { EditMessages } from './edit-messages.js';
4
4
  export { defaultMessages } from './messages.js';
5
5
  export { default as UiProvider } from './UiProvider.svelte';
6
6
  export { BASE_LOCALE, LOCALES } from './types.js';
7
- export type { Locale, NotParameterized, ParameterizedKey, ParameterlessKey, SiteLink, UiConfig, UiConfigInput, UiMessages } from './types.js';
7
+ export type { Locale, NotParameterized, ParameterlessKey, SiteLink, UiConfig, UiConfigInput, UiMessages } from './types.js';
@@ -1,4 +1,4 @@
1
- export { DEFAULT_UI_CONFIG, getUiConfig, setUiConfig } from './context.js';
1
+ export { getUiConfig, setUiConfig } from './context.js';
2
2
  export { defaultEditMessages } from './edit-messages.js';
3
3
  export { defaultMessages } from './messages.js';
4
4
  export { default as UiProvider } from './UiProvider.svelte';
@@ -105,6 +105,28 @@ export const defaultMessages = {
105
105
  category_education: () => 'Educació',
106
106
  category_collaboration: () => 'Col·laboració',
107
107
  category_press: () => 'Premsa',
108
+ nav_whoWeAre: () => 'Qui som',
109
+ nav_whatWeDo: () => 'Què fem',
110
+ nav_getInvolved: () => "Implica-t'hi",
111
+ nav_weeklies: () => 'Weeklies',
112
+ common_seeAll: () => 'Mostra-ho tot',
113
+ common_seeAllHref: () => '/transparency',
114
+ cta_meetTeam: () => "Coneix l'equip",
115
+ cta_meetTeamHref: () => '/who-we-are',
116
+ cta_contactUs: () => "Contacta'ns",
117
+ cta_contactUsHref: () => '/get-involved',
118
+ weeklies_searchPlaceholder: () => 'Cerca un tema…',
119
+ weeklies_exploreOne: () => "…o explora'n un d'aquests",
120
+ weeklies_filterLabel: () => 'Filtra per tema',
121
+ weeklies_searchResultsLabel: () => 'Resultats de la cerca',
122
+ weeklies_loadError: () => "No s'han pogut carregar els resultats. Torna-ho a provar.",
123
+ weeklies_empty: () => 'No hem trobat cap weekly amb aquests criteris.',
124
+ timeline_searchPlaceholder: () => 'Què hem estat fent?',
125
+ timeline_filterLabel: () => 'Filtra per categoria',
126
+ timeline_empty: () => 'No hem trobat cap fita amb aquests criteris.',
127
+ back_label: () => 'Torna enrere',
128
+ weeklie_sources: () => 'Dades i fonts',
129
+ weeklie_keepExploring: () => 'Segueix explorant',
108
130
  status_published: () => 'Publicat',
109
131
  status_draft: () => 'Esborrany',
110
132
  status_open: () => 'Oberta',