@specglass/theme-default 0.0.9 → 0.0.11

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 (45) hide show
  1. package/dist/__tests__/design-tokens.test.d.ts +1 -0
  2. package/dist/__tests__/design-tokens.test.js +107 -0
  3. package/dist/islands/CopyButton.js +2 -6
  4. package/dist/islands/LanguageToggle.d.ts +16 -0
  5. package/dist/islands/LanguageToggle.js +88 -0
  6. package/dist/islands/SearchPalette.js +57 -8
  7. package/dist/islands/ThemeToggle.js +1 -1
  8. package/dist/scripts/code-block-enhancer.js +6 -3
  9. package/dist/themes/notdiamond-dark.json +168 -0
  10. package/dist/themes/notdiamond-light.json +168 -0
  11. package/dist/ui/command.js +2 -2
  12. package/dist/ui/dialog.js +2 -2
  13. package/dist/utils/shiki.d.ts +1 -1
  14. package/dist/utils/shiki.js +5 -3
  15. package/package.json +5 -3
  16. package/src/components/ApiAuth.astro +31 -4
  17. package/src/components/ApiEndpoint.astro +67 -44
  18. package/src/components/ApiNavigation.astro +8 -11
  19. package/src/components/ApiParameters.astro +113 -162
  20. package/src/components/ApiResponse.astro +1 -1
  21. package/src/components/Callout.astro +59 -18
  22. package/src/components/Card.astro +4 -4
  23. package/src/components/CodeBlock.astro +7 -7
  24. package/src/components/CodeBlockGroup.astro +3 -3
  25. package/src/components/CodeExample.astro +183 -0
  26. package/src/components/EditLink.astro +53 -0
  27. package/src/components/Footer.astro +87 -25
  28. package/src/components/Header.astro +63 -7
  29. package/src/components/Sidebar.astro +43 -11
  30. package/src/components/TableOfContents.astro +5 -5
  31. package/src/components/Tabs.astro +51 -20
  32. package/src/islands/CopyButton.tsx +36 -34
  33. package/src/islands/LanguageToggle.tsx +214 -0
  34. package/src/islands/SearchPalette.tsx +121 -39
  35. package/src/islands/ThemeToggle.tsx +45 -48
  36. package/src/layouts/ApiReferencePage.astro +67 -56
  37. package/src/layouts/DocPage.astro +32 -27
  38. package/src/layouts/LandingPage.astro +348 -27
  39. package/src/scripts/code-block-enhancer.ts +8 -3
  40. package/src/styles/global.css +388 -59
  41. package/src/themes/notdiamond-dark.json +168 -0
  42. package/src/themes/notdiamond-light.json +168 -0
  43. package/src/ui/command.tsx +1 -2
  44. package/src/ui/dialog.tsx +8 -5
  45. package/src/utils/shiki.ts +5 -3
@@ -5,16 +5,29 @@
5
5
  * Used for the site index page or any page with `layout: landing` in frontmatter.
6
6
  * Architecture mandate: one of three MVP layouts (DocPage, ApiReferencePage, LandingPage).
7
7
  * Typed props from core: { frontmatter, content, navigation }.
8
+ *
9
+ * Story 8.4: Premium redesign with animated gradient mesh, feature cards grid,
10
+ * gradient text hero, section dividers, and hero_image support.
8
11
  */
9
12
  import Header from "../components/Header.astro";
10
13
  import Footer from "../components/Footer.astro";
11
14
  import { ThemeToggle } from "../islands/ThemeToggle";
12
15
  import { SearchPalette } from "../islands/SearchPalette";
16
+
13
17
  import "../styles/global.css";
14
18
  import type { NavigationTree } from "@specglass/core";
15
19
  import { config } from "virtual:specglass/config";
16
20
  import { generateThemeCSS } from "../utils/theme-css";
17
21
 
