ferst-core 0.1.0 → 0.2.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 +102 -201
- package/LICENSE-Apache-2.0 +201 -0
- package/NOTICE +24 -5
- package/README.md +89 -73
- package/bin/check-thin.mjs +74 -0
- package/components/BlockRenderer.astro +89 -0
- package/components/Button.astro +85 -33
- package/components/CalendarEmbed.astro +188 -0
- package/components/Card.astro +7 -17
- package/components/Icon.astro +35 -0
- package/components/PostArticle.astro +133 -0
- package/components/PostCard.astro +85 -225
- package/components/SiteFooter.astro +2 -2
- package/components/SiteHeader.astro +104 -51
- package/components/blocks/Badge.astro +29 -0
- package/components/blocks/ButtonBlock.astro +13 -0
- package/components/blocks/Divider.astro +12 -0
- package/components/blocks/Grid.astro +48 -0
- package/components/blocks/Heading.astro +42 -0
- package/components/blocks/ImageBlock.astro +80 -0
- package/components/blocks/List.astro +78 -0
- package/components/blocks/Prose.astro +29 -0
- package/components/blocks/Quote.astro +32 -0
- package/components/blocks/Section.astro +52 -0
- package/components/blocks/Spacer.astro +20 -0
- package/components/blocks/Stack.astro +30 -0
- package/components/blocks/Stat.astro +30 -0
- package/components/recipes/Accordion.astro +84 -0
- package/components/recipes/Announcement.astro +74 -0
- package/components/recipes/Banner.astro +134 -0
- package/components/recipes/Bento.astro +138 -0
- package/components/recipes/ContactForm.astro +172 -0
- package/components/recipes/Cta.astro +83 -0
- package/components/recipes/Faq.astro +80 -0
- package/components/recipes/FeatureCards.astro +105 -0
- package/components/recipes/Gallery.astro +55 -0
- package/components/recipes/Hero.astro +191 -0
- package/components/recipes/Logos.astro +77 -0
- package/components/recipes/Marquee.astro +75 -0
- package/components/recipes/Pricing.astro +149 -0
- package/components/recipes/Stats.astro +124 -0
- package/components/recipes/Steps.astro +120 -0
- package/components/recipes/Tabs.astro +133 -0
- package/components/recipes/Testimonial.astro +66 -0
- package/components/recipes/Tiles.astro +248 -0
- package/content/blocks.ts +534 -0
- package/content/calendar.ts +40 -0
- package/content/collections.ts +25 -14
- package/content/posts.ts +89 -0
- package/content/schemas.ts +58 -123
- package/layouts/Base.astro +30 -56
- package/lib/calendar.ts +12 -0
- package/lib/icons.ts +64 -0
- package/lib/pages.ts +42 -0
- package/lib/posts.ts +93 -0
- package/lib/siteSettings.ts +18 -36
- package/lib/themeTokens.ts +163 -0
- package/package.json +61 -48
- package/styles/theme.css +42 -0
- package/components/DocLayout.astro +0 -292
- package/components/GroupCard.astro +0 -164
- package/components/LatestPostList.astro +0 -64
- package/components/PaginationNav.astro +0 -81
- package/components/PostsToolbar.astro +0 -83
- package/content/filters.ts +0 -107
- package/lib/mailLinks.ts +0 -13
- package/lib/mapsLink.ts +0 -7
- package/utils/excerpt.ts +0 -56
- package/utils/formatDate.ts +0 -22
- package/utils/freshness.ts +0 -5
|
@@ -1,262 +1,122 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
/**
|
|
3
|
+
* PostCard — a post preview for the news index and tag pages. Token-driven (radius,
|
|
4
|
+
* border, shadow, colours all from the theme), equal-height in a grid, with the
|
|
5
|
+
* whole card clickable via a stretched title link. White-room (no CTK code).
|
|
6
|
+
*/
|
|
8
7
|
interface Props {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
href: string;
|
|
9
|
+
title: string;
|
|
10
|
+
date: Date | string;
|
|
11
|
+
summary?: string;
|
|
12
|
+
heroImage?: string;
|
|
13
|
+
/** Optional tag labels shown as a small eyebrow. */
|
|
14
|
+
tagLabels?: string[];
|
|
15
15
|
}
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
// it also carries an attachment (offered there as a separate link).
|
|
23
|
-
const directAttachment = opensAttachmentDirectly(post);
|
|
24
|
-
const href = directAttachment ? post.data.attachment! : `/posts/${post.slug}`;
|
|
25
|
-
const linkTarget = directAttachment ? '_blank' : undefined;
|
|
26
|
-
const linkRel = directAttachment ? 'noopener noreferrer' : undefined;
|
|
27
|
-
const displayTags = post.data.tags;
|
|
28
|
-
const isFresh = isFreshPost(post.data.date);
|
|
29
|
-
const dateLabel = formatDate(post.data.date);
|
|
30
|
-
const hasImage = Boolean(post.data.image);
|
|
31
|
-
const excerpt = showExcerpt ? plainTextExcerpt(post.body ?? '', withImage ? 400 : 280) : '';
|
|
16
|
+
const { href, title, date, summary, heroImage, tagLabels = [] } = Astro.props;
|
|
17
|
+
const d = date instanceof Date ? date : new Date(date);
|
|
18
|
+
const iso = Number.isNaN(d.valueOf()) ? undefined : d.toISOString().slice(0, 10);
|
|
19
|
+
const display = Number.isNaN(d.valueOf())
|
|
20
|
+
? ''
|
|
21
|
+
: d.toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric' });
|
|
32
22
|
---
|
|
33
23
|
|
|
34
|
-
<article class="post-card"
|
|
35
|
-
{
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
hasImage ? (
|
|
40
|
-
<img
|
|
41
|
-
src={post.data.image}
|
|
42
|
-
alt={post.data.title}
|
|
43
|
-
class="post-card__img"
|
|
44
|
-
loading="lazy"
|
|
45
|
-
decoding="async"
|
|
46
|
-
/>
|
|
47
|
-
) : (
|
|
48
|
-
<div class="post-card__placeholder" aria-hidden="true"></div>
|
|
49
|
-
)
|
|
24
|
+
<article class="b-post-card">
|
|
25
|
+
{heroImage && (
|
|
26
|
+
<a class="b-post-card__media" href={href} tabindex="-1" aria-hidden="true">
|
|
27
|
+
<img src={heroImage} alt="" loading="lazy" />
|
|
28
|
+
</a>
|
|
50
29
|
)}
|
|
51
|
-
<div class="post-
|
|
52
|
-
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
<span class="post-tag">{tag}</span>
|
|
56
|
-
))}
|
|
57
|
-
</div>
|
|
58
|
-
)}
|
|
59
|
-
|
|
60
|
-
<h3>{linkWholeCard ? <a href={href} target={linkTarget} rel={linkRel}>{post.data.title}</a> : post.data.title}</h3>
|
|
61
|
-
|
|
62
|
-
{excerpt ? <p>{excerpt}</p> : null}
|
|
63
|
-
|
|
64
|
-
<div class="post-card__footer">
|
|
65
|
-
<time datetime={post.data.date.toISOString()}>{dateLabel}</time>
|
|
66
|
-
{linkWholeCard ? (
|
|
67
|
-
<svg class="post-card__chevron" 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">
|
|
68
|
-
<polyline points="9 6 15 12 9 18"/>
|
|
69
|
-
</svg>
|
|
70
|
-
) : (
|
|
71
|
-
<a href={href} target={linkTarget} rel={linkRel} aria-label={`Read ${post.data.title}`}>
|
|
72
|
-
<svg class="post-card__chevron" 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">
|
|
73
|
-
<polyline points="9 6 15 12 9 18"/>
|
|
74
|
-
</svg>
|
|
75
|
-
</a>
|
|
76
|
-
)}
|
|
30
|
+
<div class="b-post-card__body">
|
|
31
|
+
<div class="b-post-card__meta">
|
|
32
|
+
{tagLabels.length > 0 && <span class="b-post-card__eyebrow">{tagLabels[0]}</span>}
|
|
33
|
+
{display && <time datetime={iso}>{display}</time>}
|
|
77
34
|
</div>
|
|
35
|
+
<h3 class="b-post-card__title">
|
|
36
|
+
<a href={href}>{title}</a>
|
|
37
|
+
</h3>
|
|
38
|
+
{summary && <p class="b-post-card__summary">{summary}</p>}
|
|
39
|
+
<span class="b-post-card__more" aria-hidden="true">Read more</span>
|
|
78
40
|
</div>
|
|
79
41
|
</article>
|
|
80
42
|
|
|
81
43
|
<style>
|
|
82
|
-
.post-card {
|
|
83
|
-
position: relative;
|
|
84
|
-
padding: 0;
|
|
85
|
-
border-bottom: none;
|
|
86
|
-
overflow: hidden;
|
|
44
|
+
.b-post-card {
|
|
87
45
|
display: flex;
|
|
88
46
|
flex-direction: column;
|
|
89
|
-
|
|
47
|
+
background: var(--bg-surface);
|
|
48
|
+
border: var(--card-border);
|
|
49
|
+
border-radius: var(--radius);
|
|
50
|
+
box-shadow: var(--shadow-sm);
|
|
51
|
+
overflow: hidden;
|
|
52
|
+
position: relative;
|
|
53
|
+
transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
|
|
90
54
|
}
|
|
91
|
-
|
|
92
|
-
|
|
55
|
+
.b-post-card:hover {
|
|
56
|
+
transform: translateY(-2px);
|
|
57
|
+
box-shadow: var(--shadow-md);
|
|
58
|
+
border-color: var(--gold);
|
|
59
|
+
}
|
|
60
|
+
.b-post-card__media {
|
|
61
|
+
display: block;
|
|
62
|
+
aspect-ratio: 16 / 9;
|
|
63
|
+
background: var(--bg-section);
|
|
64
|
+
}
|
|
65
|
+
.b-post-card__media img {
|
|
66
|
+
width: 100%;
|
|
67
|
+
height: 100%;
|
|
68
|
+
object-fit: cover;
|
|
69
|
+
display: block;
|
|
70
|
+
}
|
|
71
|
+
.b-post-card__body {
|
|
93
72
|
display: flex;
|
|
94
73
|
flex-direction: column;
|
|
74
|
+
gap: 0.5rem;
|
|
75
|
+
padding: 1.1rem 1.2rem 1.3rem;
|
|
95
76
|
flex: 1;
|
|
96
|
-
min-height: 0;
|
|
97
|
-
gap: 0.75rem;
|
|
98
|
-
padding: clamp(0.875rem, 2.2vw, 1.25rem);
|
|
99
|
-
color: var(--news-card-body);
|
|
100
77
|
}
|
|
101
|
-
|
|
102
|
-
.post-tags {
|
|
78
|
+
.b-post-card__meta {
|
|
103
79
|
display: flex;
|
|
104
80
|
flex-wrap: wrap;
|
|
105
|
-
justify-content: flex-end;
|
|
106
|
-
gap: 0.35rem;
|
|
107
|
-
margin: 0;
|
|
108
|
-
width: 100%;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
.post-tag {
|
|
112
|
-
display: inline-flex;
|
|
113
81
|
align-items: center;
|
|
114
|
-
|
|
115
|
-
|
|
82
|
+
gap: 0.5rem 0.75rem;
|
|
83
|
+
}
|
|
84
|
+
.b-post-card__eyebrow {
|
|
85
|
+
font-family: var(--font-heading);
|
|
86
|
+
font-size: 0.7rem;
|
|
116
87
|
font-weight: 600;
|
|
88
|
+
letter-spacing: 0.08em;
|
|
117
89
|
text-transform: uppercase;
|
|
118
|
-
|
|
119
|
-
color: var(--brown);
|
|
120
|
-
background: rgba(180, 145, 70, 0.22);
|
|
121
|
-
border-radius: 4px;
|
|
122
|
-
padding: 0.15rem 0.45rem;
|
|
123
|
-
line-height: 1.2;
|
|
124
|
-
}
|
|
125
|
-
:global(html[data-theme="dark"]) .post-tag {
|
|
126
|
-
color: var(--text-light);
|
|
127
|
-
background: rgba(255, 255, 255, 0.12);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
.post-content h3 {
|
|
131
|
-
/* Full title, never truncated — the admin's exact wording is shown
|
|
132
|
-
in full, however many lines it takes. */
|
|
133
|
-
margin-block: 0;
|
|
90
|
+
color: var(--gold);
|
|
134
91
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
the whole card (positioned against .post-card, the nearest positioned
|
|
138
|
-
ancestor), so clicking anywhere on the tile opens the post — no JS
|
|
139
|
-
needed. The footer "Read" link sits above it via z-index so it stays
|
|
140
|
-
independently clickable/focusable. */
|
|
141
|
-
.post-card h3 a {
|
|
142
|
-
color: inherit;
|
|
143
|
-
text-decoration: none;
|
|
92
|
+
.b-post-card__title {
|
|
93
|
+
margin: 0;
|
|
144
94
|
}
|
|
145
|
-
|
|
146
|
-
|
|
95
|
+
/* Stretch the title link over the whole card so the card is clickable, while
|
|
96
|
+
the meta/summary stay selectable. */
|
|
97
|
+
.b-post-card__title a::after {
|
|
147
98
|
content: '';
|
|
148
99
|
position: absolute;
|
|
149
100
|
inset: 0;
|
|
150
|
-
z-index: 1;
|
|
151
101
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
position: absolute;
|
|
155
|
-
top: clamp(0.5rem, 1.6vw, 0.75rem);
|
|
156
|
-
left: clamp(0.5rem, 1.6vw, 0.75rem);
|
|
157
|
-
z-index: 2;
|
|
158
|
-
display: inline-block;
|
|
159
|
-
font-family: 'Poppins', sans-serif;
|
|
160
|
-
font-size: 0.6875rem;
|
|
161
|
-
font-weight: 700;
|
|
162
|
-
letter-spacing: 0.12em;
|
|
163
|
-
text-transform: uppercase;
|
|
164
|
-
color: #fff;
|
|
165
|
-
background: var(--accent);
|
|
166
|
-
border-radius: 4px;
|
|
167
|
-
padding: 0.22rem 0.55rem;
|
|
168
|
-
line-height: 1.2;
|
|
169
|
-
transform: rotate(-4deg);
|
|
170
|
-
transform-origin: top left;
|
|
171
|
-
box-shadow:
|
|
172
|
-
0 2px 6px rgba(0, 0, 0, 0.18),
|
|
173
|
-
0 1px 2px rgba(0, 0, 0, 0.08);
|
|
174
|
-
pointer-events: none;
|
|
102
|
+
.b-post-card__title a {
|
|
103
|
+
color: var(--fg);
|
|
175
104
|
}
|
|
176
|
-
:
|
|
177
|
-
|
|
178
|
-
color: #2A1F12;
|
|
105
|
+
.b-post-card:hover .b-post-card__title a {
|
|
106
|
+
color: var(--gold);
|
|
179
107
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
108
|
+
.b-post-card__summary {
|
|
109
|
+
margin: 0;
|
|
110
|
+
color: var(--muted);
|
|
111
|
+
font-size: 0.95rem;
|
|
183
112
|
line-height: 1.55;
|
|
184
|
-
display: -webkit-box;
|
|
185
|
-
-webkit-line-clamp: 3;
|
|
186
|
-
-webkit-box-orient: vertical;
|
|
187
|
-
overflow: hidden;
|
|
188
113
|
}
|
|
189
|
-
|
|
190
|
-
/* Date on the left, chevron on the right — chevron keeps the same
|
|
191
|
-
slide-right-on-hover language as the homepage's Latest from the
|
|
192
|
-
Notice rows (.notice-row / .notice-chevron). */
|
|
193
|
-
.post-card__footer {
|
|
194
|
-
display: flex;
|
|
195
|
-
justify-content: space-between;
|
|
196
|
-
align-items: center;
|
|
197
|
-
gap: 0.4rem;
|
|
114
|
+
.b-post-card__more {
|
|
198
115
|
margin-top: auto;
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
.post-card__footer a {
|
|
206
|
-
position: relative;
|
|
207
|
-
z-index: 2;
|
|
208
|
-
display: inline-flex;
|
|
209
|
-
align-items: center;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
.post-card__chevron {
|
|
213
|
-
flex-shrink: 0;
|
|
214
|
-
color: var(--muted);
|
|
215
|
-
transition: transform 0.15s ease, color 0.15s ease;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/* Decorative in linkWholeCard mode (the real link is the title's
|
|
219
|
-
stretched-link), so it reacts to hover on the whole card. */
|
|
220
|
-
.post-card:hover .post-card__chevron {
|
|
221
|
-
transform: translateX(3px);
|
|
116
|
+
padding-top: 0.4rem;
|
|
117
|
+
font-family: var(--font-heading);
|
|
118
|
+
font-size: 0.85rem;
|
|
119
|
+
font-weight: 600;
|
|
222
120
|
color: var(--gold);
|
|
223
121
|
}
|
|
224
|
-
|
|
225
|
-
/* With-image variant: opt-in via the `withImage` prop, used by the
|
|
226
|
-
/posts/ listing. Image always sits above the text, at every screen
|
|
227
|
-
size. */
|
|
228
|
-
.post-card--with-image .post-card__img,
|
|
229
|
-
.post-card--with-image .post-card__placeholder {
|
|
230
|
-
display: block;
|
|
231
|
-
width: 100%;
|
|
232
|
-
aspect-ratio: 16 / 9;
|
|
233
|
-
border-radius: 10px 10px 0 0;
|
|
234
|
-
margin: 0;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
.post-card--with-image .post-card__img {
|
|
238
|
-
object-fit: cover;
|
|
239
|
-
object-position: center;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
.post-card--with-image .post-card__placeholder {
|
|
243
|
-
background: linear-gradient(135deg, var(--gold-light) 0%, var(--gold) 100%);
|
|
244
|
-
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
:global(html[data-theme="dark"]) .post-card--with-image .post-card__placeholder {
|
|
248
|
-
background: linear-gradient(135deg, #4A6741 0%, #2C3B2A 100%);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
.post-card--with-image .post-content > p {
|
|
252
|
-
-webkit-line-clamp: 5;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
.post-card--with-image .post-tags {
|
|
256
|
-
justify-content: flex-start;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
.post-card--with-image .post-content {
|
|
260
|
-
padding-top: clamp(1.5rem, 3.5vw, 2rem);
|
|
261
|
-
}
|
|
262
122
|
</style>
|
|
@@ -131,7 +131,7 @@ const year = new Date().getFullYear();
|
|
|
131
131
|
}
|
|
132
132
|
.site-footer .footer-brand {
|
|
133
133
|
margin: 0;
|
|
134
|
-
font-family:
|
|
134
|
+
font-family: var(--font-heading);
|
|
135
135
|
font-weight: 700;
|
|
136
136
|
font-size: 1.15rem;
|
|
137
137
|
line-height: 1.3;
|
|
@@ -217,7 +217,7 @@ const year = new Date().getFullYear();
|
|
|
217
217
|
}
|
|
218
218
|
.site-footer .footer-copy {
|
|
219
219
|
margin: 0;
|
|
220
|
-
font-family:
|
|
220
|
+
font-family: var(--font-body);
|
|
221
221
|
font-size: 0.68rem;
|
|
222
222
|
font-weight: 400;
|
|
223
223
|
color: var(--muted);
|
|
@@ -15,8 +15,8 @@ import type { NavLink, NavGroup } from '../content/schemas';
|
|
|
15
15
|
interface Props {
|
|
16
16
|
path: string;
|
|
17
17
|
headerClass?: string;
|
|
18
|
-
primaryLogoLight
|
|
19
|
-
primaryLogoDark
|
|
18
|
+
primaryLogoLight?: string;
|
|
19
|
+
primaryLogoDark?: string;
|
|
20
20
|
brandAlt: string;
|
|
21
21
|
primaryNav: NavLink[];
|
|
22
22
|
/** Optional dropdown / mega-menu group. Client-configured — the core ships none. */
|
|
@@ -56,28 +56,34 @@ const primaryNavLongestMatch = Math.max(-1, ...primaryNavMatchLengths);
|
|
|
56
56
|
-->
|
|
57
57
|
<header class:list={['site-header', headerClass]} transition:persist transition:name="site-header" transition:animate="none">
|
|
58
58
|
<a href="/" class="brand">
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
59
|
+
{primaryLogoLight ? (
|
|
60
|
+
<img
|
|
61
|
+
id="primary-logo"
|
|
62
|
+
src={primaryLogoLight}
|
|
63
|
+
data-src-light={primaryLogoLight}
|
|
64
|
+
data-src-dark={primaryLogoDark ?? primaryLogoLight}
|
|
65
|
+
alt={brandAlt}
|
|
66
|
+
class="primary-logo"
|
|
67
|
+
width="545"
|
|
68
|
+
height="151"
|
|
69
|
+
loading="eager"
|
|
70
|
+
fetchpriority="high"
|
|
71
|
+
decoding="sync"
|
|
72
|
+
/>
|
|
73
|
+
) : (
|
|
74
|
+
<span class="brand-wordmark">{brandAlt}</span>
|
|
75
|
+
)}
|
|
72
76
|
</a>
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
{primaryLogoLight && (
|
|
78
|
+
<script is:inline define:vars={{ primaryLogoDark, primaryLogoLight }}>
|
|
79
|
+
(function () {
|
|
80
|
+
var img = document.getElementById('primary-logo');
|
|
81
|
+
if (!img) return;
|
|
82
|
+
var t = document.documentElement.dataset.theme;
|
|
83
|
+
img.src = t === 'dark' ? (primaryLogoDark || primaryLogoLight) : primaryLogoLight;
|
|
84
|
+
})();
|
|
85
|
+
</script>
|
|
86
|
+
)}
|
|
81
87
|
|
|
82
88
|
<nav class="site-nav" id="site-nav" aria-label="Main navigation">
|
|
83
89
|
{dropdown && (
|
|
@@ -196,6 +202,21 @@ const primaryNavLongestMatch = Math.max(-1, ...primaryNavMatchLengths);
|
|
|
196
202
|
width: auto;
|
|
197
203
|
}
|
|
198
204
|
|
|
205
|
+
/* Text wordmark fallback — shown when a client has configured no logo image
|
|
206
|
+
(var(--fg) auto-switches for dark mode via theme.css). */
|
|
207
|
+
.brand-wordmark {
|
|
208
|
+
font-family: var(--font-heading);
|
|
209
|
+
font-weight: 700;
|
|
210
|
+
font-size: 1.25rem;
|
|
211
|
+
line-height: 1;
|
|
212
|
+
color: var(--fg);
|
|
213
|
+
letter-spacing: 0.01em;
|
|
214
|
+
white-space: nowrap;
|
|
215
|
+
}
|
|
216
|
+
@media (min-width: 64em) {
|
|
217
|
+
.brand-wordmark { font-size: 1.5rem; }
|
|
218
|
+
}
|
|
219
|
+
|
|
199
220
|
/* ── Header ── */
|
|
200
221
|
/* Logo + header: mobile-first — compact until desktop ≥64em scales up (matches button/theme sizing). */
|
|
201
222
|
.site-header {
|
|
@@ -218,6 +239,31 @@ const primaryNavLongestMatch = Math.max(-1, ...primaryNavMatchLengths);
|
|
|
218
239
|
}
|
|
219
240
|
}
|
|
220
241
|
[data-theme="dark"] .site-header { background: var(--dark-bg); }
|
|
242
|
+
|
|
243
|
+
/* Sticky "shrink on scroll": the header condenses and gains a shadow once the
|
|
244
|
+
page is scrolled, then expands again at the top. Purely additive; safe with
|
|
245
|
+
the sticky positioning. Motion honoured. */
|
|
246
|
+
.site-header {
|
|
247
|
+
transition: height 0.25s ease, box-shadow 0.25s ease;
|
|
248
|
+
}
|
|
249
|
+
.site-header.is-compact {
|
|
250
|
+
box-shadow: 0 2px 14px rgba(15, 23, 42, 0.10);
|
|
251
|
+
height: 50px;
|
|
252
|
+
}
|
|
253
|
+
@media (min-width: 64em) {
|
|
254
|
+
.site-header.is-compact { height: 54px; }
|
|
255
|
+
}
|
|
256
|
+
.site-header .primary-logo,
|
|
257
|
+
.site-header .brand-wordmark {
|
|
258
|
+
transition: height 0.25s ease, font-size 0.25s ease;
|
|
259
|
+
}
|
|
260
|
+
.site-header.is-compact .primary-logo { height: 38px; }
|
|
261
|
+
.site-header.is-compact .brand-wordmark { font-size: 1.15rem; }
|
|
262
|
+
@media (prefers-reduced-motion: reduce) {
|
|
263
|
+
.site-header,
|
|
264
|
+
.site-header .primary-logo,
|
|
265
|
+
.site-header .brand-wordmark { transition: none; }
|
|
266
|
+
}
|
|
221
267
|
.brand {
|
|
222
268
|
text-decoration: none;
|
|
223
269
|
display: flex;
|
|
@@ -265,7 +311,7 @@ const primaryNavLongestMatch = Math.max(-1, ...primaryNavMatchLengths);
|
|
|
265
311
|
padding: 14px 0;
|
|
266
312
|
color: var(--accent);
|
|
267
313
|
text-decoration: none;
|
|
268
|
-
font-family:
|
|
314
|
+
font-family: var(--font-body);
|
|
269
315
|
font-size: 0.875rem;
|
|
270
316
|
font-weight: 500;
|
|
271
317
|
letter-spacing: 0.1em;
|
|
@@ -316,10 +362,10 @@ const primaryNavLongestMatch = Math.max(-1, ...primaryNavMatchLengths);
|
|
|
316
362
|
white-space: nowrap;
|
|
317
363
|
}
|
|
318
364
|
.nav-cta:hover,
|
|
319
|
-
.nav-cta.active { background: #
|
|
320
|
-
[data-theme="dark"] .nav-cta { background: var(--gold); color: var(--
|
|
365
|
+
.nav-cta.active { background: color-mix(in srgb, var(--gold) 85%, #000); color: var(--text-light); border: none; }
|
|
366
|
+
[data-theme="dark"] .nav-cta { background: var(--gold); color: var(--dark-bg); border: none; }
|
|
321
367
|
[data-theme="dark"] .nav-cta:hover,
|
|
322
|
-
[data-theme="dark"] .nav-cta.active { background: var(--
|
|
368
|
+
[data-theme="dark"] .nav-cta.active { background: color-mix(in srgb, var(--gold) 85%, #fff); color: var(--dark-bg); border: none; }
|
|
323
369
|
|
|
324
370
|
.nav-mobile-theme .nav-cta--drawer {
|
|
325
371
|
display: none;
|
|
@@ -389,7 +435,7 @@ const primaryNavLongestMatch = Math.max(-1, ...primaryNavMatchLengths);
|
|
|
389
435
|
}
|
|
390
436
|
.theme-icon--sun { opacity: 1; }
|
|
391
437
|
[data-theme="dark"] .theme-icon--sun { opacity: 0.5; }
|
|
392
|
-
[data-theme="dark"] .theme-icon--moon { opacity: 1; color:
|
|
438
|
+
[data-theme="dark"] .theme-icon--moon { opacity: 1; color: var(--dark-bg); }
|
|
393
439
|
|
|
394
440
|
/* Mobile theme toggle row — hidden on desktop */
|
|
395
441
|
.nav-mobile-theme { display: none; }
|
|
@@ -416,7 +462,7 @@ const primaryNavLongestMatch = Math.max(-1, ...primaryNavMatchLengths);
|
|
|
416
462
|
position: absolute;
|
|
417
463
|
width: 22px;
|
|
418
464
|
height: 2px;
|
|
419
|
-
background:
|
|
465
|
+
background: var(--fg);
|
|
420
466
|
border-radius: 2px;
|
|
421
467
|
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.2s ease, top 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
422
468
|
transform-origin: center;
|
|
@@ -501,7 +547,7 @@ const primaryNavLongestMatch = Math.max(-1, ...primaryNavMatchLengths);
|
|
|
501
547
|
margin: 0;
|
|
502
548
|
font-size: 1rem;
|
|
503
549
|
color: var(--accent);
|
|
504
|
-
font-family:
|
|
550
|
+
font-family: var(--font-heading);
|
|
505
551
|
font-weight: 600;
|
|
506
552
|
letter-spacing: 0.04em;
|
|
507
553
|
}
|
|
@@ -567,7 +613,7 @@ const primaryNavLongestMatch = Math.max(-1, ...primaryNavMatchLengths);
|
|
|
567
613
|
font-size: 0.875rem;
|
|
568
614
|
font-weight: 600;
|
|
569
615
|
color: var(--accent);
|
|
570
|
-
font-family:
|
|
616
|
+
font-family: var(--font-body);
|
|
571
617
|
line-height: 1.3;
|
|
572
618
|
transition: color 0.15s;
|
|
573
619
|
}
|
|
@@ -576,14 +622,14 @@ const primaryNavLongestMatch = Math.max(-1, ...primaryNavMatchLengths);
|
|
|
576
622
|
font-size: 0.75rem;
|
|
577
623
|
color: var(--accent);
|
|
578
624
|
font-weight: 500;
|
|
579
|
-
font-family:
|
|
625
|
+
font-family: var(--font-body);
|
|
580
626
|
line-height: 1.4;
|
|
581
627
|
}
|
|
582
628
|
.mega-about-sub {
|
|
583
629
|
font-size: 0.8rem;
|
|
584
630
|
color: var(--accent);
|
|
585
631
|
font-weight: 500;
|
|
586
|
-
font-family:
|
|
632
|
+
font-family: var(--font-body);
|
|
587
633
|
}
|
|
588
634
|
[data-theme="dark"] .mega-about-desc,
|
|
589
635
|
[data-theme="dark"] .mega-about-sub {
|
|
@@ -626,8 +672,8 @@ const primaryNavLongestMatch = Math.max(-1, ...primaryNavMatchLengths);
|
|
|
626
672
|
left: 50%;
|
|
627
673
|
transform: translateX(-50%);
|
|
628
674
|
min-width: 280px;
|
|
629
|
-
background:
|
|
630
|
-
border: 1px solid
|
|
675
|
+
background: var(--bg-surface);
|
|
676
|
+
border: 1px solid var(--border);
|
|
631
677
|
border-top: 2px solid var(--gold);
|
|
632
678
|
border-radius: 0 0 10px 10px;
|
|
633
679
|
box-shadow: 0 12px 32px rgba(0,0,0,0.35);
|
|
@@ -643,21 +689,21 @@ const primaryNavLongestMatch = Math.max(-1, ...primaryNavMatchLengths);
|
|
|
643
689
|
align-items: center;
|
|
644
690
|
gap: 12px;
|
|
645
691
|
padding: 0.65rem 1.1rem;
|
|
646
|
-
color:
|
|
692
|
+
color: var(--muted);
|
|
647
693
|
text-decoration: none;
|
|
648
694
|
transition: background 0.12s, color 0.12s;
|
|
649
695
|
border-radius: 0;
|
|
650
696
|
}
|
|
651
697
|
.nav-dropdown li a:hover {
|
|
652
698
|
background: rgba(200,151,58,0.1);
|
|
653
|
-
color:
|
|
699
|
+
color: var(--fg);
|
|
654
700
|
}
|
|
655
701
|
.nav-dropdown li a:focus-visible {
|
|
656
702
|
outline: 2px solid var(--gold);
|
|
657
703
|
outline-offset: -2px;
|
|
658
704
|
}
|
|
659
705
|
.nav-dropdown li a:hover .drop-title { color: var(--gold); }
|
|
660
|
-
.nav-dropdown li a.active { color:
|
|
706
|
+
.nav-dropdown li a.active { color: var(--fg); }
|
|
661
707
|
.nav-dropdown li a.active .drop-title { color: var(--gold); }
|
|
662
708
|
.nav-dropdown li a.active .drop-icon { background: rgba(200,151,58,0.25); }
|
|
663
709
|
.nav-dropdown li + li {
|
|
@@ -683,16 +729,16 @@ const primaryNavLongestMatch = Math.max(-1, ...primaryNavMatchLengths);
|
|
|
683
729
|
}
|
|
684
730
|
|
|
685
731
|
.drop-title {
|
|
686
|
-
font-family:
|
|
732
|
+
font-family: var(--font-body);
|
|
687
733
|
font-size: 0.8125rem;
|
|
688
734
|
font-weight: 600;
|
|
689
|
-
color:
|
|
735
|
+
color: var(--text-light);
|
|
690
736
|
line-height: 1.3;
|
|
691
737
|
transition: color 0.12s;
|
|
692
738
|
}
|
|
693
739
|
|
|
694
740
|
.drop-desc {
|
|
695
|
-
font-family:
|
|
741
|
+
font-family: var(--font-body);
|
|
696
742
|
font-size: 0.6875rem;
|
|
697
743
|
color: var(--text-subtle);
|
|
698
744
|
line-height: 1.3;
|
|
@@ -766,11 +812,11 @@ const primaryNavLongestMatch = Math.max(-1, ...primaryNavMatchLengths);
|
|
|
766
812
|
padding: 1rem 3rem;
|
|
767
813
|
font-size: 0.9375rem;
|
|
768
814
|
font-weight: 300;
|
|
769
|
-
font-family:
|
|
815
|
+
font-family: var(--font-heading);
|
|
770
816
|
border-radius: 0;
|
|
771
817
|
text-align: left;
|
|
772
818
|
width: 100%;
|
|
773
|
-
color:
|
|
819
|
+
color: var(--fg);
|
|
774
820
|
transition: color 0.18s ease;
|
|
775
821
|
}
|
|
776
822
|
.nav-link::before {
|
|
@@ -785,9 +831,9 @@ const primaryNavLongestMatch = Math.max(-1, ...primaryNavMatchLengths);
|
|
|
785
831
|
/* Last link (Calendar) sits directly above .nav-mobile-theme's own
|
|
786
832
|
border-top — drop its trailing divider so the two don't double up. */
|
|
787
833
|
.site-nav > .nav-link:last-of-type::before { display: none; }
|
|
788
|
-
.nav-link:hover { color:
|
|
834
|
+
.nav-link:hover { color: var(--gold); background: none; }
|
|
789
835
|
.nav-link::after { display: none; }
|
|
790
|
-
.nav-link.active { color:
|
|
836
|
+
.nav-link.active { color: var(--gold); font-weight: 400; }
|
|
791
837
|
|
|
792
838
|
.nav-mobile-theme {
|
|
793
839
|
display: flex;
|
|
@@ -831,19 +877,19 @@ const primaryNavLongestMatch = Math.max(-1, ...primaryNavMatchLengths);
|
|
|
831
877
|
gap: 10px;
|
|
832
878
|
justify-content: flex-start;
|
|
833
879
|
font-size: 0.8rem;
|
|
834
|
-
color:
|
|
880
|
+
color: var(--fg);
|
|
835
881
|
transition: color 0.18s ease;
|
|
836
882
|
}
|
|
837
|
-
.nav-dropdown li a:hover { color:
|
|
883
|
+
.nav-dropdown li a:hover { color: var(--gold); background: none; }
|
|
838
884
|
.drop-icon { width: 24px; height: 24px; flex-shrink: 0; transition: color 0.18s ease; }
|
|
839
|
-
.drop-title { font-size: 0.75rem; color:
|
|
885
|
+
.drop-title { font-size: 0.75rem; color: var(--fg); transition: color 0.18s ease; }
|
|
840
886
|
[data-theme="dark"] .nav-dropdown li a { color: rgba(250,246,239,0.75); }
|
|
841
887
|
[data-theme="dark"] .nav-dropdown li a:hover,
|
|
842
|
-
[data-theme="dark"] .nav-dropdown li a:active { color:
|
|
888
|
+
[data-theme="dark"] .nav-dropdown li a:active { color: var(--gold); }
|
|
843
889
|
[data-theme="dark"] .nav-dropdown li a:hover .drop-title,
|
|
844
|
-
[data-theme="dark"] .nav-dropdown li a:active .drop-title { color:
|
|
890
|
+
[data-theme="dark"] .nav-dropdown li a:active .drop-title { color: var(--gold); }
|
|
845
891
|
[data-theme="dark"] .nav-dropdown li a:hover .drop-icon,
|
|
846
|
-
[data-theme="dark"] .nav-dropdown li a:active .drop-icon { color:
|
|
892
|
+
[data-theme="dark"] .nav-dropdown li a:active .drop-icon { color: var(--gold); }
|
|
847
893
|
[data-theme="dark"] .drop-title { color: rgba(250,246,239,0.9); }
|
|
848
894
|
.drop-desc { display: none; }
|
|
849
895
|
.nav-dropdown li + li { border-top: 1px solid rgba(255,255,255,0.04); }
|
|
@@ -937,6 +983,13 @@ const primaryNavLongestMatch = Math.max(-1, ...primaryNavMatchLengths);
|
|
|
937
983
|
|
|
938
984
|
const siteHeader = document.querySelector('.site-header') as HTMLElement | null;
|
|
939
985
|
|
|
986
|
+
// Shrink-on-scroll: condense the header once the page is scrolled.
|
|
987
|
+
if (siteHeader) {
|
|
988
|
+
const onScrollCompact = () => siteHeader.classList.toggle('is-compact', window.scrollY > 40);
|
|
989
|
+
onScrollCompact();
|
|
990
|
+
window.addEventListener('scroll', onScrollCompact, { passive: true });
|
|
991
|
+
}
|
|
992
|
+
|
|
940
993
|
function openMega() {
|
|
941
994
|
if (!megaNav || !aboutTrigger) return;
|
|
942
995
|
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
|