@stnd/modules 0.5.1
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/Backlinks.astro +56 -0
- package/CHANGELOG.md +60 -0
- package/Comments.astro +115 -0
- package/README.md +404 -0
- package/ReadingTime.astro +60 -0
- package/brand-manual/index.module.js +12 -0
- package/brand-manual/route.astro +341 -0
- package/confetti/Confetti.astro +42 -0
- package/confetti/index.module.js +22 -0
- package/content/index.module.js +19 -0
- package/content/route.astro +59 -0
- package/copy-buttons/index.module.js +9 -0
- package/deep-link/index.module.js +9 -0
- package/deep-link/init.js +14 -0
- package/eink/index.module.js +9 -0
- package/fonts/index.module.js +76 -0
- package/gestures/index.module.js +9 -0
- package/gestures/init.js +13 -0
- package/gsap/client.js +41 -0
- package/gsap/index.module.js +20 -0
- package/headers/index.module.js +16 -0
- package/headers/route.js +93 -0
- package/humans/index.module.js +17 -0
- package/humans/route.js +79 -0
- package/iconify/index.module.js +9 -0
- package/image-zoom/index.module.js +9 -0
- package/keyboard/index.module.js +9 -0
- package/lab/index.module.js +26 -0
- package/launcher/index.module.js +27 -0
- package/maintenance/index.module.js +18 -0
- package/maintenance/middleware.js +34 -0
- package/maintenance/routes/index.astro +45 -0
- package/manifest/index.module.js +16 -0
- package/manifest/route.js +57 -0
- package/math/client.js +98 -0
- package/math/index.module.js +19 -0
- package/math/styles.css +25 -0
- package/mermaid/client.js +125 -0
- package/mermaid/index.module.js +19 -0
- package/mermaid/styles.css +114 -0
- package/p5/client.js +25 -0
- package/p5/index.module.js +24 -0
- package/package.json +35 -0
- package/prism/README.md +13 -0
- package/prism/index.module.js +19 -0
- package/robots/index.module.js +16 -0
- package/robots/route.js +67 -0
- package/rss/index.module.js +18 -0
- package/rss/route.js +163 -0
- package/scroll-wrappers/index.module.js +9 -0
- package/security-txt/index.module.js +17 -0
- package/security-txt/route.js +90 -0
- package/sitemap/index.module.js +22 -0
- package/stripe/client.js +27 -0
- package/stripe/components/MockBuyButton.svelte +38 -0
- package/stripe/index.module.js +33 -0
- package/stripe/routes/mock-checkout.js +64 -0
- package/stripe/views/CheckoutView.svelte +181 -0
- package/styles/index.module.js +11 -0
- package/theme-utils/README.md +74 -0
- package/theme-utils/client.js +79 -0
- package/theme-utils/index.module.js +27 -0
- package/themes/index.module.js +50 -0
- package/toast/index.module.js +17 -0
- package/toast/toast.js +17 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
id: "stnd-brand-manual",
|
|
3
|
+
name: "Stnd :: Normes Graphiques",
|
|
4
|
+
description:
|
|
5
|
+
"Un manuel des normes graphiques affichant les tokens du thème comme les couleurs et la typographie.",
|
|
6
|
+
routes: [
|
|
7
|
+
{
|
|
8
|
+
path: "/brand",
|
|
9
|
+
entrypoint: "./route.astro",
|
|
10
|
+
},
|
|
11
|
+
],
|
|
12
|
+
};
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Base from "@stnd/layout/Base.astro";
|
|
3
|
+
|
|
4
|
+
// Manuel des Normes Graphiques - Définitions des Tokens
|
|
5
|
+
const colors = [
|
|
6
|
+
{ name: "Fond (Background)", token: "var(--color-background)" },
|
|
7
|
+
{ name: "Premier plan (Foreground)", token: "var(--color-foreground)" },
|
|
8
|
+
{ name: "Accentuation", token: "var(--color-accent)" },
|
|
9
|
+
{ name: "Rouge", token: "var(--color-red)" },
|
|
10
|
+
{ name: "Orange", token: "var(--color-orange)" },
|
|
11
|
+
{ name: "Jaune", token: "var(--color-yellow)" },
|
|
12
|
+
{ name: "Vert", token: "var(--color-green)" },
|
|
13
|
+
{ name: "Cyan", token: "var(--color-cyan)" },
|
|
14
|
+
{ name: "Bleu", token: "var(--color-blue)" },
|
|
15
|
+
{ name: "Magenta", token: "var(--color-magenta)" },
|
|
16
|
+
{ name: "Atténué (Muted)", token: "var(--color-muted)" },
|
|
17
|
+
{ name: "Surface", token: "var(--color-surface)" },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const typography = [
|
|
21
|
+
{
|
|
22
|
+
name: "Sans empattement (Sans)",
|
|
23
|
+
token: "var(--font-sans)",
|
|
24
|
+
fallback: "system-ui, sans-serif",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: "Avec empattement (Serif)",
|
|
28
|
+
token: "var(--font-serif)",
|
|
29
|
+
fallback: "Georgia, serif",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: "Chasse fixe (Monospace)",
|
|
33
|
+
token: "var(--font-monospace)",
|
|
34
|
+
fallback: "monospace",
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const scales = [
|
|
39
|
+
{ name: "Le plus petit", token: "--scale-d5" },
|
|
40
|
+
{ name: "Plus petit", token: "--scale-d2" },
|
|
41
|
+
{ name: "Base", token: "--scale" },
|
|
42
|
+
{ name: "Moyen", token: "--scale-2" },
|
|
43
|
+
{ name: "Grand", token: "--scale-4" },
|
|
44
|
+
{ name: "Le plus grand", token: "--scale-8" },
|
|
45
|
+
];
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
<Base
|
|
49
|
+
title="Manuel des Normes Graphiques"
|
|
50
|
+
description="Lignes directrices de la marque & Tokens du système de design"
|
|
51
|
+
>
|
|
52
|
+
<header class="manual-header full">
|
|
53
|
+
<h1>Manuel des Normes Graphiques</h1>
|
|
54
|
+
<p>Lignes directrices de la marque & Tokens du système de design</p>
|
|
55
|
+
</header>
|
|
56
|
+
|
|
57
|
+
<section class="manual-section full">
|
|
58
|
+
<h2 class="brand-header">1.0 Palette de Couleurs</h2>
|
|
59
|
+
<div class="grid-6 sm:grid-2 gap">
|
|
60
|
+
{
|
|
61
|
+
colors.map((color) => (
|
|
62
|
+
<div class="color-swatch-container">
|
|
63
|
+
<div
|
|
64
|
+
class="color-swatch"
|
|
65
|
+
style={`background-color: ${color.token}; border: 1px solid var(--color-border);`}
|
|
66
|
+
/>
|
|
67
|
+
<div class="color-info">
|
|
68
|
+
<strong>{color.name}</strong>
|
|
69
|
+
<code>{color.token.replace("var(", "").replace(")", "")}</code>
|
|
70
|
+
<code class="hex-value" data-token={color.token} />
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
))
|
|
74
|
+
}
|
|
75
|
+
</div>
|
|
76
|
+
</section>
|
|
77
|
+
|
|
78
|
+
<section class="manual-section full">
|
|
79
|
+
<h2 class="brand-header">2.0 Typographie</h2>
|
|
80
|
+
<div class="typography-list">
|
|
81
|
+
{
|
|
82
|
+
typography.map((font) => (
|
|
83
|
+
<div class="typography-item">
|
|
84
|
+
<div
|
|
85
|
+
class="typography-sample"
|
|
86
|
+
style={`font-family: ${font.token}, ${font.fallback};`}
|
|
87
|
+
>
|
|
88
|
+
Aa
|
|
89
|
+
</div>
|
|
90
|
+
<div class="typography-info">
|
|
91
|
+
<div class="typography-meta">
|
|
92
|
+
<strong>{font.name}</strong>
|
|
93
|
+
<span class="font-value" data-token={font.token} />
|
|
94
|
+
<!--code>{font.token.replace("var(", "").replace(")", "")}</code-->
|
|
95
|
+
|
|
96
|
+
</div>
|
|
97
|
+
<div
|
|
98
|
+
class="typography-preview"
|
|
99
|
+
style={`font-family: ${font.token}, ${font.fallback};`}
|
|
100
|
+
>
|
|
101
|
+
<h2>Whereas recognition of the inherent dignity</h2>
|
|
102
|
+
<p>No one shall be subjected to arbitrary arrest, detention or exile. Everyone is entitled in full equality to a fair and public hearing by an independent and impartial tribunal, in the determination of his rights and obligations and of any criminal charge against him. No one shall be subjected to arbitrary interference with his privacy, family, home or correspondence, nor to attacks upon his honour and reputation. Everyone has the right to the protection of the law against such interference or attacks.</p>
|
|
103
|
+
<p>Everyone has the right to freedom of thought, conscience and religion; this right includes freedom to change his religion or belief, and freedom, either alone or in community with others and in public or private, to manifest his religion or belief in teaching, practice, worship and observance. Everyone has the right to freedom of opinion and expression; this right includes freedom to hold opinions without interference and to seek, receive and impart information and ideas through any media and regardless of frontiers. Everyone has the right to rest and leisure, including reasonable limitation of working hours and periodic holidays with pay.</p>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
))
|
|
108
|
+
}
|
|
109
|
+
</div>
|
|
110
|
+
</section>
|
|
111
|
+
|
|
112
|
+
<script>
|
|
113
|
+
// Get all hex-value elements
|
|
114
|
+
const hexElements = document.querySelectorAll(".hex-value");
|
|
115
|
+
|
|
116
|
+
// Create a reusable canvas to compute exact RGBA pixels from ANY color string (like oklch)
|
|
117
|
+
const canvas = document.createElement("canvas");
|
|
118
|
+
canvas.width = 1;
|
|
119
|
+
canvas.height = 1;
|
|
120
|
+
const ctx = canvas.getContext("2d", { willReadFrequently: true });
|
|
121
|
+
|
|
122
|
+
// Calculate computed color for each token
|
|
123
|
+
hexElements.forEach((el) => {
|
|
124
|
+
const token = el.getAttribute("data-token");
|
|
125
|
+
if (token) {
|
|
126
|
+
// Create a temporary element to compute the style
|
|
127
|
+
const tempDiv = document.createElement("div");
|
|
128
|
+
tempDiv.style.color = token;
|
|
129
|
+
tempDiv.style.display = "none";
|
|
130
|
+
document.body.appendChild(tempDiv);
|
|
131
|
+
|
|
132
|
+
const computedColor = window.getComputedStyle(tempDiv).color;
|
|
133
|
+
document.body.removeChild(tempDiv);
|
|
134
|
+
|
|
135
|
+
let hexColor = computedColor;
|
|
136
|
+
|
|
137
|
+
if (ctx) {
|
|
138
|
+
// Draw the color onto our 1x1 canvas
|
|
139
|
+
ctx.clearRect(0, 0, 1, 1);
|
|
140
|
+
ctx.fillStyle = computedColor;
|
|
141
|
+
ctx.fillRect(0, 0, 1, 1);
|
|
142
|
+
|
|
143
|
+
// Read the precise pixel data
|
|
144
|
+
const [r, g, b, a] = ctx.getImageData(0, 0, 1, 1).data;
|
|
145
|
+
|
|
146
|
+
const toHex = (c) => c.toString(16).padStart(2, "0");
|
|
147
|
+
let hex = "#" + toHex(r) + toHex(g) + toHex(b);
|
|
148
|
+
if (a < 255) {
|
|
149
|
+
hex += toHex(a);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
hexColor = hex.toUpperCase();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Display the computed Hex code
|
|
156
|
+
el.textContent = hexColor;
|
|
157
|
+
|
|
158
|
+
// Click to copy functionality
|
|
159
|
+
const container = el.closest(".color-swatch-container");
|
|
160
|
+
if (container) {
|
|
161
|
+
container.style.cursor = "pointer";
|
|
162
|
+
container.title = "Cliquez pour copier";
|
|
163
|
+
|
|
164
|
+
container.addEventListener("click", async () => {
|
|
165
|
+
try {
|
|
166
|
+
await navigator.clipboard.writeText(hexColor);
|
|
167
|
+
|
|
168
|
+
if (window.toast) {
|
|
169
|
+
window.toast(`Couleur copiée: ${hexColor}`, "success", 2000);
|
|
170
|
+
} else {
|
|
171
|
+
// Fallback visual feedback if toast is not available
|
|
172
|
+
const originalText = el.textContent;
|
|
173
|
+
el.textContent = "Copié !";
|
|
174
|
+
el.style.color = "var(--color-green)";
|
|
175
|
+
el.style.fontWeight = "bold";
|
|
176
|
+
|
|
177
|
+
setTimeout(() => {
|
|
178
|
+
el.textContent = originalText;
|
|
179
|
+
el.style.color = "";
|
|
180
|
+
el.style.fontWeight = "";
|
|
181
|
+
}, 1500);
|
|
182
|
+
}
|
|
183
|
+
} catch (err) {
|
|
184
|
+
console.error("Failed to copy text: ", err);
|
|
185
|
+
if (window.toast) {
|
|
186
|
+
window.toast("Erreur lors de la copie", "error");
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Calculate computed font-family for each token
|
|
195
|
+
const fontElements = document.querySelectorAll(".font-value");
|
|
196
|
+
fontElements.forEach((el) => {
|
|
197
|
+
const token = el.getAttribute("data-token");
|
|
198
|
+
if (token) {
|
|
199
|
+
const tempDiv = document.createElement("div");
|
|
200
|
+
tempDiv.style.fontFamily = token;
|
|
201
|
+
tempDiv.style.display = "none";
|
|
202
|
+
document.body.appendChild(tempDiv);
|
|
203
|
+
|
|
204
|
+
// Get computed font-family and clean it up
|
|
205
|
+
let computedFont = window.getComputedStyle(tempDiv).fontFamily;
|
|
206
|
+
document.body.removeChild(tempDiv);
|
|
207
|
+
|
|
208
|
+
// Remove quotes around font names for cleaner display
|
|
209
|
+
computedFont = computedFont.replace(/['"]/g, "");
|
|
210
|
+
|
|
211
|
+
// Display only the primary font (first one in the stack) if it's long, or the whole stack
|
|
212
|
+
const fonts = computedFont.split(",");
|
|
213
|
+
el.textContent = fonts[0].trim();
|
|
214
|
+
|
|
215
|
+
if (fonts.length > 1) {
|
|
216
|
+
el.title = computedFont; // Show full stack on hover
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
</script>
|
|
221
|
+
</Base>
|
|
222
|
+
|
|
223
|
+
<style>
|
|
224
|
+
.manual-header {
|
|
225
|
+
border-bottom: 4px solid var(--color-foreground);
|
|
226
|
+
padding-bottom: var(--space-3);
|
|
227
|
+
margin-block: var(--space-6);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.manual-header h1 {
|
|
231
|
+
font-size: var(--size-4xl);
|
|
232
|
+
font-weight: var(--font-header-weight);
|
|
233
|
+
text-transform: uppercase;
|
|
234
|
+
letter-spacing: -0.04em;
|
|
235
|
+
margin: 0 0 var(--space-d2) 0;
|
|
236
|
+
line-height: 0.8;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.manual-header p {
|
|
240
|
+
color: var(--color-muted);
|
|
241
|
+
font-size: var(--size-lg);
|
|
242
|
+
margin: 0;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.manual-section {
|
|
246
|
+
margin-bottom: var(--space-8);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.manual-section .brand-header {
|
|
250
|
+
font-size: var(--scale-3, 1.5rem);
|
|
251
|
+
font-weight: 700;
|
|
252
|
+
text-transform: uppercase;
|
|
253
|
+
letter-spacing: 0.05em;
|
|
254
|
+
border-bottom: 1px solid var(--color-border);
|
|
255
|
+
padding-bottom: var(--space);
|
|
256
|
+
margin-bottom: var(--space-4);
|
|
257
|
+
color: var(--color-foreground);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/* Colors */
|
|
261
|
+
|
|
262
|
+
.color-swatch {
|
|
263
|
+
width: 100%;
|
|
264
|
+
aspect-ratio: 1;
|
|
265
|
+
border-radius: var(--radius);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.color-info {
|
|
269
|
+
display: flex;
|
|
270
|
+
flex-direction: column;
|
|
271
|
+
gap: var(--space-d4);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.color-info strong {
|
|
275
|
+
font-weight: 600;
|
|
276
|
+
font-size: var(--scale);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.color-info code {
|
|
280
|
+
font-size: var(--scale-d2);
|
|
281
|
+
color: var(--color-muted);
|
|
282
|
+
font-family: var(--font-monospace, monospace);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/* Typography */
|
|
286
|
+
.typography-list {
|
|
287
|
+
display: flex;
|
|
288
|
+
flex-direction: column;
|
|
289
|
+
gap: var(--space-5);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.typography-item {
|
|
293
|
+
display: flex;
|
|
294
|
+
gap: var(--space-4);
|
|
295
|
+
align-items: center;
|
|
296
|
+
border-bottom: 1px dashed var(--color-border);
|
|
297
|
+
padding-bottom: var(--space-4);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.typography-item:last-child {
|
|
301
|
+
border-bottom: none;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.typography-sample {
|
|
305
|
+
font-size: var(--scale-8);
|
|
306
|
+
line-height: 1;
|
|
307
|
+
min-width: 120px;
|
|
308
|
+
text-align: center;
|
|
309
|
+
font-weight: 400;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.typography-info {
|
|
313
|
+
flex: 1;
|
|
314
|
+
display: flex;
|
|
315
|
+
flex-direction: column;
|
|
316
|
+
gap: var(--space);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.typography-meta {
|
|
320
|
+
display: flex;
|
|
321
|
+
align-items: baseline;
|
|
322
|
+
gap: var(--space-half);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.typography-meta strong {
|
|
326
|
+
font-weight: 600;
|
|
327
|
+
font-size: var(--size-lg);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.typography-meta code {
|
|
331
|
+
font-size: var(--size-xs);
|
|
332
|
+
color: var(--color-subtle);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.typography-preview {
|
|
336
|
+
font-size: var(--scale-base);
|
|
337
|
+
h2 {
|
|
338
|
+
font-family:inherit;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
</style>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* @stnd/modules/confetti/Confetti.astro
|
|
4
|
+
*
|
|
5
|
+
* A standalone component that triggers a confetti explosion on page load.
|
|
6
|
+
*
|
|
7
|
+
* It is safe to include even if the confetti script is not loaded; it will gracefully fail.
|
|
8
|
+
*/
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
<script>
|
|
12
|
+
// Fire standard ready event on page load and attempt a guarded confetti burst.
|
|
13
|
+
document.addEventListener("astro:page-load", () => {
|
|
14
|
+
try {
|
|
15
|
+
// Use bracket access to avoid TypeScript/linters complaining about unknown properties.
|
|
16
|
+
// @ts-ignore
|
|
17
|
+
if (typeof window !== "undefined" && typeof window["confetti"] === "function") {
|
|
18
|
+
// @ts-ignore
|
|
19
|
+
window["confetti"]({
|
|
20
|
+
particleCount: 150,
|
|
21
|
+
spread: 90,
|
|
22
|
+
origin: { y: 0.7 },
|
|
23
|
+
});
|
|
24
|
+
} else {
|
|
25
|
+
// CDN script may still be loading — one retry
|
|
26
|
+
setTimeout(() => {
|
|
27
|
+
// @ts-ignore
|
|
28
|
+
if (typeof window !== "undefined" && typeof window["confetti"] === "function") {
|
|
29
|
+
// @ts-ignore
|
|
30
|
+
window["confetti"]({
|
|
31
|
+
particleCount: 150,
|
|
32
|
+
spread: 90,
|
|
33
|
+
origin: { y: 0.7 },
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}, 600);
|
|
37
|
+
}
|
|
38
|
+
} catch (err) {
|
|
39
|
+
// Swallow any errors — confetti is non-critical.
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
</script>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🎉 Confetti - Page Load Celebration
|
|
3
|
+
*
|
|
4
|
+
* Adds a fun confetti explosion on page load.
|
|
5
|
+
* Registered as a Gold Standard module — every @stnd site ships with this.
|
|
6
|
+
*
|
|
7
|
+
* Zone: stnd:base (static Astro SSR, no hydration needed)
|
|
8
|
+
*/
|
|
9
|
+
export default {
|
|
10
|
+
id: "stnd-confetti",
|
|
11
|
+
name: "Stnd :: Confetti",
|
|
12
|
+
description: "A fun confetti explosion on page load.",
|
|
13
|
+
environment: ["dev"],
|
|
14
|
+
|
|
15
|
+
scripts: [
|
|
16
|
+
"https://cdn.jsdelivr.net/npm/canvas-confetti@1.6.0/dist/confetti.browser.min.js",
|
|
17
|
+
],
|
|
18
|
+
|
|
19
|
+
hooks: {
|
|
20
|
+
"stnd:base": ["./Confetti.astro"],
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content Route core module
|
|
3
|
+
*
|
|
4
|
+
* Injects the dynamic markdown content route so it can be opt-in
|
|
5
|
+
* via the Standard module system.
|
|
6
|
+
*/
|
|
7
|
+
export default {
|
|
8
|
+
id: "stnd-content",
|
|
9
|
+
name: "Stnd :: Content Route",
|
|
10
|
+
description:
|
|
11
|
+
"Renders markdown content from the 'content' collection using generated permalinks.",
|
|
12
|
+
routes: [
|
|
13
|
+
{
|
|
14
|
+
// Dynamic slug route for content collection pages
|
|
15
|
+
path: "/[...slug]",
|
|
16
|
+
entrypoint: "./route.astro",
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Dynamic content route for markdown files
|
|
4
|
+
* Provided via the content-route folio.
|
|
5
|
+
*
|
|
6
|
+
* It handles all markdown content from the 'docs' collection
|
|
7
|
+
* and generates pages based on their permalink.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { getCollection, render } from "astro:content";
|
|
11
|
+
import Base from "../../layouts/Base.astro";
|
|
12
|
+
import { generatePermalink } from "../../core/permalink";
|
|
13
|
+
|
|
14
|
+
export async function getStaticPaths() {
|
|
15
|
+
const content = await getCollection("content");
|
|
16
|
+
|
|
17
|
+
return content.map((entry: any) => {
|
|
18
|
+
// Get permalink from frontmatter OR generate it using shared utility
|
|
19
|
+
// (Remark folios run too late for getStaticPaths, so we must ensure it exists here)
|
|
20
|
+
const permalink = entry.data.permalink || generatePermalink(entry.id);
|
|
21
|
+
|
|
22
|
+
// Convert permalink to a valid Astro slug (remove leading/trailing slashes)
|
|
23
|
+
const slug = permalink.replace(/^\/|\/$/g, "");
|
|
24
|
+
|
|
25
|
+
// Empty string/undefined for index file (/) - this becomes the root route
|
|
26
|
+
return {
|
|
27
|
+
params: { slug: slug || undefined },
|
|
28
|
+
props: { entry },
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const { entry } = Astro.props;
|
|
34
|
+
const rendered = await render(entry);
|
|
35
|
+
// @ts-ignore
|
|
36
|
+
const { Content: ContentComponent, remarkFolioFrontmatter } = rendered;
|
|
37
|
+
|
|
38
|
+
// Merge schema data with data injected by remark folios (like backlinks)
|
|
39
|
+
const frontmatter = {
|
|
40
|
+
...entry.data,
|
|
41
|
+
...remarkFolioFrontmatter,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Ensure backlinks are present
|
|
45
|
+
const frontmatterWithBacklinks = {
|
|
46
|
+
...frontmatter,
|
|
47
|
+
backlinks: frontmatter?.backlinks || {},
|
|
48
|
+
};
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
<!-- Use Base layout and pass the markdown content through -->
|
|
52
|
+
<Base
|
|
53
|
+
frontmatter={frontmatterWithBacklinks}
|
|
54
|
+
title={frontmatter?.title || "Standard Framework"}
|
|
55
|
+
description={frontmatter?.description || ""}
|
|
56
|
+
class="prose"
|
|
57
|
+
>
|
|
58
|
+
<ContentComponent />
|
|
59
|
+
</Base>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copy Buttons Module
|
|
3
|
+
* Client script lives in @stnd/client — manifest registered here.
|
|
4
|
+
*/
|
|
5
|
+
export default {
|
|
6
|
+
id: "stnd-copy-buttons",
|
|
7
|
+
name: "Stnd :: Client :: Copy Buttons",
|
|
8
|
+
scripts: [{ src: "@stnd/client/copy-buttons", init: "initCopyButtons", refresh: "refreshCopyButtons" }],
|
|
9
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep Link — Configured initializer
|
|
3
|
+
*
|
|
4
|
+
* Wraps initTextFragments with app-specific callbacks (toast notifications).
|
|
5
|
+
* Called by the @stnd/store script runner on load and after View Transitions.
|
|
6
|
+
*/
|
|
7
|
+
import { initTextFragments } from "@stnd/client/deep-link.js";
|
|
8
|
+
|
|
9
|
+
export function init() {
|
|
10
|
+
initTextFragments({
|
|
11
|
+
onSuccess: () => window.toast?.("Link to highlight copied"),
|
|
12
|
+
onError: (msg) => window.toast?.(msg ?? "Failed to copy link", "error"),
|
|
13
|
+
});
|
|
14
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core Themes Module
|
|
3
|
+
* Loads individual theme folios so their fonts and styles are registered via the module system.
|
|
4
|
+
*
|
|
5
|
+
* Reason: loading the theme folios (instead of a single aggregated stylesheet)
|
|
6
|
+
* ensures each theme's `dependencies` (fonts) are processed and available when
|
|
7
|
+
* the theme is applied.
|
|
8
|
+
*/
|
|
9
|
+
export default {
|
|
10
|
+
id: "stnd-fonts",
|
|
11
|
+
name: "Stnd :: Fonts",
|
|
12
|
+
description: "Loads all shipped font",
|
|
13
|
+
|
|
14
|
+
// Declare module-level dependencies so the loader will import each theme folio.
|
|
15
|
+
// Each theme folio in packages/themes/<name>/ has its own `styles` and `dependencies`
|
|
16
|
+
// (e.g. font folios) and should be loaded via `dependencies` here.
|
|
17
|
+
dependencies: [
|
|
18
|
+
// Fonts
|
|
19
|
+
"@stnd/fonts/andika",
|
|
20
|
+
"@stnd/fonts/atkinson-hyperlegible-mono",
|
|
21
|
+
"@stnd/fonts/atkinson-hyperlegible-next",
|
|
22
|
+
"@stnd/fonts/avant-garde-pro",
|
|
23
|
+
"@stnd/fonts/avenir-next",
|
|
24
|
+
"@stnd/fonts/bauhaus-pro",
|
|
25
|
+
"@stnd/fonts/berkeley-mono",
|
|
26
|
+
"@stnd/fonts/bookerly",
|
|
27
|
+
"@stnd/fonts/brixton",
|
|
28
|
+
"@stnd/fonts/calsans",
|
|
29
|
+
"@stnd/fonts/cargo-arizona",
|
|
30
|
+
"@stnd/fonts/cargo-diatype",
|
|
31
|
+
"@stnd/fonts/cargo-diatype-widths",
|
|
32
|
+
"@stnd/fonts/cargo-marist",
|
|
33
|
+
"@stnd/fonts/cargo-monument-grotesk",
|
|
34
|
+
"@stnd/fonts/cargo-synt",
|
|
35
|
+
"@stnd/fonts/courier-prime",
|
|
36
|
+
"@stnd/fonts/din-condensed",
|
|
37
|
+
"@stnd/fonts/eb-garamond",
|
|
38
|
+
"@stnd/fonts/fern",
|
|
39
|
+
"@stnd/fonts/forrest",
|
|
40
|
+
"@stnd/fonts/fraunces",
|
|
41
|
+
"@stnd/fonts/futura-now",
|
|
42
|
+
"@stnd/fonts/gnomon",
|
|
43
|
+
"@stnd/fonts/gorton-perfected",
|
|
44
|
+
"@stnd/fonts/graveur",
|
|
45
|
+
"@stnd/fonts/helvetica-mono",
|
|
46
|
+
"@stnd/fonts/helvetica-now",
|
|
47
|
+
"@stnd/fonts/herbus",
|
|
48
|
+
"@stnd/fonts/ia-writer",
|
|
49
|
+
"@stnd/fonts/ibm-plex",
|
|
50
|
+
"@stnd/fonts/instrument-sans",
|
|
51
|
+
"@stnd/fonts/instrument-serif",
|
|
52
|
+
"@stnd/fonts/inter",
|
|
53
|
+
"@stnd/fonts/jimmy",
|
|
54
|
+
"@stnd/fonts/joly",
|
|
55
|
+
"@stnd/fonts/kalice",
|
|
56
|
+
"@stnd/fonts/lexend",
|
|
57
|
+
"@stnd/fonts/literata",
|
|
58
|
+
"@stnd/fonts/maine",
|
|
59
|
+
"@stnd/fonts/maple",
|
|
60
|
+
"@stnd/fonts/marauder",
|
|
61
|
+
"@stnd/fonts/monaspace-xenon",
|
|
62
|
+
"@stnd/fonts/monolisa",
|
|
63
|
+
"@stnd/fonts/monosten",
|
|
64
|
+
"@stnd/fonts/national-park",
|
|
65
|
+
"@stnd/fonts/newsreader",
|
|
66
|
+
"@stnd/fonts/oktah",
|
|
67
|
+
"@stnd/fonts/opendyslexic",
|
|
68
|
+
"@stnd/fonts/quicksand",
|
|
69
|
+
"@stnd/fonts/sohne",
|
|
70
|
+
"@stnd/fonts/source-serif-4",
|
|
71
|
+
"@stnd/fonts/souvenir-mono",
|
|
72
|
+
"@stnd/fonts/tiempos-headline",
|
|
73
|
+
"@stnd/fonts/univers-next",
|
|
74
|
+
"@stnd/fonts/wonder",
|
|
75
|
+
],
|
|
76
|
+
};
|
package/gestures/init.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gestures — Configured initializer
|
|
3
|
+
*
|
|
4
|
+
* Wraps initGestures with app-specific defaults (swipe-back navigation).
|
|
5
|
+
* Called by the @stnd/store script runner on load and after View Transitions.
|
|
6
|
+
*/
|
|
7
|
+
import { initGestures } from "@stnd/client/gestures.js";
|
|
8
|
+
|
|
9
|
+
export function init() {
|
|
10
|
+
initGestures({
|
|
11
|
+
onSwipeBack: () => window.history.back(),
|
|
12
|
+
});
|
|
13
|
+
}
|
package/gsap/client.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSAP Module — Client Loader
|
|
3
|
+
*
|
|
4
|
+
* Provides lazy loaders for GSAP core and plugins from CDN.
|
|
5
|
+
* Use `loadGsap()` in Svelte `onMount` or any async context.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* import { loadGsap } from "@stnd/modules/gsap/client.js";
|
|
9
|
+
*
|
|
10
|
+
* onMount(async () => {
|
|
11
|
+
* const { gsap, ScrollTrigger } = await loadGsap();
|
|
12
|
+
* gsap.registerPlugin(ScrollTrigger);
|
|
13
|
+
* // ...
|
|
14
|
+
* });
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const GSAP_VERSION = "3";
|
|
18
|
+
const GSAP_CDN = `https://cdn.jsdelivr.net/npm/gsap@${GSAP_VERSION}`;
|
|
19
|
+
|
|
20
|
+
let loading = null;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Load GSAP core + ScrollTrigger from CDN (ESM build).
|
|
24
|
+
* Cached after first call — safe to call multiple times.
|
|
25
|
+
* @returns {Promise<{ gsap: typeof gsap, ScrollTrigger: typeof ScrollTrigger }>}
|
|
26
|
+
*/
|
|
27
|
+
export function loadGsap() {
|
|
28
|
+
if (loading) return loading;
|
|
29
|
+
|
|
30
|
+
loading = Promise.all([
|
|
31
|
+
import(/* @vite-ignore */ `${GSAP_CDN}/index.js`),
|
|
32
|
+
import(/* @vite-ignore */ `${GSAP_CDN}/ScrollTrigger.js`),
|
|
33
|
+
]).then(([gsapMod, stMod]) => {
|
|
34
|
+
const gsap = gsapMod.gsap ?? gsapMod.default;
|
|
35
|
+
const ScrollTrigger = stMod.ScrollTrigger ?? stMod.default;
|
|
36
|
+
gsap.registerPlugin(ScrollTrigger);
|
|
37
|
+
return { gsap, ScrollTrigger };
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return loading;
|
|
41
|
+
}
|