22
+ export interface FeatureCard {
23
+ title: string;
24
+ description: string;
25
+ icon?: string;
26
+ href?: string;
27
+ /** Card size in the Bento grid: "large" spans 2 columns, "default" spans 1 */
28
+ size?: "large" | "default";
29
+ }
30
+
18
31
  export interface Props {
19
32
  /** Page title from frontmatter */
20
33
  title: string;
@@ -36,6 +49,11 @@ const ctaLabel = (frontmatter.cta_label as string) ?? "Get Started";
36
49
  const ctaHref = (frontmatter.cta_href as string) ?? "/getting-started";
37
50
  const ctaSecondaryLabel = frontmatter.cta_secondary_label as string | undefined;
38
51
  const ctaSecondaryHref = frontmatter.cta_secondary_href as string | undefined;
52
+ const heroImage = frontmatter.hero_image as string | undefined;
53
+
54
+ // Feature cards from frontmatter
55
+ const features = (frontmatter.features as FeatureCard[] | undefined) ?? [];
56
+ const hasFeatures = features.length > 0;
39
57
  ---
40
58
 
41
59
  <html lang="en" class="dark">
@@ -50,28 +68,21 @@ const ctaSecondaryHref = frontmatter.cta_secondary_href as string | undefined;
50
68
  <link rel="preconnect" href="https://fonts.googleapis.com" />
51
69
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
52
70
  <link
53
- href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap"
71
+ href="https://fonts.googleapis.com/css2?family=Geist:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap"
54
72
  rel="stylesheet"
55
73
  />
56
- <!-- FOUC prevention: apply stored theme before first paint -->
74
+
57
75
  <script is:inline>
58
76
  (function () {
59
- function applyStoredTheme() {
60
- var stored = null;
61
- try {
62
- stored = localStorage.getItem("specglass-theme");
63
- } catch (e) {}
64
- var theme = stored === "dark" || stored === "light" ? stored : "dark";
65
- if (theme === "dark") {
66
- document.documentElement.classList.add("dark");
67
- } else {
68
- document.documentElement.classList.remove("dark");
69
- }
70
- }
71
- applyStoredTheme();
72
- if (!window.__sgThemeInit) {
73
- window.__sgThemeInit = true;
74
- document.addEventListener("astro:after-swap", applyStoredTheme);
77
+ var stored = null;
78
+ try {
79
+ stored = localStorage.getItem("specglass-theme");
80
+ } catch (e) {}
81
+ var theme = stored === "dark" || stored === "light" ? stored : "dark";
82
+ if (theme === "dark") {
83
+ document.documentElement.classList.add("dark");
84
+ } else {
85
+ document.documentElement.classList.remove("dark");
75
86
  }
76
87
  })();
77
88
  </script>
@@ -80,7 +91,7 @@ const ctaSecondaryHref = frontmatter.cta_secondary_href as string | undefined;
80
91
  <!-- Skip to content link for accessibility -->
81
92
  <a
82
93
  href="#main-content"
83
- class="sr-only focus:not-sr-only focus:absolute focus:top-2 focus:left-2 focus:z-50 focus:bg-primary focus:text-white focus:px-4 focus:py-2 focus:rounded"
94
+ class="sr-only focus:not-sr-only focus:absolute focus:top-2 focus:left-2 focus:z-50 focus:bg-text focus:text-surface focus:px-4 focus:py-2 focus:rounded"
84
95
  >
85
96
  Skip to content
86
97
  </a>
@@ -91,30 +102,45 @@ const ctaSecondaryHref = frontmatter.cta_secondary_href as string | undefined;
91
102
  </Header>
92
103
 
93
104
  <main id="main-content" class="pt-(--height-header)" data-pagefind-body>
94
- <!-- Hero section -->
95
- <section class="relative overflow-hidden py-24 px-6 text-center" aria-label="Hero">
105
+ <!-- Hero section with animated gradient mesh -->
106
+ <section
107
+ class="landing-hero relative overflow-hidden py-16 sm:py-24 lg:py-32 px-6 text-center"
108
+ aria-label="Hero"
109
+ >
110
+ <!-- Animated gradient mesh background (3 blobs with transform animation) -->
111
+ <div class="absolute inset-0 overflow-hidden" aria-hidden="true">
112
+ <div class="landing-blob landing-blob-1"></div>
113
+ <div class="landing-blob landing-blob-2"></div>
114
+ <div class="landing-blob landing-blob-3"></div>
115
+ </div>
116
+
117
+ <!-- Static gradient fallback layer -->
96
118
  <div
97
- class="absolute inset-0 bg-linear-to-br from-primary/10 via-transparent to-primary/5 dark:from-primary/15 dark:to-primary/5"
119
+ class="absolute inset-0 bg-linear-to-br from-surface-1/50 via-transparent to-surface-1/30"
98
120
  aria-hidden="true"
99
121
  >
100
122
  </div>
123
+
101
124
  <div class="relative max-w-3xl mx-auto">
125
+ <!-- Hero title with gradient text -->
102
126
  <h1
103
- class="text-5xl font-bold tracking-tight mb-6 bg-linear-to-r from-primary to-primary-light bg-clip-text text-transparent"
127
+ class="landing-fade-in text-3xl sm:text-4xl lg:text-5xl font-bold tracking-tight mb-6 text-text"
104
128
  >
105
129
  {heroTitle}
106
130
  </h1>
107
131
  {
108
132
  heroSubtitle && (
109
- <p class="text-xl text-text-muted mb-10 max-w-2xl mx-auto leading-relaxed">
133
+ <p class="landing-fade-in landing-delay-1 text-lg sm:text-xl text-text-muted mb-10 max-w-2xl mx-auto leading-relaxed">
110
134
  {heroSubtitle}
111
135
  </p>
112
136
  )
113
137
  }
114
- <div class="flex justify-center gap-4 flex-wrap">
138
+
139
+ <!-- CTA buttons -->
140
+ <div class="landing-fade-in landing-delay-2 flex justify-center gap-4 flex-wrap">
115
141
  <a
116
142
  href={ctaHref}
117
- class="inline-flex items-center px-6 py-3 bg-primary text-white font-medium rounded-lg hover:bg-primary-dark transition-colors shadow-lg shadow-primary/25"
143
+ class="inline-flex items-center px-6 py-3 bg-text text-surface font-semibold rounded-lg hover:opacity-90 transition-opacity duration-150"
118
144
  >
119
145
  {ctaLabel}
120
146
  </a>
@@ -122,16 +148,76 @@ const ctaSecondaryHref = frontmatter.cta_secondary_href as string | undefined;
122
148
  ctaSecondaryLabel && ctaSecondaryHref && (
123
149
  <a
124
150
  href={ctaSecondaryHref}
125
- class="inline-flex items-center px-6 py-3 border border-border text-text font-medium rounded-lg hover:bg-hover-bg transition-colors"
151
+ class="inline-flex items-center px-6 py-3 border border-border text-text font-medium rounded-lg hover:bg-hover-bg transition-all duration-200 hover:-translate-y-0.5"
126
152
  >
127
153
  {ctaSecondaryLabel}
128
154
  </a>
129
155
  )
130
156
  }
131
157
  </div>
158
+
159
+ <!-- Optional hero image -->
160
+ {
161
+ heroImage && (
162
+ <div class="landing-fade-in landing-delay-3 mt-12 max-w-4xl mx-auto">
163
+ <img
164
+ src={heroImage}
165
+ alt={`${heroTitle} screenshot`}
166
+ class="rounded-xl shadow-2xl border border-border/50 w-full"
167
+ loading="eager"
168
+ />
169
+ </div>
170
+ )
171
+ }
132
172
  </div>
133
173
  </section>
134
174
 
175
+ <!-- Section divider: hero → features/content -->
176
+ <div class="landing-divider mx-auto" aria-hidden="true"></div>
177
+
178
+ <!-- Feature cards grid (from frontmatter) -->
179
+ {
180
+ hasFeatures && (
181
+ <section class="max-w-(--width-content-max) mx-auto px-6 py-12" aria-label="Features">
182
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
183
+ {features.map((feature: FeatureCard) => {
184
+ const isLarge = feature.size === "large";
185
+ const inner = (
186
+ <div
187
+ class:list={[
188
+ "landing-glow-card group rounded-xl border border-border p-6 transition-all duration-200",
189
+ "bg-surface-1 hover:border-border hover:-translate-y-0.5",
190
+ isLarge && "md:col-span-2",
191
+ !isLarge && "col-span-1",
192
+ feature.href && "cursor-pointer",
193
+ ]}
194
+ >
195
+ {feature.icon && (
196
+ <div class="mb-4 text-3xl" aria-hidden="true">
197
+ {feature.icon}
198
+ </div>
199
+ )}
200
+ <h3 class="text-lg font-semibold text-text mb-2">{feature.title}</h3>
201
+ <p class="text-sm text-text-muted leading-relaxed">{feature.description}</p>
202
+ </div>
203
+ );
204
+
205
+ return feature.href ? (
206
+ <a href={feature.href} class="no-underline">
207
+ {inner}
208
+ </a>
209
+ ) : (
210
+ inner
211
+ );
212
+ })}
213
+ </div>
214
+ </section>
215
+ )
216
+ }
217
+
218
+ <!-- Section divider: features → content (only if features present) -->
219
+ {hasFeatures && <div class="landing-divider mx-auto" aria-hidden="true" />}
220
+
135
221
  <!-- Content area (renders MDX body) -->
136
222
  <article class="sg-content max-w-(--width-content-max) mx-auto px-6 py-12">
137
223
  <slot />
@@ -141,3 +227,238 @@ const ctaSecondaryHref = frontmatter.cta_secondary_href as string | undefined;
141
227
  <Footer />
142
228
  </body>
143
229
  </html>
230
+
231
+ <style>
232
+ /* ─── Gradient Blob Base ──────────────────────────────────── */
233
+ .landing-blob {
234
+ position: absolute;
235
+ border-radius: 50%;
236
+ will-change: transform;
237
+ /* Prevent blobs from triggering scrollbar */
238
+ pointer-events: none;
239
+ }
240
+
241
+ .landing-blob-1 {
242
+ width: 60%;
243
+ height: 60%;
244
+ top: 10%;
245
+ left: 5%;
246
+ background: radial-gradient(ellipse at center, oklch(0.82 0.24 135 / 0.08), transparent 70%);
247
+ }
248
+ .landing-blob-2 {
249
+ width: 50%;
250
+ height: 70%;
251
+ top: -10%;
252
+ right: 0;
253
+ background: radial-gradient(ellipse at center, oklch(0.88 0.2 135 / 0.06), transparent 70%);
254
+ }
255
+ .landing-blob-3 {
256
+ width: 55%;
257
+ height: 50%;
258
+ bottom: 0;
259
+ left: 20%;
260
+ background: radial-gradient(ellipse at center, oklch(0.82 0.24 135 / 0.04), transparent 70%);
261
+ }
262
+
263
+ @media (prefers-reduced-motion: no-preference) {
264
+ .landing-blob-1 {
265
+ animation: blob-drift-1 20s ease-in-out infinite alternate;
266
+ }
267
+ .landing-blob-2 {
268
+ animation: blob-drift-2 24s ease-in-out infinite alternate;
269
+ }
270
+ .landing-blob-3 {
271
+ animation: blob-drift-3 18s ease-in-out infinite alternate;
272
+ }
273
+ }
274
+
275
+ @keyframes blob-drift-1 {
276
+ 0% {
277
+ transform: translate(0, 0) scale(1);
278
+ }
279
+ 50% {
280
+ transform: translate(10%, 15%) scale(1.1);
281
+ }
282
+ 100% {
283
+ transform: translate(5%, -5%) scale(0.95);
284
+ }
285
+ }
286
+ @keyframes blob-drift-2 {
287
+ 0% {
288
+ transform: translate(0, 0) scale(1);
289
+ }
290
+ 50% {
291
+ transform: translate(-15%, 10%) scale(1.05);
292
+ }
293
+ 100% {
294
+ transform: translate(-5%, 20%) scale(1.1);
295
+ }
296
+ }
297
+ @keyframes blob-drift-3 {
298
+ 0% {
299
+ transform: translate(0, 0) scale(1);
300
+ }
301
+ 50% {
302
+ transform: translate(15%, -10%) scale(1.15);
303
+ }
304
+ 100% {
305
+ transform: translate(-10%, 5%) scale(0.9);
306
+ }
307
+ }
308
+
309
+ /* ─── Dark mode blob opacity boost ───────────────────────── */
310
+ :global(.dark) .landing-blob-1 {
311
+ background: radial-gradient(ellipse at center, oklch(0.82 0.24 135 / 0.15), transparent 70%);
312
+ }
313
+ :global(.dark) .landing-blob-2 {
314
+ background: radial-gradient(ellipse at center, oklch(0.88 0.2 135 / 0.1), transparent 70%);
315
+ }
316
+ :global(.dark) .landing-blob-3 {
317
+ background: radial-gradient(ellipse at center, oklch(0.82 0.24 135 / 0.08), transparent 70%);
318
+ }
319
+
320
+ /* ─── Entrance Animations ────────────────────────────────── */
321
+ .landing-fade-in {
322
+ opacity: 0;
323
+ transform: translateY(1rem);
324
+ }
325
+
326
+ @media (prefers-reduced-motion: no-preference) {
327
+ .landing-fade-in {
328
+ animation: landing-enter 150ms ease-out forwards;
329
+ }
330
+
331
+ .landing-delay-1 {
332
+ animation-delay: 50ms;
333
+ }
334
+ .landing-delay-2 {
335
+ animation-delay: 100ms;
336
+ }
337
+ .landing-delay-3 {
338
+ animation-delay: 150ms;
339
+ }
340
+ }
341
+
342
+ @media (prefers-reduced-motion: reduce) {
343
+ .landing-fade-in {
344
+ opacity: 1;
345
+ transform: none;
346
+ }
347
+ }
348
+
349
+ @keyframes landing-enter {
350
+ to {
351
+ opacity: 1;
352
+ transform: translateY(0);
353
+ }
354
+ }
355
+
356
+ /* ─── Section Divider ────────────────────────────────────── */
357
+ .landing-divider {
358
+ height: 1px;
359
+ max-width: var(--width-content-max);
360
+ background: linear-gradient(
361
+ to right,
362
+ transparent,
363
+ oklch(0.82 0.24 135 / 0.3),
364
+ oklch(0.88 0.2 135 / 0.2),
365
+ transparent
366
+ );
367
+ }
368
+
369
+ /* ─── Mouse-Tracking Glow on Feature Cards ──────────────── */
370
+ /* The signature "intelligence" interaction from the Brand Architecture
371
+ * Report §5.2/§6.2. A radial gradient follows the cursor via CSS
372
+ * custom properties set by JS. Creates an "active compute" metaphor. */
373
+ .landing-glow-card {
374
+ position: relative;
375
+ overflow: hidden;
376
+ }
377
+
378
+ .landing-glow-card::after {
379
+ content: "";
380
+ position: absolute;
381
+ inset: -1px;
382
+ z-index: 0;
383
+ border-radius: inherit;
384
+ background: radial-gradient(
385
+ 300px circle at var(--mouse-x, 50%) var(--mouse-y, 50%),
386
+ oklch(0.82 0.24 135 / 0.15),
387
+ transparent 40%
388
+ );
389
+ opacity: 0;
390
+ transition: opacity 150ms ease;
391
+ pointer-events: none;
392
+ }
393
+
394
+ .landing-glow-card:hover::after {
395
+ opacity: 1;
396
+ }
397
+
398
+ /* Ensure card content sits above the glow layer */
399
+ .landing-glow-card > * {
400
+ position: relative;
401
+ z-index: 1;
402
+ }
403
+
404
+ @media (prefers-reduced-motion: reduce) {
405
+ .landing-glow-card::after {
406
+ transition: none;
407
+ }
408
+ /* Fallback: static border color change instead of glow */
409
+ .landing-glow-card:hover {
410
+ border-color: oklch(0.82 0.24 135 / 0.5);
411
+ }
412
+ .landing-glow-card:hover::after {
413
+ opacity: 0;
414
+ }
415
+ }
416
+ </style>
417
+
418
+ <script>
419
+ function initGlowCards() {
420
+ const cards = document.querySelectorAll<HTMLElement>(".landing-glow-card");
421
+ if (!cards.length) return;
422
+
423
+ // Check if user prefers reduced motion
424
+ const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
425
+ if (prefersReducedMotion) return;
426
+
427
+ let rafId: number | null = null;
428
+
429
+ function handleMouseMove(e: MouseEvent) {
430
+ const card = e.currentTarget as HTMLElement;
431
+ const rect = card.getBoundingClientRect();
432
+ // Position relative to card, as CSS values
433
+ const x = e.clientX - rect.left;
434
+ const y = e.clientY - rect.top;
435
+
436
+ if (rafId !== null) cancelAnimationFrame(rafId);
437
+ rafId = requestAnimationFrame(() => {
438
+ card.style.setProperty("--mouse-x", `${x}px`);
439
+ card.style.setProperty("--mouse-y", `${y}px`);
440
+ rafId = null;
441
+ });
442
+ }
443
+
444
+ cards.forEach((card) => {
445
+ card.addEventListener("mousemove", handleMouseMove, { passive: true });
446
+ });
447
+
448
+ // Cleanup on Astro page transition
449
+ document.addEventListener(
450
+ "astro:before-swap",
451
+ () => {
452
+ cards.forEach((card) => {
453
+ card.removeEventListener("mousemove", handleMouseMove);
454
+ });
455
+ if (rafId !== null) cancelAnimationFrame(rafId);
456
+ },
457
+ { once: true },
458
+ );
459
+ }
460
+
461
+ // Init on first load + on Astro page transitions
462
+ initGlowCards();
463
+ document.addEventListener("astro:after-swap", initGlowCards);
464
+ </script>
@@ -14,6 +14,9 @@
14
14
  */
15
15
 
16
16
  function initCopyButtons() {
17
+ const COPY_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="sg-copy-icon" aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>`;
18
+ const CHECK_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="sg-copy-check" style="color:#22c55e" aria-hidden="true"><polyline points="20 6 9 17 4 12"/></svg>`;
19
+
17
20
  document
18
21
  .querySelectorAll<HTMLDivElement>(".code-block--fenced")
19
22
  .forEach((block) => {
@@ -27,15 +30,16 @@ function initCopyButtons() {
27
30
  btn.className = "sg-copy-btn-native sg-copy-btn";
28
31
  btn.setAttribute("aria-label", "Copy code");
29
32
  btn.title = "Copy code";
30
- btn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>`;
33
+ // Render both icons overlaid CSS controls visibility
34
+ btn.innerHTML = COPY_SVG + CHECK_SVG;
31
35
 
32
36
  btn.addEventListener("click", () => {
33
37
  navigator.clipboard.writeText(code).then(() => {
34
- btn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color:#22c55e"><polyline points="20 6 9 17 4 12"/></svg>`;
38
+ btn.classList.add("sg-copy-btn-copied");
35
39
  btn.setAttribute("aria-label", "Copied!");
36
40
  btn.title = "Copied!";
37
41
  setTimeout(() => {
38
- btn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>`;
42
+ btn.classList.remove("sg-copy-btn-copied");
39
43
  btn.setAttribute("aria-label", "Copy code");
40
44
  btn.title = "Copy code";
41
45
  }, 2000);
@@ -52,6 +56,7 @@ function initCopyButtons() {
52
56
  });
53
57
  }
54
58
 
59
+
55
60
  // Run on initial load
56
61
  document.addEventListener("DOMContentLoaded", initCopyButtons);
57
62