ferst-core 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.
- package/LICENSE +201 -0
- package/NOTICE +12 -0
- package/README.md +73 -0
- package/components/Button.astro +208 -0
- package/components/Card.astro +59 -0
- package/components/CookiePreferences.astro +232 -0
- package/components/DocLayout.astro +292 -0
- package/components/GroupCard.astro +164 -0
- package/components/LatestPostList.astro +64 -0
- package/components/PaginationNav.astro +81 -0
- package/components/PostCard.astro +262 -0
- package/components/PostsToolbar.astro +83 -0
- package/components/SecondaryLogo.astro +63 -0
- package/components/SiteFooter.astro +245 -0
- package/components/SiteHeader.astro +1130 -0
- package/content/collections.ts +40 -0
- package/content/filters.ts +107 -0
- package/content/schemas.ts +210 -0
- package/layouts/Base.astro +414 -0
- package/lib/mailLinks.ts +13 -0
- package/lib/mapsLink.ts +7 -0
- package/lib/siteSettings.ts +78 -0
- package/package.json +48 -0
- package/styles/fonts.css +76 -0
- package/styles/theme.css +90 -0
- package/utils/excerpt.ts +56 -0
- package/utils/formatDate.ts +22 -0
- package/utils/freshness.ts +5 -0
- package/utils/scrollReveal.ts +40 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Site-wide footer: site name, a one-line description, a Facebook /
|
|
4
|
+
* Contact link row, then a legal-nav + copyright strip. Extracted from
|
|
5
|
+
* Base.astro so changing footer links is a JSON edit, not a layout edit.
|
|
6
|
+
*
|
|
7
|
+
* Split masthead at 48em+ (768px, matching --bp-tablet): name/description
|
|
8
|
+
* left, Facebook/Contact right, one row — below that it's a centred stack.
|
|
9
|
+
* Fills the page's width deliberately on desktop instead of sitting as a
|
|
10
|
+
* small centred island inside a full-bleed band.
|
|
11
|
+
*
|
|
12
|
+
* Follows the page's light/dark theme like every other component. Colour
|
|
13
|
+
* rule throughout: accent links/labels are `--accent` in light mode and
|
|
14
|
+
* `--gold` in dark mode, never the reverse — `--gold` measures only
|
|
15
|
+
* ~1.75:1 against the light-mode footer background (fails WCAG AA badly),
|
|
16
|
+
* it only reads well on the dark background. Checked with the WCAG 2.1
|
|
17
|
+
* relative-luminance formula before writing the CSS below, not after.
|
|
18
|
+
*/
|
|
19
|
+
import type { NavLink } from '../content/schemas';
|
|
20
|
+
|
|
21
|
+
interface Props {
|
|
22
|
+
facebookUrl: string;
|
|
23
|
+
footerLinks: NavLink[];
|
|
24
|
+
footerClass?: string;
|
|
25
|
+
/** Brand / site name shown in the footer masthead. */
|
|
26
|
+
shortName: string;
|
|
27
|
+
/** Optional one-line tagline under the brand. Client-provided; omitted if blank. */
|
|
28
|
+
tagline?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const { facebookUrl, footerLinks, footerClass, shortName, tagline } = Astro.props;
|
|
32
|
+
const year = new Date().getFullYear();
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
<footer class:list={['site-footer', footerClass]}>
|
|
36
|
+
<div class="footer-top">
|
|
37
|
+
<div class="footer-row">
|
|
38
|
+
<div class="footer-copyblock">
|
|
39
|
+
<p class="footer-brand">{shortName}</p>
|
|
40
|
+
{tagline && <p class="footer-blurb">{tagline}</p>}
|
|
41
|
+
</div>
|
|
42
|
+
<div class="footer-social">
|
|
43
|
+
{facebookUrl && (
|
|
44
|
+
<>
|
|
45
|
+
<a class="footer-social__link" href={facebookUrl} target="_blank" rel="noopener noreferrer">
|
|
46
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
47
|
+
<path d="M22 12c0-5.522-4.477-10-10-10S2 6.478 2 12c0 4.991 3.657 9.128 8.438 9.878v-6.987H7.898V12h2.54V9.797c0-2.506 1.492-3.89 3.777-3.89 1.094 0 2.238.195 2.238.195v2.46h-1.26c-1.243 0-1.63.771-1.63 1.562V12h2.773l-.443 2.89h-2.33v6.988C18.343 21.128 22 16.991 22 12z"></path>
|
|
48
|
+
</svg>
|
|
49
|
+
Facebook
|
|
50
|
+
</a>
|
|
51
|
+
<span class="footer-social__divider" aria-hidden="true"></span>
|
|
52
|
+
</>
|
|
53
|
+
)}
|
|
54
|
+
<a class="footer-social__link" href="/contact/">
|
|
55
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
56
|
+
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
|
|
57
|
+
</svg>
|
|
58
|
+
Contact
|
|
59
|
+
</a>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<div class="footer-bottom">
|
|
65
|
+
<nav class="footer-nav" aria-label="Footer navigation">
|
|
66
|
+
{footerLinks.map((link) => (
|
|
67
|
+
<a href={link.href} set:html={link.label} />
|
|
68
|
+
))}
|
|
69
|
+
<button type="button" id="footer-cookie-preferences">Cookie preferences</button>
|
|
70
|
+
</nav>
|
|
71
|
+
<p class="footer-copy">© {year} All rights reserved.</p>
|
|
72
|
+
</div>
|
|
73
|
+
</footer>
|
|
74
|
+
|
|
75
|
+
<script>
|
|
76
|
+
document.getElementById('footer-cookie-preferences')?.addEventListener('click', () => {
|
|
77
|
+
document.dispatchEvent(new CustomEvent('open-cookie-preferences'));
|
|
78
|
+
});
|
|
79
|
+
</script>
|
|
80
|
+
|
|
81
|
+
<style is:global>
|
|
82
|
+
/*
|
|
83
|
+
* Every rule below is qualified under `.site-footer` on purpose, even
|
|
84
|
+
* where a bare class would normally do. Base.astro's global stylesheet
|
|
85
|
+
* carries `[data-theme="dark"] p { color: #C8D4C2 }` and
|
|
86
|
+
* `[data-theme="dark"] a { color: #E8A84A }` — both specificity (0,1,1).
|
|
87
|
+
* A bare `.footer-brand { color: ... }` is only (0,1,0) and loses that
|
|
88
|
+
* fight in dark mode. Prefixing with `.site-footer` (0,2,0/1) wins
|
|
89
|
+
* outright instead of depending on stylesheet order.
|
|
90
|
+
*/
|
|
91
|
+
.site-footer {
|
|
92
|
+
font-size: 0.85rem;
|
|
93
|
+
background: var(--bg-tag);
|
|
94
|
+
color: var(--fg);
|
|
95
|
+
}
|
|
96
|
+
[data-theme="dark"] .site-footer {
|
|
97
|
+
background: var(--dark-bg);
|
|
98
|
+
color: var(--text-light);
|
|
99
|
+
}
|
|
100
|
+
.site-footer a {
|
|
101
|
+
text-decoration: none;
|
|
102
|
+
}
|
|
103
|
+
.site-footer a:focus-visible,
|
|
104
|
+
.site-footer button:focus-visible {
|
|
105
|
+
outline: 2px solid var(--gold);
|
|
106
|
+
outline-offset: 2px;
|
|
107
|
+
border-radius: 4px;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* ── Top: title, description, social row ──
|
|
111
|
+
Below 48em (768px): centred stack, same as before. At 768px+: a split
|
|
112
|
+
masthead — name/description left, Facebook/Contact right — so the
|
|
113
|
+
top section uses the page's width instead of sitting as a small
|
|
114
|
+
centred island inside it. */
|
|
115
|
+
.site-footer .footer-top {
|
|
116
|
+
max-width: 72rem;
|
|
117
|
+
margin-inline: auto;
|
|
118
|
+
padding: clamp(1.75rem, 4vw, 2.5rem) 1.25rem clamp(1.5rem, 3vw, 2rem);
|
|
119
|
+
}
|
|
120
|
+
.site-footer .footer-row {
|
|
121
|
+
display: flex;
|
|
122
|
+
flex-direction: column;
|
|
123
|
+
align-items: center;
|
|
124
|
+
gap: 0.5rem;
|
|
125
|
+
text-align: center;
|
|
126
|
+
}
|
|
127
|
+
.site-footer .footer-copyblock {
|
|
128
|
+
display: flex;
|
|
129
|
+
flex-direction: column;
|
|
130
|
+
gap: 0.5rem;
|
|
131
|
+
}
|
|
132
|
+
.site-footer .footer-brand {
|
|
133
|
+
margin: 0;
|
|
134
|
+
font-family: 'Poppins', sans-serif;
|
|
135
|
+
font-weight: 700;
|
|
136
|
+
font-size: 1.15rem;
|
|
137
|
+
line-height: 1.3;
|
|
138
|
+
color: inherit;
|
|
139
|
+
}
|
|
140
|
+
.site-footer .footer-blurb {
|
|
141
|
+
margin: 0;
|
|
142
|
+
font-size: 0.85rem;
|
|
143
|
+
font-weight: 400;
|
|
144
|
+
line-height: 1.5;
|
|
145
|
+
color: var(--muted);
|
|
146
|
+
}
|
|
147
|
+
.site-footer .footer-social {
|
|
148
|
+
display: flex;
|
|
149
|
+
align-items: center;
|
|
150
|
+
gap: 1.25rem;
|
|
151
|
+
margin-top: 0.35rem;
|
|
152
|
+
}
|
|
153
|
+
@media (min-width: 48em) {
|
|
154
|
+
.site-footer .footer-row {
|
|
155
|
+
flex-direction: row;
|
|
156
|
+
justify-content: space-between;
|
|
157
|
+
text-align: left;
|
|
158
|
+
}
|
|
159
|
+
.site-footer .footer-social {
|
|
160
|
+
margin-top: 0;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
.site-footer .footer-social__link {
|
|
164
|
+
display: inline-flex;
|
|
165
|
+
align-items: center;
|
|
166
|
+
gap: 0.4rem;
|
|
167
|
+
min-height: 44px;
|
|
168
|
+
font-size: 0.92rem;
|
|
169
|
+
font-weight: 600;
|
|
170
|
+
color: var(--accent);
|
|
171
|
+
}
|
|
172
|
+
[data-theme="dark"] .site-footer .footer-social__link {
|
|
173
|
+
color: var(--gold);
|
|
174
|
+
}
|
|
175
|
+
.site-footer .footer-social__divider {
|
|
176
|
+
width: 1px;
|
|
177
|
+
height: 1.4rem;
|
|
178
|
+
background: var(--border);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* ── Legal nav + copyright ── */
|
|
182
|
+
.site-footer .footer-bottom {
|
|
183
|
+
border-top: 1px solid var(--border);
|
|
184
|
+
padding: 1.1rem clamp(1.25rem, 4vw, 2.25rem);
|
|
185
|
+
max-width: 72rem;
|
|
186
|
+
margin-inline: auto;
|
|
187
|
+
display: flex;
|
|
188
|
+
flex-direction: column;
|
|
189
|
+
align-items: center;
|
|
190
|
+
text-align: center;
|
|
191
|
+
gap: 0.6rem 1.5rem;
|
|
192
|
+
}
|
|
193
|
+
.site-footer .footer-nav {
|
|
194
|
+
display: flex;
|
|
195
|
+
flex-wrap: wrap;
|
|
196
|
+
justify-content: center;
|
|
197
|
+
gap: 0.3rem 1rem;
|
|
198
|
+
}
|
|
199
|
+
.site-footer .footer-nav a,
|
|
200
|
+
.site-footer .footer-nav button {
|
|
201
|
+
min-height: 44px;
|
|
202
|
+
display: inline-flex;
|
|
203
|
+
align-items: center;
|
|
204
|
+
font-family: inherit;
|
|
205
|
+
font-size: 0.68rem;
|
|
206
|
+
font-weight: 400;
|
|
207
|
+
letter-spacing: 0.02em;
|
|
208
|
+
color: var(--accent);
|
|
209
|
+
background: none;
|
|
210
|
+
border: none;
|
|
211
|
+
padding: 0;
|
|
212
|
+
cursor: pointer;
|
|
213
|
+
}
|
|
214
|
+
[data-theme="dark"] .site-footer .footer-nav a,
|
|
215
|
+
[data-theme="dark"] .site-footer .footer-nav button {
|
|
216
|
+
color: var(--gold);
|
|
217
|
+
}
|
|
218
|
+
.site-footer .footer-copy {
|
|
219
|
+
margin: 0;
|
|
220
|
+
font-family: 'Inter', sans-serif;
|
|
221
|
+
font-size: 0.68rem;
|
|
222
|
+
font-weight: 400;
|
|
223
|
+
color: var(--muted);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/* 48em / 768px, matching --bp-tablet in theme.css — the bottom bar's
|
|
227
|
+
row layout (legal nav | copyright, space-between) gets cramped once
|
|
228
|
+
the nav wraps below it, so it stacks and centres instead. */
|
|
229
|
+
@media (min-width: 48em) {
|
|
230
|
+
.site-footer .footer-bottom {
|
|
231
|
+
flex-direction: row;
|
|
232
|
+
justify-content: space-between;
|
|
233
|
+
text-align: left;
|
|
234
|
+
}
|
|
235
|
+
.site-footer .footer-nav {
|
|
236
|
+
justify-content: flex-start;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
@media (max-width: 22.5em) {
|
|
241
|
+
.site-footer .footer-social {
|
|
242
|
+
gap: 1rem;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
</style>
|