@vit-foundation/ui 0.24.2 → 0.25.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 (32) hide show
  1. package/README.md +11 -11
  2. package/dist/components/pages/GetInvolvedPage.svelte +139 -0
  3. package/dist/components/pages/GetInvolvedPage.svelte.d.ts +36 -0
  4. package/dist/components/pages/HomePage.svelte +256 -0
  5. package/dist/components/pages/HomePage.svelte.d.ts +36 -0
  6. package/dist/components/pages/LegalPage.svelte +39 -0
  7. package/dist/components/pages/LegalPage.svelte.d.ts +14 -0
  8. package/dist/components/pages/ProjectPage.svelte +128 -0
  9. package/dist/components/pages/ProjectPage.svelte.d.ts +17 -0
  10. package/dist/components/pages/TransparencyPage.svelte +164 -0
  11. package/dist/components/pages/TransparencyPage.svelte.d.ts +40 -0
  12. package/dist/components/pages/WeekliesPage.svelte +184 -0
  13. package/dist/components/pages/WeekliesPage.svelte.d.ts +46 -0
  14. package/dist/components/pages/WeeklyPage.svelte +203 -0
  15. package/dist/components/pages/WeeklyPage.svelte.d.ts +35 -0
  16. package/dist/components/pages/WhatWeDoPage.svelte +107 -0
  17. package/dist/components/pages/WhatWeDoPage.svelte.d.ts +27 -0
  18. package/dist/components/pages/WhoWeArePage.svelte +108 -0
  19. package/dist/components/pages/WhoWeArePage.svelte.d.ts +32 -0
  20. package/dist/config/messages.js +22 -0
  21. package/dist/config/types.d.ts +36 -0
  22. package/dist/content/index.d.ts +3 -1
  23. package/dist/content/index.js +1 -0
  24. package/dist/content/pages.d.ts +64 -0
  25. package/dist/content/pages.js +71 -0
  26. package/dist/content/types.d.ts +45 -0
  27. package/dist/content/types.js +0 -5
  28. package/dist/content-components.d.ts +18 -0
  29. package/dist/content-components.js +12 -0
  30. package/dist/contract.d.ts +3 -0
  31. package/dist/contract.js +4 -0
  32. package/package.json +1 -1
package/README.md CHANGED
@@ -22,17 +22,17 @@ Requires `svelte` ^5.0.0 as a peer. Import the two stylesheets once:
22
22
 
23
23
  Everything exports flat from the root, and again grouped by role:
24
24
 
