@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.
Files changed (65) hide show
  1. package/Backlinks.astro +56 -0
  2. package/CHANGELOG.md +60 -0
  3. package/Comments.astro +115 -0
  4. package/README.md +404 -0
  5. package/ReadingTime.astro +60 -0
  6. package/brand-manual/index.module.js +12 -0
  7. package/brand-manual/route.astro +341 -0
  8. package/confetti/Confetti.astro +42 -0
  9. package/confetti/index.module.js +22 -0
  10. package/content/index.module.js +19 -0
  11. package/content/route.astro +59 -0
  12. package/copy-buttons/index.module.js +9 -0
  13. package/deep-link/index.module.js +9 -0
  14. package/deep-link/init.js +14 -0
  15. package/eink/index.module.js +9 -0
  16. package/fonts/index.module.js +76 -0
  17. package/gestures/index.module.js +9 -0
  18. package/gestures/init.js +13 -0
  19. package/gsap/client.js +41 -0
  20. package/gsap/index.module.js +20 -0
  21. package/headers/index.module.js +16 -0
  22. package/headers/route.js +93 -0
  23. package/humans/index.module.js +17 -0
  24. package/humans/route.js +79 -0
  25. package/iconify/index.module.js +9 -0
  26. package/image-zoom/index.module.js +9 -0
  27. package/keyboard/index.module.js +9 -0
  28. package/lab/index.module.js +26 -0
  29. package/launcher/index.module.js +27 -0
  30. package/maintenance/index.module.js +18 -0
  31. package/maintenance/middleware.js +34 -0
  32. package/maintenance/routes/index.astro +45 -0
  33. package/manifest/index.module.js +16 -0
  34. package/manifest/route.js +57 -0
  35. package/math/client.js +98 -0
  36. package/math/index.module.js +19 -0
  37. package/math/styles.css +25 -0
  38. package/mermaid/client.js +125 -0
  39. package/mermaid/index.module.js +19 -0
  40. package/mermaid/styles.css +114 -0
  41. package/p5/client.js +25 -0
  42. package/p5/index.module.js +24 -0
  43. package/package.json +35 -0
  44. package/prism/README.md +13 -0
  45. package/prism/index.module.js +19 -0
  46. package/robots/index.module.js +16 -0
  47. package/robots/route.js +67 -0
  48. package/rss/index.module.js +18 -0
  49. package/rss/route.js +163 -0
  50. package/scroll-wrappers/index.module.js +9 -0
  51. package/security-txt/index.module.js +17 -0
  52. package/security-txt/route.js +90 -0
  53. package/sitemap/index.module.js +22 -0
  54. package/stripe/client.js +27 -0
  55. package/stripe/components/MockBuyButton.svelte +38 -0
  56. package/stripe/index.module.js +33 -0
  57. package/stripe/routes/mock-checkout.js +64 -0
  58. package/stripe/views/CheckoutView.svelte +181 -0
  59. package/styles/index.module.js +11 -0
  60. package/theme-utils/README.md +74 -0
  61. package/theme-utils/client.js +79 -0
  62. package/theme-utils/index.module.js +27 -0
  63. package/themes/index.module.js +50 -0
  64. package/toast/index.module.js +17 -0
  65. 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,9 @@
1
+ /**
2
+ * Deep Link Module
3
+ * Client script lives in @stnd/client — manifest registered here.
4
+ */
5
+ export default {
6
+ id: "stnd-deep-link",
7
+ name: "Stnd :: Client :: Deep Link",
8
+ scripts: [{ src: "./init.js", init: "init" }],
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,9 @@
1
+ /**
2
+ * E-Ink Module
3
+ * Client script lives in @stnd/client — manifest registered here.
4
+ */
5
+ export default {
6
+ id: "stnd-eink",
7
+ name: "Stnd :: Client :: E-Ink",
8
+ scripts: [{ src: "@stnd/client/eink", init: "initEInk" }],
9
+ };
@@ -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
+ };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Gestures Module
3
+ * Client script lives in @stnd/client — manifest registered here.
4
+ */
5
+ export default {
6
+ id: "stnd-gestures",
7
+ name: "Stnd :: Client :: Gestures",
8
+ scripts: [{ src: "./init.js", init: "init" }],
9
+ };
@@ -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
+ }