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.
@@ -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">&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>