25
- | Entry point | What lives there |
26
- | --------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
27
- | [`/primitives`](./docs/components/primitives.md) | Button, GhostButton, Link, CardMedia, CardTitle, CopyIntro, DateText, DecorShapes, FilterChips, Pagination, RichText, SearchInput, ShareRow |
28
- | [`/forms`](./docs/forms.md) | Field, FormFeedback, FormErrorFeedback, FormResultSlot, Honeypot, LocaleField, NewsletterIntentField — plus the remote-form seam: structural types, transport constants, field bounds |
29
- | [`/chrome`](./docs/components/chrome.md) | PageShell, Nav, Footer |
30
- | [`/content`](./docs/components/content.md) | WeeklieCard, ProjectCard, Timeline, TimelineMilestone, TeamMemberCard, CollaboratorList, JobList, SortSelect — plus the data shapes, helpers and the two list rules (createWeeklyList, createUrlFilters) |
31
- | [`/community`](./docs/components/community.md) | AuthPageShell, LoginForm, SignupForm, GoogleAuthForm, AccountPanel, NewsletterSignup, CommentSection, ReactionBar, ContactForm |
32
- | [`/edit`](./docs/edit-mode.md) | Editable, setEditAdapter/getEditAdapter, descriptors and helpers, collectionEditing, LocalizedText |
33
- | [`/config`](./docs/getting-started.md#wiring-an-app-uiprovider) | UiProvider, UiConfig, the locale set, the default Catalan messages |
34
- | `/contract` | The component-free half: LOCALES, BASE_LOCALE, localize, REACTIONS and the edit-descriptor types — the one subpath a host may import from SERVER code |
35
- | `/testing` | createRemoteFormMock — the remote-form stand-in for stories and tests |
25
+ | Entry point | What lives there |
26
+ | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
27
+ | [`/primitives`](./docs/components/primitives.md) | Button, GhostButton, Link, CardMedia, CardTitle, CopyIntro, DateText, DecorShapes, FilterChips, Pagination, RichText, SearchInput, ShareRow |
28
+ | [`/forms`](./docs/forms.md) | Field, FormFeedback, FormErrorFeedback, FormResultSlot, Honeypot, LocaleField, NewsletterIntentField — plus the remote-form seam: structural types, transport constants, field bounds |
29
+ | [`/chrome`](./docs/components/chrome.md) | PageShell, Nav, Footer |
30
+ | [`/content`](./docs/components/content.md) | WeeklieCard, ProjectCard, Timeline, TimelineMilestone, TeamMemberCard, CollaboratorList, JobList, SortSelect, the nine page modules (HomePage … WeeklyPage) — plus the data shapes, the page-copy vocabulary, helpers and the two list rules (createWeeklyList, createUrlFilters) |
31
+ | [`/community`](./docs/components/community.md) | AuthPageShell, LoginForm, SignupForm, GoogleAuthForm, AccountPanel, NewsletterSignup, CommentSection, ReactionBar, ContactForm |
32
+ | [`/edit`](./docs/edit-mode.md) | Editable, setEditAdapter/getEditAdapter, descriptors and helpers, collectionEditing, LocalizedText |
33
+ | [`/config`](./docs/getting-started.md#wiring-an-app-uiprovider) | UiProvider, UiConfig, the locale set, the default Catalan messages |
34
+ | `/contract` | The component-free half: LOCALES, BASE_LOCALE, localize, REACTIONS, PAGE_COPY_KEYS and the edit-descriptor types — the one subpath a host may import from SERVER code |
35
+ | `/testing` | createRemoteFormMock — the remote-form stand-in for stories and tests |
36
36
 
37
37
  Every component renders standalone (Catalan copy, identity hrefs); an app
38
38
  integrates its i18n and router through one `UiProvider` in the root layout.
@@ -0,0 +1,139 @@
1
+ <script module lang="ts">
2
+ import type { CopyEditFor } from '../../content/pages.js';
3
+ import type { JobOpeningData } from '../../content/types.js';
4
+ import type { CollectionRef } from '../../edit/types.js';
5
+ import type { JobEditMap } from '../jobs/JobList.svelte';
6
+
7
+ /**
8
+ * What a CMS may open on /get-involved. The five «why get in touch» rows
9
+ * are copy blocks and edit through `copy` like the headings; `jobs` names
10
+ * the openings' collection, which turns on the list's add slot and removes.
11
+ */
12
+ export interface GetInvolvedPageEdit {
13
+ copy?: CopyEditFor<'get-involved'>;
14
+ jobFor?: (job: JobOpeningData) => JobEditMap | undefined;
15
+ jobs?: CollectionRef;
16
+ }
17
+ </script>
18
+
19
+ <script lang="ts">
20
+ import { getUiConfig } from '../../config/context.js';
21
+ import { GET_INVOLVED_REASON_KEYS, type PageCopy } from '../../content/pages.js';
22
+ import Editable from '../../edit/Editable.svelte';
23
+ import ContactForm, { type ContactFormInstance } from '../contact/ContactForm.svelte';
24
+ import JobList from '../jobs/JobList.svelte';
25
+ import PageShell from '../layout/PageShell.svelte';
26
+ import CopyIntro from '../ui/CopyIntro.svelte';
27
+ import DecorShapes from '../ui/DecorShapes.svelte';
28
+
29
+ /**
30
+ * The contact page: the reasons to write beside the contact form, the
31
+ * funding band, the open roles. The form arrives preflighted by the host
32
+ * against its own schema (the website's `contactForm.preflight(...)`; a
33
+ * CMS mirror passes the package's inert mock) — the one prop the hosts
34
+ * fill differently.
35
+ *
36
+ * The reasons list derives from `GET_INVOLVED_REASON_KEYS`, in that
37
+ * order, and does NOT skip a block with no row yet — the same answer
38
+ * CopyIntro gives, and the one `PageCopy` states.
39
+ */
40
+ interface Props {
41
+ content: PageCopy<'get-involved'>;
42
+ jobs: JobOpeningData[];
43
+ form: ContactFormInstance;
44
+ edit?: GetInvolvedPageEdit;
45
+ }
46
+
47
+ let { content, jobs, form, edit }: Props = $props();
48
+
49
+ const config = getUiConfig();
50
+ const msg = $derived(config.messages);
51
+ </script>
52
+
53
+ <PageShell title={msg.nav_getInvolved()}>
54
+ <Editable edit={config.messageEdit?.('nav_getInvolved')} value={msg.nav_getInvolved()}>
55
+ {#snippet children(text, attrs)}<h1 {...attrs}>{text}</h1>{/snippet}
56
+ </Editable>
57
+
58
+ <section class="contact" aria-labelledby="contact-heading">
59
+ <div class="contact-text">
60
+ <Editable edit={edit?.copy?.('contact_heading')} value={content.contact_heading}>
61
+ {#snippet children(text, attrs)}
62
+ <h2 class="section-heading" id="contact-heading" {...attrs}>{text}</h2>
63
+ {/snippet}
64
+ </Editable>
65
+ <CopyIntro text={content.contact_intro} edit={edit?.copy?.('contact_intro')} />
66
+ <ul class="reasons">
67
+ {#each GET_INVOLVED_REASON_KEYS as key (key)}
68
+ <!-- The li itself is the editable node: no extra span, so the
69
+ read-only markup stays the website's. -->
70
+ <Editable edit={edit?.copy?.(key)} value={content[key]}>
71
+ {#snippet children(text, attrs)}<li {...attrs}>{text}</li>{/snippet}
72
+ </Editable>
73
+ {/each}
74
+ </ul>
75
+ </div>
76
+ <ContactForm {form} />
77
+ </section>
78
+
79
+ <section class="funding" aria-labelledby="funding-heading">
80
+ <div>
81
+ <Editable edit={edit?.copy?.('funding_heading')} value={content.funding_heading}>
82
+ {#snippet children(text, attrs)}
83
+ <h2 class="section-heading" id="funding-heading" {...attrs}>{text}</h2>
84
+ {/snippet}
85
+ </Editable>
86
+ <CopyIntro text={content.funding_body} edit={edit?.copy?.('funding_body')} />
87
+ </div>
88
+ <DecorShapes flip />
89
+ </section>
90
+
91
+ <section aria-labelledby="careers-heading">
92
+ <Editable edit={edit?.copy?.('careers_heading')} value={content.careers_heading}>
93
+ {#snippet children(text, attrs)}
94
+ <h2 class="section-heading" id="careers-heading" {...attrs}>{text}</h2>
95
+ {/snippet}
96
+ </Editable>
97
+ <CopyIntro text={content.careers_intro} edit={edit?.copy?.('careers_intro')} />
98
+ <JobList {jobs} editFor={edit?.jobFor} collection={edit?.jobs} />
99
+ </section>
100
+ </PageShell>
101
+
102
+ <style>
103
+ .contact {
104
+ display: grid;
105
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
106
+ gap: var(--space-5);
107
+ align-items: start;
108
+ }
109
+
110
+ .reasons {
111
+ padding-left: var(--space-4);
112
+ display: flex;
113
+ flex-direction: column;
114
+ gap: var(--space-2);
115
+ color: var(--color-ink-secondary);
116
+ }
117
+
118
+ .funding {
119
+ display: flex;
120
+ justify-content: space-between;
121
+ align-items: center;
122
+ gap: var(--space-5);
123
+ margin-block: var(--space-6);
124
+ padding: var(--space-4);
125
+ background: var(--color-cream);
126
+ border-radius: var(--radius-lg);
127
+ }
128
+
129
+ @media (max-width: 720px) {
130
+ .contact {
131
+ grid-template-columns: 1fr;
132
+ }
133
+
134
+ .funding {
135
+ flex-direction: column;
136
+ align-items: flex-start;
137
+ }
138
+ }
139
+ </style>
@@ -0,0 +1,36 @@
1
+ import type { CopyEditFor } from '../../content/pages.js';
2
+ import type { JobOpeningData } from '../../content/types.js';
3
+ import type { CollectionRef } from '../../edit/types.js';
4
+ import type { JobEditMap } from '../jobs/JobList.svelte';
5
+ /**
6
+ * What a CMS may open on /get-involved. The five «why get in touch» rows
7
+ * are copy blocks and edit through `copy` like the headings; `jobs` names
8
+ * the openings' collection, which turns on the list's add slot and removes.
9
+ */
10
+ export interface GetInvolvedPageEdit {
11
+ copy?: CopyEditFor<'get-involved'>;
12
+ jobFor?: (job: JobOpeningData) => JobEditMap | undefined;
13
+ jobs?: CollectionRef;
14
+ }
15
+ import { type PageCopy } from '../../content/pages.js';
16
+ import { type ContactFormInstance } from '../contact/ContactForm.svelte';
17
+ /**
18
+ * The contact page: the reasons to write beside the contact form, the
19
+ * funding band, the open roles. The form arrives preflighted by the host
20
+ * against its own schema (the website's `contactForm.preflight(...)`; a
21
+ * CMS mirror passes the package's inert mock) — the one prop the hosts
22
+ * fill differently.
23
+ *
24
+ * The reasons list derives from `GET_INVOLVED_REASON_KEYS`, in that
25
+ * order, and does NOT skip a block with no row yet — the same answer
26
+ * CopyIntro gives, and the one `PageCopy` states.
27
+ */
28
+ interface Props {
29
+ content: PageCopy<'get-involved'>;
30
+ jobs: JobOpeningData[];
31
+ form: ContactFormInstance;
32
+ edit?: GetInvolvedPageEdit;
33
+ }
34
+ declare const GetInvolvedPage: import("svelte").Component<Props, {}, "">;
35
+ type GetInvolvedPage = ReturnType<typeof GetInvolvedPage>;
36
+ export default GetInvolvedPage;
@@ -0,0 +1,256 @@
1
+ <script module lang="ts">
2
+ import type { CopyEditFor } from '../../content/pages.js';
3
+ import type { MilestoneData, WeeklyCardData } from '../../content/types.js';
4
+ import type { MilestoneEditMap } from '../timeline/TimelineMilestone.svelte';
5
+ import type { WeeklyEditMap } from '../weeklies/WeeklieCard.svelte';
6
+
7
+ /**
8
+ * What a CMS may open on the home page. Every member optional: a read-only
9
+ * host passes no `edit` at all, and the render is byte-identical to the
10
+ * website's route. The chrome wording (the «Mostra-ho tot» link, the CTAs,
11
+ * the search placeholder, «…o explora'n un») needs no member here — the
12
+ * module reads `config.messageEdit`, like every component does for its
13
+ * own message sites.
14
+ */
15
+ export interface HomePageEdit {
16
+ copy?: CopyEditFor<'home'>;
17
+ milestoneFor?: (milestone: MilestoneData) => MilestoneEditMap | undefined;
18
+ weeklyFor?: (weekly: WeeklyCardData) => WeeklyEditMap | undefined;
19
+ }
20
+ </script>
21
+
22
+ <script lang="ts">
23
+ import { getUiConfig } from '../../config/context.js';
24
+ import type { PageCopy } from '../../content/pages.js';
25
+ import LinkEdit from '../../edit/chrome/LinkEdit.svelte';
26
+ import Editable from '../../edit/Editable.svelte';
27
+ import { chromeProperty } from '../../edit/helpers.js';
28
+ import PageShell from '../layout/PageShell.svelte';
29
+ import Timeline from '../timeline/Timeline.svelte';
30
+ import CopyIntro from '../ui/CopyIntro.svelte';
31
+ import DecorShapes from '../ui/DecorShapes.svelte';
32
+ import Link from '../ui/Link.svelte';
33
+ import SearchInput from '../ui/SearchInput.svelte';
34
+ import WeeklieCard from '../weeklies/WeeklieCard.svelte';
35
+
36
+ /**
37
+ * The landing page: hero, the latest milestones, a weeklies band with a
38
+ * search box, and two CTAs. Takes the website's read projection — its
39
+ * `+page.server.ts` returns exactly these three — plus the one thing the
40
+ * hosts do differently: where a search goes. The package has no router,
41
+ * so the host navigates to its own /weeklies with the query.
42
+ */
43
+ interface Props {
44
+ content: PageCopy<'home'>;
45
+ milestones: MilestoneData[];
46
+ weeklies: WeeklyCardData[];
47
+ /** Called with the trimmed query after the search box's debounce. */
48
+ onsearch: (query: string) => void;
49
+ edit?: HomePageEdit;
50
+ }
51
+
52
+ let { content, milestones, weeklies, onsearch, edit }: Props = $props();
53
+
54
+ const config = getUiConfig();
55
+ const msg = $derived(config.messages);
56
+
57
+ // A link is configurable whole: clicking it while editing opens ONE modal
58
+ // with Text and Adreça — the label saves as wording, the destination as
59
+ // its *Href key. Both halves undefined without `messageEdit`, so the
60
+ // control stays inert (the CommentSection rule).
61
+ const hrefProperty = (key: string) =>
62
+ config.messageEdit
63
+ ? chromeProperty(key, {
64
+ type: 'text',
65
+ locale: config.locale(),
66
+ label: config.editMessages.edit_linkUrl()
67
+ })
68
+ : undefined;
69
+ // A placeholder cannot hold a caret, so it edits through the search box's
70
+ // panel — only where the host exposes `messageEdit` (the ContactForm rule).
71
+ const placeholderProperty = $derived(
72
+ config.messageEdit
73
+ ? chromeProperty('weeklies_searchPlaceholder', {
74
+ type: 'text',
75
+ locale: config.locale(),
76
+ label: msg.weeklies_searchPlaceholder()
77
+ })
78
+ : undefined
79
+ );
80
+ </script>
81
+
82
+ <PageShell title={content.hero_title} description={content.hero_subtitle}>
83
+ <section class="hero">
84
+ <div class="shapes left" aria-hidden="true"><DecorShapes /></div>
85
+ <div class="hero-text">
86
+ <Editable edit={edit?.copy?.('hero_title')} value={content.hero_title}>
87
+ {#snippet children(text, attrs)}<h1 {...attrs}>{text}</h1>{/snippet}
88
+ </Editable>
89
+ <Editable edit={edit?.copy?.('hero_subtitle')} value={content.hero_subtitle}>
90
+ {#snippet children(text, attrs)}<p {...attrs}>{text}</p>{/snippet}
91
+ </Editable>
92
+ </div>
93
+ <div class="shapes right" aria-hidden="true"><DecorShapes flip /></div>
94
+ </section>
95
+
96
+ <section aria-labelledby="milestones-heading">
97
+ <Editable edit={edit?.copy?.('milestones_heading')} value={content.milestones_heading}>
98
+ {#snippet children(text, attrs)}
99
+ <h2 class="section-heading" id="milestones-heading" {...attrs}>{text}</h2>
100
+ {/snippet}
101
+ </Editable>
102
+ <Timeline {milestones} variant="compact" editFor={edit?.milestoneFor} />
103
+ <p class="see-all">
104
+ <LinkEdit
105
+ text={{ edit: config.messageEdit?.('common_seeAll'), value: msg.common_seeAll() }}
106
+ href={{ descriptor: hrefProperty('common_seeAllHref'), value: msg.common_seeAllHref() }}
107
+ >
108
+ {#snippet control()}
109
+ <Link href={msg.common_seeAllHref()}>{msg.common_seeAll()} →</Link>
110
+ {/snippet}
111
+ </LinkEdit>
112
+ </p>
113
+ </section>
114
+
115
+ <section class="weeklies-band" aria-labelledby="weeklies-heading">
116
+ <Editable edit={edit?.copy?.('weeklies_heading')} value={content.weeklies_heading}>
117
+ {#snippet children(text, attrs)}
118
+ <h2 class="section-heading" id="weeklies-heading" {...attrs}>{text}</h2>
119
+ {/snippet}
120
+ </Editable>
121
+ <CopyIntro text={content.weeklies_intro} edit={edit?.copy?.('weeklies_intro')} />
122
+ <SearchInput
123
+ placeholder={msg.weeklies_searchPlaceholder()}
124
+ label={msg.weeklies_searchPlaceholder()}
125
+ onsearch={(query) => onsearch(query)}
126
+ debounceMs={800}
127
+ placeholderEdit={placeholderProperty}
128
+ />
129
+ <Editable edit={config.messageEdit?.('weeklies_exploreOne')} value={msg.weeklies_exploreOne()}>
130
+ {#snippet children(text, attrs)}<p class="explore" {...attrs}>{text}</p>{/snippet}
131
+ </Editable>
132
+ <div class="grid">
133
+ {#each weeklies as weekly (weekly.slug)}
134
+ <WeeklieCard {weekly} edit={edit?.weeklyFor?.(weekly)} />
135
+ {/each}
136
+ </div>
137
+ </section>
138
+
139
+ <section class="know-more" aria-labelledby="know-more-heading">
140
+ <Editable edit={edit?.copy?.('know_more_heading')} value={content.know_more_heading}>
141
+ {#snippet children(text, attrs)}<h2 id="know-more-heading" {...attrs}>{text}</h2>{/snippet}
142
+ </Editable>
143
+ <div class="ctas">
144
+ <LinkEdit
145
+ text={{ edit: config.messageEdit?.('cta_meetTeam'), value: msg.cta_meetTeam() }}
146
+ href={{ descriptor: hrefProperty('cta_meetTeamHref'), value: msg.cta_meetTeamHref() }}
147
+ >
148
+ {#snippet control()}
149
+ <Link href={msg.cta_meetTeamHref()} class="cta">{msg.cta_meetTeam()}</Link>
150
+ {/snippet}
151
+ </LinkEdit>
152
+ <LinkEdit
153
+ text={{ edit: config.messageEdit?.('cta_contactUs'), value: msg.cta_contactUs() }}
154
+ href={{ descriptor: hrefProperty('cta_contactUsHref'), value: msg.cta_contactUsHref() }}
155
+ >
156
+ {#snippet control()}
157
+ <Link href={msg.cta_contactUsHref()} class="cta primary">{msg.cta_contactUs()}</Link>
158
+ {/snippet}
159
+ </LinkEdit>
160
+ </div>
161
+ </section>
162
+ </PageShell>
163
+
164
+ <style>
165
+ .hero {
166
+ display: grid;
167
+ grid-template-columns: auto 1fr auto;
168
+ align-items: center;
169
+ gap: var(--space-4);
170
+ padding-block: var(--space-6);
171
+ }
172
+
173
+ .hero-text {
174
+ text-align: center;
175
+ }
176
+
177
+ /* The one heading in the library that sets its own size: --text-hero is a
178
+ * deliberate decision about the landing page. Every other page module
179
+ * takes the h1/h2 scale from the host's global rules. */
180
+ h1 {
181
+ font-size: var(--text-hero);
182
+ margin-bottom: var(--space-3);
183
+ }
184
+
185
+ .hero-text p {
186
+ font-size: var(--text-lg);
187
+ color: var(--color-ink-secondary);
188
+ max-width: 44ch;
189
+ margin-inline: auto;
190
+ }
191
+
192
+ .see-all :global(a) {
193
+ color: var(--color-brand);
194
+ font-weight: 600;
195
+ text-decoration: none;
196
+ }
197
+
198
+ .weeklies-band {
199
+ background: var(--color-band-grey);
200
+ border-radius: var(--radius-lg);
201
+ padding: var(--space-4);
202
+ margin-block: var(--space-5);
203
+ }
204
+
205
+ .explore {
206
+ color: var(--color-ink-secondary);
207
+ max-width: 60ch;
208
+ }
209
+
210
+ .explore {
211
+ margin-top: var(--space-3);
212
+ }
213
+
214
+ .grid {
215
+ display: grid;
216
+ grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
217
+ gap: var(--space-4);
218
+ }
219
+
220
+ .know-more {
221
+ text-align: center;
222
+ padding-block: var(--space-6);
223
+ }
224
+
225
+ .ctas {
226
+ display: flex;
227
+ justify-content: center;
228
+ gap: var(--space-3);
229
+ flex-wrap: wrap;
230
+ }
231
+
232
+ .ctas :global(a.cta) {
233
+ padding: var(--space-2) var(--space-5);
234
+ border: 2px solid var(--color-ink);
235
+ border-radius: var(--radius);
236
+ text-decoration: none;
237
+ font-weight: 600;
238
+ color: var(--color-ink);
239
+ }
240
+
241
+ .ctas :global(a.cta.primary) {
242
+ background: var(--color-brand);
243
+ border-color: var(--color-brand);
244
+ color: var(--color-surface);
245
+ }
246
+
247
+ @media (max-width: 900px) {
248
+ .shapes {
249
+ display: none;
250
+ }
251
+
252
+ .hero {
253
+ grid-template-columns: 1fr;
254
+ }
255
+ }
256
+ </style>
@@ -0,0 +1,36 @@
1
+ import type { CopyEditFor } from '../../content/pages.js';
2
+ import type { MilestoneData, WeeklyCardData } from '../../content/types.js';
3
+ import type { MilestoneEditMap } from '../timeline/TimelineMilestone.svelte';
4
+ import type { WeeklyEditMap } from '../weeklies/WeeklieCard.svelte';
5
+ /**
6
+ * What a CMS may open on the home page. Every member optional: a read-only
7
+ * host passes no `edit` at all, and the render is byte-identical to the
8
+ * website's route. The chrome wording (the «Mostra-ho tot» link, the CTAs,
9
+ * the search placeholder, «…o explora'n un») needs no member here — the
10
+ * module reads `config.messageEdit`, like every component does for its
11
+ * own message sites.
12
+ */
13
+ export interface HomePageEdit {
14
+ copy?: CopyEditFor<'home'>;
15
+ milestoneFor?: (milestone: MilestoneData) => MilestoneEditMap | undefined;
16
+ weeklyFor?: (weekly: WeeklyCardData) => WeeklyEditMap | undefined;
17
+ }
18
+ import type { PageCopy } from '../../content/pages.js';
19
+ /**
20
+ * The landing page: hero, the latest milestones, a weeklies band with a
21
+ * search box, and two CTAs. Takes the website's read projection — its
22
+ * `+page.server.ts` returns exactly these three — plus the one thing the
23
+ * hosts do differently: where a search goes. The package has no router,
24
+ * so the host navigates to its own /weeklies with the query.
25
+ */
26
+ interface Props {
27
+ content: PageCopy<'home'>;
28
+ milestones: MilestoneData[];
29
+ weeklies: WeeklyCardData[];
30
+ /** Called with the trimmed query after the search box's debounce. */
31
+ onsearch: (query: string) => void;
32
+ edit?: HomePageEdit;
33
+ }
34
+ declare const HomePage: import("svelte").Component<Props, {}, "">;
35
+ type HomePage = ReturnType<typeof HomePage>;
36
+ export default HomePage;
@@ -0,0 +1,39 @@
1
+ <script module lang="ts">
2
+ import type { CopyEditFor } from '../../content/pages.js';
3
+
4
+ /** What a CMS may open on /legal: its two copy blocks (the body edits as rich text). */
5
+ export interface LegalPageEdit {
6
+ copy?: CopyEditFor<'legal'>;
7
+ }
8
+ </script>
9
+
10
+ <script lang="ts">
11
+ import type { PageCopy } from '../../content/pages.js';
12
+ import Editable from '../../edit/Editable.svelte';
13
+ import PageShell from '../layout/PageShell.svelte';
14
+ import RichText from '../ui/RichText.svelte';
15
+
16
+ /** The terms page: a heading and one rich-text body, on the chrome shell. */
17
+ interface Props {
18
+ content: PageCopy<'legal'>;
19
+ edit?: LegalPageEdit;
20
+ }
21
+
22
+ let { content, edit }: Props = $props();
23
+ </script>
24
+
25
+ <PageShell title={content.heading} variant="chrome">
26
+ <Editable edit={edit?.copy?.('heading')} value={content.heading}>
27
+ {#snippet children(text, attrs)}<h1 {...attrs}>{text}</h1>{/snippet}
28
+ </Editable>
29
+ <div class="body">
30
+ <RichText body={content.body} edit={edit?.copy?.('body')} />
31
+ </div>
32
+ </PageShell>
33
+
34
+ <style>
35
+ .body {
36
+ color: var(--color-ink-secondary);
37
+ max-width: 40rem;
38
+ }
39
+ </style>
@@ -0,0 +1,14 @@
1
+ import type { CopyEditFor } from '../../content/pages.js';
2
+ /** What a CMS may open on /legal: its two copy blocks (the body edits as rich text). */
3
+ export interface LegalPageEdit {
4
+ copy?: CopyEditFor<'legal'>;
5
+ }
6
+ import type { PageCopy } from '../../content/pages.js';
7
+ /** The terms page: a heading and one rich-text body, on the chrome shell. */
8
+ interface Props {
9
+ content: PageCopy<'legal'>;
10
+ edit?: LegalPageEdit;
11
+ }
12
+ declare const LegalPage: import("svelte").Component<Props, {}, "">;
13
+ type LegalPage = ReturnType<typeof LegalPage>;
14
+ export default LegalPage;