create-website-build-kit 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/README.md +54 -0
- package/index.mjs +149 -0
- package/package.json +42 -0
- package/template/.dev.vars.example +3 -0
- package/template/.github/workflows/gates.yml +58 -0
- package/template/.node-version +1 -0
- package/template/.pa11yci.json +24 -0
- package/template/BUILD-STATE.md +47 -0
- package/template/CLAUDE.md +153 -0
- package/template/astro.config.mjs +150 -0
- package/template/docs/analytics.md +86 -0
- package/template/docs/content.md +138 -0
- package/template/docs/handover.md +182 -0
- package/template/docs/handover.pdf +0 -0
- package/template/docs/runbook.md +661 -0
- package/template/docs/traps.md +903 -0
- package/template/gitignore +31 -0
- package/template/package-lock.json +8159 -0
- package/template/package.json +53 -0
- package/template/public/_headers +61 -0
- package/template/public/_redirects +39 -0
- package/template/public/site.webmanifest +13 -0
- package/template/scripts/a11y-evidence.mjs +258 -0
- package/template/scripts/check-console.mjs +125 -0
- package/template/scripts/check-env.mjs +99 -0
- package/template/scripts/check-reflow.mjs +148 -0
- package/template/scripts/check-sitemap.mjs +113 -0
- package/template/scripts/dns-snapshot.mjs +267 -0
- package/template/scripts/extract.mjs +317 -0
- package/template/scripts/indexnow.mjs +154 -0
- package/template/scripts/lastmod.mjs +147 -0
- package/template/scripts/lib/inventory.mjs +104 -0
- package/template/scripts/lib/preserved.mjs +42 -0
- package/template/scripts/lib/routes.mjs +92 -0
- package/template/scripts/md-to-pdf.mjs +335 -0
- package/template/scripts/og-cards.config.mjs +114 -0
- package/template/scripts/og-cards.mjs +487 -0
- package/template/scripts/optimize-media.mjs +380 -0
- package/template/scripts/recon.mjs +480 -0
- package/template/scripts/redirects.mjs +298 -0
- package/template/scripts/shots.mjs +447 -0
- package/template/scripts/staging-headers.mjs +102 -0
- package/template/scripts/tells.mjs +268 -0
- package/template/scripts/verify.mjs +1069 -0
- package/template/src/components/ContactForm.astro +405 -0
- package/template/src/components/CtaBand.astro +82 -0
- package/template/src/components/EnvBadge.astro +146 -0
- package/template/src/components/Footer.astro +210 -0
- package/template/src/components/Header.astro +530 -0
- package/template/src/components/Icon.astro +56 -0
- package/template/src/components/Img.astro +129 -0
- package/template/src/components/PageHero.astro +88 -0
- package/template/src/components/Seo.astro +119 -0
- package/template/src/components/StructuredData.astro +173 -0
- package/template/src/content/blog/.gitkeep +5 -0
- package/template/src/content/legal/.gitkeep +0 -0
- package/template/src/content.config.ts +81 -0
- package/template/src/data/areas.ts +31 -0
- package/template/src/data/business.ts +121 -0
- package/template/src/data/categories.ts +37 -0
- package/template/src/data/fonts.ts +25 -0
- package/template/src/data/image-manifest.json +1 -0
- package/template/src/data/lastmod.json +1 -0
- package/template/src/data/nav.ts +49 -0
- package/template/src/data/services.ts +39 -0
- package/template/src/data/site.ts +136 -0
- package/template/src/env.d.ts +28 -0
- package/template/src/layouts/Base.astro +223 -0
- package/template/src/lib/brevo.ts +96 -0
- package/template/src/lib/hast-media.mjs +55 -0
- package/template/src/lib/lastmod.mjs +47 -0
- package/template/src/lib/lead.ts +92 -0
- package/template/src/lib/legal-routes.mjs +31 -0
- package/template/src/lib/legal.ts +75 -0
- package/template/src/lib/posts.ts +64 -0
- package/template/src/lib/runtime.ts +33 -0
- package/template/src/pages/404.astro +51 -0
- package/template/src/pages/[slug].astro +111 -0
- package/template/src/pages/accessibility.astro +128 -0
- package/template/src/pages/api/contact.ts +191 -0
- package/template/src/pages/api/leads.csv.ts +82 -0
- package/template/src/pages/contact.astro +112 -0
- package/template/src/pages/index.astro +84 -0
- package/template/src/pages/robots.txt.ts +38 -0
- package/template/src/pages/rss.xml.ts +27 -0
- package/template/src/styles/global.css +463 -0
- package/template/src/styles/project.css +14 -0
- package/template/src/styles/prose.css +182 -0
- package/template/src/styles/tokens.css +218 -0
- package/template/tsconfig.json +5 -0
- package/template/wrangler.jsonc +63 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { business, addressOneLine } from '../data/business';
|
|
3
|
+
import { nav, legalNav } from '../data/nav';
|
|
4
|
+
import { getLegalLinks } from '../lib/legal';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Site footer.
|
|
8
|
+
*
|
|
9
|
+
* Structural, not styled. What matters here and must survive any redesign:
|
|
10
|
+
*
|
|
11
|
+
* - every primary route is reachable, so nothing in the mobile menu is
|
|
12
|
+
* stranded when JavaScript fails
|
|
13
|
+
* - the accessibility statement is linked from every page — required under
|
|
14
|
+
* the EAA and the UK PSBAR
|
|
15
|
+
* - privacy, terms and the rest come from the `legal` COLLECTION, never from
|
|
16
|
+
* a hand-kept list. A footer link to a page nobody wrote yet is a 404 on
|
|
17
|
+
* every page of the site and nothing reports it; deriving the links from the
|
|
18
|
+
* pages means the link cannot exist without the page
|
|
19
|
+
* - NAP (name, address, phone) matches src/data/business.ts exactly, because
|
|
20
|
+
* the JSON-LD reads the same file and a local listing that disagrees with
|
|
21
|
+
* the page is a ranking problem
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const year = new Date().getFullYear();
|
|
25
|
+
const footerNav = nav.filter((item) => item.footer !== false);
|
|
26
|
+
|
|
27
|
+
/* legalNav first — the accessibility statement is a hand-written page and
|
|
28
|
+
required — then whatever the legal collection holds, in its own order. */
|
|
29
|
+
const footerLegal = [...legalNav, ...(await getLegalLinks())];
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
<footer class="footer no-print">
|
|
33
|
+
<div class="container container--wide">
|
|
34
|
+
<div class="footer__top">
|
|
35
|
+
<div>
|
|
36
|
+
<p class="footer__name">{business.name}</p>
|
|
37
|
+
<p class="footer__blurb">{business.description}</p>
|
|
38
|
+
{
|
|
39
|
+
business.credentials.length > 0 && (
|
|
40
|
+
<ul role="list" class="footer__badges">
|
|
41
|
+
{business.credentials.map((c) => (
|
|
42
|
+
<li>{c.name}</li>
|
|
43
|
+
))}
|
|
44
|
+
</ul>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<nav class="footer__col" aria-labelledby="footer-links">
|
|
50
|
+
<h2 class="footer__heading" id="footer-links">Pages</h2>
|
|
51
|
+
<ul role="list">
|
|
52
|
+
{footerNav.map((link) => <li><a href={link.href}>{link.label}</a></li>)}
|
|
53
|
+
</ul>
|
|
54
|
+
</nav>
|
|
55
|
+
|
|
56
|
+
<div class="footer__col">
|
|
57
|
+
<h2 class="footer__heading">Get in touch</h2>
|
|
58
|
+
<ul role="list">
|
|
59
|
+
<li><a href={business.phone.href}>{business.phone.display}</a></li>
|
|
60
|
+
<li><a href={business.email.href}>{business.email.display}</a></li>
|
|
61
|
+
<li>
|
|
62
|
+
<a href={business.address.mapUrl} rel="noopener noreferrer" target="_blank">
|
|
63
|
+
{addressOneLine}
|
|
64
|
+
</a>
|
|
65
|
+
</li>
|
|
66
|
+
<li>{business.hours.display}</li>
|
|
67
|
+
</ul>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
{
|
|
72
|
+
business.serviceAreas.length > 0 && (
|
|
73
|
+
<div class="footer__areas">
|
|
74
|
+
<h2 class="footer__heading">Areas we serve</h2>
|
|
75
|
+
{/* A scannable list, not a run-on sentence — someone checking
|
|
76
|
+
whether you cover their town should find it without reading a
|
|
77
|
+
paragraph. Link an area only where a page exists for it. */}
|
|
78
|
+
<ul role="list" class="footer__areas-list">
|
|
79
|
+
{business.serviceAreas.map((name) => (
|
|
80
|
+
<li>{name}</li>
|
|
81
|
+
))}
|
|
82
|
+
</ul>
|
|
83
|
+
</div>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
<div class="footer__bottom">
|
|
88
|
+
<p>© {year} {business.name}. All rights reserved.</p>
|
|
89
|
+
<p class="footer__meta">
|
|
90
|
+
{
|
|
91
|
+
footerLegal.map((link, i) => (
|
|
92
|
+
<>
|
|
93
|
+
{i > 0 && <span aria-hidden="true">·</span>}
|
|
94
|
+
<a href={link.href}>{link.label}</a>
|
|
95
|
+
</>
|
|
96
|
+
))
|
|
97
|
+
}
|
|
98
|
+
{
|
|
99
|
+
business.credit.name && (
|
|
100
|
+
<>
|
|
101
|
+
<span aria-hidden="true">·</span>
|
|
102
|
+
<span>
|
|
103
|
+
Website by{' '}
|
|
104
|
+
<a href={business.credit.url} rel="noopener noreferrer" target="_blank">
|
|
105
|
+
{business.credit.name}
|
|
106
|
+
</a>
|
|
107
|
+
</span>
|
|
108
|
+
</>
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
</p>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</footer>
|
|
115
|
+
|
|
116
|
+
<style>
|
|
117
|
+
.footer {
|
|
118
|
+
margin-block-start: auto;
|
|
119
|
+
padding-block: var(--space-2xl) var(--space-l);
|
|
120
|
+
border-block-start: 1px solid var(--border);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.footer__top {
|
|
124
|
+
display: grid;
|
|
125
|
+
gap: var(--space-xl) var(--space-l);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@media (min-width: 60rem) {
|
|
129
|
+
.footer__top {
|
|
130
|
+
grid-template-columns: 2fr 1fr 1.4fr;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.footer__name {
|
|
135
|
+
font-family: var(--font-display);
|
|
136
|
+
font-size: var(--step-1);
|
|
137
|
+
font-weight: var(--weight-semibold);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.footer__blurb {
|
|
141
|
+
margin-block-start: var(--space-2xs);
|
|
142
|
+
color: var(--text-muted);
|
|
143
|
+
max-inline-size: var(--measure-narrow);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.footer__badges {
|
|
147
|
+
display: flex;
|
|
148
|
+
flex-wrap: wrap;
|
|
149
|
+
gap: var(--space-2xs);
|
|
150
|
+
margin-block-start: var(--space-m);
|
|
151
|
+
padding: 0;
|
|
152
|
+
font-size: var(--step--1);
|
|
153
|
+
color: var(--text-muted);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.footer__heading {
|
|
157
|
+
font-family: var(--font-body);
|
|
158
|
+
font-size: var(--step--1);
|
|
159
|
+
font-weight: var(--weight-semibold);
|
|
160
|
+
letter-spacing: var(--tracking-wide);
|
|
161
|
+
margin-block-end: var(--space-s);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.footer__col ul {
|
|
165
|
+
display: grid;
|
|
166
|
+
gap: var(--space-2xs);
|
|
167
|
+
padding: 0;
|
|
168
|
+
color: var(--text-muted);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.footer__col a {
|
|
172
|
+
color: inherit;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.footer__areas {
|
|
176
|
+
margin-block-start: var(--space-xl);
|
|
177
|
+
padding-block-start: var(--space-l);
|
|
178
|
+
border-block-start: 1px solid var(--border);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.footer__areas-list {
|
|
182
|
+
display: flex;
|
|
183
|
+
flex-wrap: wrap;
|
|
184
|
+
gap: var(--space-3xs) var(--space-m);
|
|
185
|
+
margin: 0;
|
|
186
|
+
padding: 0;
|
|
187
|
+
font-size: var(--step--1);
|
|
188
|
+
color: var(--text-muted);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.footer__bottom {
|
|
192
|
+
display: flex;
|
|
193
|
+
flex-wrap: wrap;
|
|
194
|
+
gap: var(--space-xs) var(--space-m);
|
|
195
|
+
justify-content: space-between;
|
|
196
|
+
align-items: center;
|
|
197
|
+
margin-block-start: var(--space-xl);
|
|
198
|
+
padding-block-start: var(--space-m);
|
|
199
|
+
border-block-start: 1px solid var(--border);
|
|
200
|
+
font-size: var(--step--1);
|
|
201
|
+
color: var(--text-muted);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.footer__meta {
|
|
205
|
+
display: flex;
|
|
206
|
+
flex-wrap: wrap;
|
|
207
|
+
align-items: center;
|
|
208
|
+
gap: var(--space-2xs);
|
|
209
|
+
}
|
|
210
|
+
</style>
|