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.
Files changed (91) hide show
  1. package/README.md +54 -0
  2. package/index.mjs +149 -0
  3. package/package.json +42 -0
  4. package/template/.dev.vars.example +3 -0
  5. package/template/.github/workflows/gates.yml +58 -0
  6. package/template/.node-version +1 -0
  7. package/template/.pa11yci.json +24 -0
  8. package/template/BUILD-STATE.md +47 -0
  9. package/template/CLAUDE.md +153 -0
  10. package/template/astro.config.mjs +150 -0
  11. package/template/docs/analytics.md +86 -0
  12. package/template/docs/content.md +138 -0
  13. package/template/docs/handover.md +182 -0
  14. package/template/docs/handover.pdf +0 -0
  15. package/template/docs/runbook.md +661 -0
  16. package/template/docs/traps.md +903 -0
  17. package/template/gitignore +31 -0
  18. package/template/package-lock.json +8159 -0
  19. package/template/package.json +53 -0
  20. package/template/public/_headers +61 -0
  21. package/template/public/_redirects +39 -0
  22. package/template/public/site.webmanifest +13 -0
  23. package/template/scripts/a11y-evidence.mjs +258 -0
  24. package/template/scripts/check-console.mjs +125 -0
  25. package/template/scripts/check-env.mjs +99 -0
  26. package/template/scripts/check-reflow.mjs +148 -0
  27. package/template/scripts/check-sitemap.mjs +113 -0
  28. package/template/scripts/dns-snapshot.mjs +267 -0
  29. package/template/scripts/extract.mjs +317 -0
  30. package/template/scripts/indexnow.mjs +154 -0
  31. package/template/scripts/lastmod.mjs +147 -0
  32. package/template/scripts/lib/inventory.mjs +104 -0
  33. package/template/scripts/lib/preserved.mjs +42 -0
  34. package/template/scripts/lib/routes.mjs +92 -0
  35. package/template/scripts/md-to-pdf.mjs +335 -0
  36. package/template/scripts/og-cards.config.mjs +114 -0
  37. package/template/scripts/og-cards.mjs +487 -0
  38. package/template/scripts/optimize-media.mjs +380 -0
  39. package/template/scripts/recon.mjs +480 -0
  40. package/template/scripts/redirects.mjs +298 -0
  41. package/template/scripts/shots.mjs +447 -0
  42. package/template/scripts/staging-headers.mjs +102 -0
  43. package/template/scripts/tells.mjs +268 -0
  44. package/template/scripts/verify.mjs +1069 -0
  45. package/template/src/components/ContactForm.astro +405 -0
  46. package/template/src/components/CtaBand.astro +82 -0
  47. package/template/src/components/EnvBadge.astro +146 -0
  48. package/template/src/components/Footer.astro +210 -0
  49. package/template/src/components/Header.astro +530 -0
  50. package/template/src/components/Icon.astro +56 -0
  51. package/template/src/components/Img.astro +129 -0
  52. package/template/src/components/PageHero.astro +88 -0
  53. package/template/src/components/Seo.astro +119 -0
  54. package/template/src/components/StructuredData.astro +173 -0
  55. package/template/src/content/blog/.gitkeep +5 -0
  56. package/template/src/content/legal/.gitkeep +0 -0
  57. package/template/src/content.config.ts +81 -0
  58. package/template/src/data/areas.ts +31 -0
  59. package/template/src/data/business.ts +121 -0
  60. package/template/src/data/categories.ts +37 -0
  61. package/template/src/data/fonts.ts +25 -0
  62. package/template/src/data/image-manifest.json +1 -0
  63. package/template/src/data/lastmod.json +1 -0
  64. package/template/src/data/nav.ts +49 -0
  65. package/template/src/data/services.ts +39 -0
  66. package/template/src/data/site.ts +136 -0
  67. package/template/src/env.d.ts +28 -0
  68. package/template/src/layouts/Base.astro +223 -0
  69. package/template/src/lib/brevo.ts +96 -0
  70. package/template/src/lib/hast-media.mjs +55 -0
  71. package/template/src/lib/lastmod.mjs +47 -0
  72. package/template/src/lib/lead.ts +92 -0
  73. package/template/src/lib/legal-routes.mjs +31 -0
  74. package/template/src/lib/legal.ts +75 -0
  75. package/template/src/lib/posts.ts +64 -0
  76. package/template/src/lib/runtime.ts +33 -0
  77. package/template/src/pages/404.astro +51 -0
  78. package/template/src/pages/[slug].astro +111 -0
  79. package/template/src/pages/accessibility.astro +128 -0
  80. package/template/src/pages/api/contact.ts +191 -0
  81. package/template/src/pages/api/leads.csv.ts +82 -0
  82. package/template/src/pages/contact.astro +112 -0
  83. package/template/src/pages/index.astro +84 -0
  84. package/template/src/pages/robots.txt.ts +38 -0
  85. package/template/src/pages/rss.xml.ts +27 -0
  86. package/template/src/styles/global.css +463 -0
  87. package/template/src/styles/project.css +14 -0
  88. package/template/src/styles/prose.css +182 -0
  89. package/template/src/styles/tokens.css +218 -0
  90. package/template/tsconfig.json +5 -0
  91. 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>&copy; {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>