@dontfeedmachines/fieldkit 0.0.0-stage → 0.4.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 (50) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/LICENSE +203 -0
  3. package/NOTICE +131 -0
  4. package/README.md +12 -2
  5. package/css/base.css +150 -0
  6. package/package.json +43 -3
  7. package/pouches/sutures/astro/AnsiLogo.astro +51 -0
  8. package/pouches/sutures/astro/Barcode.astro +19 -0
  9. package/pouches/sutures/astro/BomTable.astro +44 -0
  10. package/pouches/sutures/astro/Breadcrumb.astro +56 -0
  11. package/pouches/sutures/astro/BuildStep.astro +42 -0
  12. package/pouches/sutures/astro/BuildSteps.astro +63 -0
  13. package/pouches/sutures/astro/Button.astro +5 -0
  14. package/pouches/sutures/astro/CTA.astro +58 -0
  15. package/pouches/sutures/astro/Difficulty.astro +27 -0
  16. package/pouches/sutures/astro/Footer.astro +80 -0
  17. package/pouches/sutures/astro/Glossary.astro +64 -0
  18. package/pouches/sutures/astro/Grid.astro +54 -0
  19. package/pouches/sutures/astro/Header.astro +270 -0
  20. package/pouches/sutures/astro/Hero.astro +38 -0
  21. package/pouches/sutures/astro/ImageMod.astro +66 -0
  22. package/pouches/sutures/astro/InteractiveCircuit.astro +192 -0
  23. package/pouches/sutures/astro/LegalWarning.astro +18 -0
  24. package/pouches/sutures/astro/MobileMenuToggle.astro +105 -0
  25. package/pouches/sutures/astro/PageFrame.astro +166 -0
  26. package/pouches/sutures/astro/Search.astro +541 -0
  27. package/pouches/sutures/astro/Section.astro +14 -0
  28. package/pouches/sutures/astro/SidebarNav.astro +106 -0
  29. package/pouches/sutures/astro/SidebarRestorePoint.astro +10 -0
  30. package/pouches/sutures/astro/SidebarSublist.astro +257 -0
  31. package/pouches/sutures/astro/SiteTitle.astro +65 -0
  32. package/pouches/sutures/astro/SpecTable.astro +64 -0
  33. package/pouches/sutures/astro/TeletextChar.astro +51 -0
  34. package/pouches/sutures/astro/ThemeSwitch.astro +93 -0
  35. package/pouches/sutures/astro/ThreatLevel.astro +77 -0
  36. package/pouches/sutures/astro/ThreatModel.astro +32 -0
  37. package/pouches/sutures/astro/TwoColumnContent.astro +84 -0
  38. package/pouches/sutures/astro/Win.astro +13 -0
  39. package/pouches/sutures/astro/index.ts +21 -0
  40. package/src/elements.ts +404 -0
  41. package/src/lib/scrapeview.ts +61 -0
  42. package/src/lib/seal.ts +157 -0
  43. package/src/lib/teletext-chars.ts +723 -0
  44. package/src/node/astro.ts +121 -0
  45. package/src/node/registry.ts +221 -0
  46. package/src/node/seal.ts +7 -0
  47. package/src/wiki/ansi.ts +15 -0
  48. package/src/wiki/config.ts +5 -0
  49. package/src/wiki/teletext.ts +21 -0
  50. package/src/wiki/textConverter.ts +16 -0
@@ -0,0 +1,14 @@
1
+ ---
2
+ const { title, description } = Astro.props;
3
+ ---
4
+
5
+ <section class="section">
6
+ <div class="text-center mb-16">
7
+ {title && <h2 class="section-title mb-4" set:html={(title)} />}
8
+ {description && <p class="section-description" set:html={description} />}
9
+ </div>
10
+
11
+ <div>
12
+ <slot />
13
+ </div>
14
+ </section>
@@ -0,0 +1,106 @@
1
+ ---
2
+ import LanguageSelect from "@astrojs/starlight/components/LanguageSelect.astro";
3
+ import { plainLabel } from "../../../src/wiki/textConverter.ts";
4
+
5
+ const { sidebar } = Astro.locals.starlightRoute;
6
+
7
+ // url path
8
+ const currentPath = Astro.url.pathname;
9
+
10
+ function getFirstHref(item: any): string | null {
11
+ if (typeof item?.href === "string" && item.href) {
12
+ return item.href;
13
+ }
14
+ if (item?.entries && Array.isArray(item.entries) && item.entries.length > 0) {
15
+ return getFirstHref(item.entries[0]);
16
+ }
17
+ return null;
18
+ }
19
+
20
+ function hasActiveEntry(item: any, currentPath: string): boolean {
21
+ if (typeof item?.href === "string" && item.href === currentPath) {
22
+ return true;
23
+ }
24
+ if (item?.entries && Array.isArray(item.entries)) {
25
+ return item.entries.some((entry: any) =>
26
+ hasActiveEntry(entry, currentPath)
27
+ );
28
+ }
29
+ return false;
30
+ }
31
+ ---
32
+
33
+ <div class="sidebar-nav">
34
+ <div class="container h-full">
35
+ <div class="sidebar-content-top">
36
+ <ul>
37
+ {
38
+ sidebar.map((item: any) => {
39
+ const href = getFirstHref(item);
40
+ const isActive = hasActiveEntry(item, currentPath);
41
+
42
+ return (
43
+ <li>
44
+ <a
45
+ aria-label={plainLabel(item.label)}
46
+ href={href}
47
+ class={isActive ? "active" : ""}
48
+ >
49
+ {plainLabel(item.label)}
50
+ </a>
51
+ </li>
52
+ );
53
+ })
54
+ }
55
+ </ul>
56
+ <div>
57
+ <LanguageSelect />
58
+ </div>
59
+ </div>
60
+ </div>
61
+ </div>
62
+
63
+ <style>
64
+ @layer starlight.core {
65
+ .sidebar-nav {
66
+ z-index: var(--sl-z-index-navbar);
67
+ position: sticky;
68
+ inset-inline-start: 0;
69
+ inset-block-start: 0;
70
+ width: 100%;
71
+ top: 75px;
72
+ border-bottom: 1px solid
73
+ color-mix(in srgb, var(--sl-color-white) 10%, transparent);
74
+ padding-top: 20px;
75
+ padding-bottom: 0px;
76
+ }
77
+ @media (max-width: 1151px) {
78
+ .sidebar-nav {
79
+ display: none;
80
+ }
81
+ }
82
+ .sidebar-content-top {
83
+ display: flex;
84
+ justify-content: space-between;
85
+ align-items: center;
86
+ }
87
+ .sidebar-content-top ul {
88
+ display: flex;
89
+ align-items: center;
90
+ gap: 2rem;
91
+ list-style: none;
92
+ padding-left: 0px !important;
93
+ }
94
+ .sidebar-content-top ul li a {
95
+ padding: 10px 0px;
96
+ text-decoration: none;
97
+ color: color-mix(in srgb, var(--sl-color-white) 80%, transparent);
98
+ display: inline-block;
99
+ }
100
+ .sidebar-content-top ul li a.active {
101
+ border-bottom: 1px solid var(--sl-color-white);
102
+ color: var(--sl-color-white);
103
+ font-weight: 600;
104
+ }
105
+ }
106
+ </style>
@@ -0,0 +1,10 @@
1
+ ---
2
+ // unique symbol for running index in locals
3
+ const currentGroupIndexSymbol = Symbol.for('starlight-sidebar-group-index');
4
+ const locals = Astro.locals as App.Locals & { [currentGroupIndexSymbol]: number };
5
+
6
+ const index = locals[currentGroupIndexSymbol] || 0;
7
+ locals[currentGroupIndexSymbol] = index + 1;
8
+ ---
9
+
10
+ <sl-sidebar-restore data-index={index}></sl-sidebar-restore>
@@ -0,0 +1,257 @@
1
+ ---
2
+ import { Icon } from "@astrojs/starlight/components";
3
+ import { Badge } from "@astrojs/starlight/components";
4
+ import SidebarRestorePoint from "./SidebarRestorePoint.astro";
5
+ import { plainLabel, getIconFromLabel } from "../../../src/wiki/textConverter.ts";
6
+
7
+ function flattenSidebar(sidebar: any[]): any[] {
8
+ return sidebar.flatMap((entry) => entry.type === "group" ? flattenSidebar(entry.entries) : entry);
9
+ }
10
+
11
+ const { sublist, nested } = Astro.props;
12
+ ---
13
+
14
+ <ul class:list={{ "top-level": !nested }}>
15
+ {
16
+ sublist.map((entry: any) => (
17
+ <li>
18
+ {entry.type === "link" ? (
19
+ <a
20
+ href={entry.href}
21
+ aria-current={entry.isCurrent && "page"}
22
+ class:list={[{ large: !nested }, entry.attrs.class]}
23
+ {...entry.attrs}
24
+ >
25
+ {getIconFromLabel(entry.label) && (
26
+ <Icon
27
+ name={getIconFromLabel(entry.label) as any}
28
+ size="1.3rem"
29
+ class="mr-2.5"
30
+ />
31
+ )}
32
+ <span>{plainLabel(entry.label)}</span>
33
+ {entry.badge && (
34
+ <Badge
35
+ variant={entry.badge.variant}
36
+ class={entry.badge.class}
37
+ text={entry.badge.text}
38
+ />
39
+ )}
40
+ </a>
41
+ ) : (
42
+ <details
43
+ open={
44
+ flattenSidebar(entry.entries).some((i) => i.isCurrent) ||
45
+ !entry.collapsed
46
+ }
47
+ >
48
+ <SidebarRestorePoint />
49
+ <summary>
50
+ <span class="group-label">
51
+ {getIconFromLabel(entry.label) && (
52
+ <Icon
53
+ name={getIconFromLabel(entry.label) as any}
54
+ size="1.3rem"
55
+ class="mr-2.5"
56
+ color={
57
+ flattenSidebar(entry.entries).some((i) => i.isCurrent)
58
+ ? "var(--color-primary)"
59
+ : ""
60
+ }
61
+ />
62
+ )}
63
+
64
+ <span class="large">{plainLabel(entry.label)}</span>
65
+ {entry.badge && (
66
+ <Badge
67
+ variant={entry.badge.variant}
68
+ class={entry.badge.class}
69
+ text={entry.badge.text}
70
+ />
71
+ )}
72
+ </span>
73
+ <Icon name="right-caret" class="caret" size="1.25rem" />
74
+ </summary>
75
+ <Astro.self sublist={entry.entries} nested />
76
+ </details>
77
+ )}
78
+ </li>
79
+ ))
80
+ }
81
+ </ul>
82
+
83
+ <style>
84
+ @layer starlight.core {
85
+ ul {
86
+ --sl-sidebar-item-padding-inline: 0.5rem;
87
+ list-style: none;
88
+ padding: 0;
89
+ }
90
+
91
+ li {
92
+ overflow-wrap: anywhere;
93
+ }
94
+
95
+ ul ul li {
96
+ margin-inline-start: calc(var(--sl-sidebar-item-padding-inline) + 8px);
97
+ border-inline-start: 1px solid var(--sl-color-hairline-light);
98
+ padding-inline-start: var(--sl-sidebar-item-padding-inline);
99
+ }
100
+
101
+ .large {
102
+ font-weight: 500;
103
+ color: var(--sl-color-white);
104
+ }
105
+
106
+ .top-level > li + li {
107
+ margin-top: 0.75rem;
108
+ }
109
+ .top-level > li > details > ul > li {
110
+ margin-inline-start: 0px;
111
+ border-inline-start: 0px;
112
+ padding-inline-start: 0px;
113
+ margin-top: 8px;
114
+ margin-bottom: 8px;
115
+ }
116
+
117
+ .group-label {
118
+ display: flex;
119
+ align-items: center;
120
+ margin-bottom: 8px;
121
+ }
122
+
123
+ summary {
124
+ display: flex;
125
+ align-items: center;
126
+ justify-content: space-between;
127
+ padding: 0.2em var(--sl-sidebar-item-padding-inline);
128
+ line-height: 1.4;
129
+ cursor: pointer;
130
+ user-select: none;
131
+ }
132
+ .top-level > li > details > summary {
133
+ padding-left: 0px !important;
134
+ }
135
+ .top-level > li {
136
+ border-bottom: 1px solid var(--sl-color-hairline);
137
+ padding-bottom: 1.6rem;
138
+ padding-top: 1rem;
139
+ }
140
+ .top-level > li:last-child {
141
+ border-bottom: none;
142
+ padding-bottom: 0px;
143
+ }
144
+ .top-level > li:first-child {
145
+ padding-top: 0px;
146
+ }
147
+ .top-level > li {
148
+ padding-top: 10px;
149
+ padding-bottom: 10px;
150
+ }
151
+
152
+ summary::marker,
153
+ summary::-webkit-details-marker {
154
+ display: none;
155
+ }
156
+
157
+ .caret {
158
+ transition: transform 0.2s ease-in-out;
159
+ flex-shrink: 0;
160
+ }
161
+ .top-level > li > details > summary .caret {
162
+ display: none;
163
+ }
164
+ :global([dir="rtl"]) .caret {
165
+ transform: rotateZ(180deg);
166
+ }
167
+ [open] > summary .caret {
168
+ transform: rotateZ(90deg);
169
+ }
170
+
171
+ a {
172
+ display: flex;
173
+ align-items: center;
174
+ border-radius: 8px;
175
+ text-decoration: none;
176
+ color: var(--sl-color-gray-2);
177
+ padding: 8px 10px;
178
+ line-height: 1.4;
179
+ }
180
+
181
+ a:hover,
182
+ a:focus {
183
+ color: var(--sl-color-white);
184
+ }
185
+
186
+ [aria-current="page"],
187
+ [aria-current="page"]:hover,
188
+ [aria-current="page"]:focus {
189
+ color: var(--color-primary) !important;
190
+ background-color: color-mix(
191
+ in srgb,
192
+ var(--sl-color-white) 6%,
193
+ transparent
194
+ );
195
+ }
196
+ [aria-current="page"] span {
197
+ color: var(--sl-color-white);
198
+ }
199
+
200
+ li:has([aria-current="page"]) {
201
+ padding-top: 4px;
202
+ margin-top: 0px !important;
203
+ margin-bottom: 8px !important;
204
+ }
205
+ a > *:not(:last-child),
206
+ .group-label > *:not(:last-child) {
207
+ margin-inline-end: 0.25em;
208
+ }
209
+
210
+ @media (min-width: 50rem) {
211
+ .top-level > li + li {
212
+ margin-top: 0.5rem;
213
+ }
214
+ .large {
215
+ font-size: var(--sl-text-base);
216
+ }
217
+ }
218
+ }
219
+ </style>
220
+
221
+ <script>
222
+ function initializeSidebar(): void {
223
+ const topLevelItems = document.querySelectorAll(
224
+ ".sidebar-content .top-level > li"
225
+ ) as NodeListOf<HTMLLIElement>;
226
+
227
+ topLevelItems.forEach((item: HTMLLIElement) => {
228
+ const detailsElement = item.querySelector(
229
+ "details"
230
+ ) as HTMLDetailsElement;
231
+
232
+ if (detailsElement) {
233
+ // top-level groups remain permanently expanded to keep teletext navigation index visible
234
+ detailsElement.open = true;
235
+
236
+ const summaryElement = detailsElement.querySelector("summary");
237
+ if (summaryElement) {
238
+ summaryElement.addEventListener("click", (e: Event) => {
239
+ e.preventDefault();
240
+ });
241
+ }
242
+
243
+ detailsElement.addEventListener("toggle", () => {
244
+ if (!detailsElement.open) {
245
+ detailsElement.open = true;
246
+ }
247
+ });
248
+ }
249
+ });
250
+ }
251
+
252
+ if (document.readyState === "loading") {
253
+ document.addEventListener("DOMContentLoaded", initializeSidebar);
254
+ } else {
255
+ initializeSidebar();
256
+ }
257
+ </script>
@@ -0,0 +1,65 @@
1
+ ---
2
+ import { config } from "../../../src/wiki/config.ts";
3
+ import ImageMod from "./ImageMod.astro";
4
+ const { siteTitle, siteTitleHref } = Astro.locals.starlightRoute;
5
+
6
+ const { src, srcDarkmode }: { src?: string; srcDarkmode?: string } =
7
+ Astro.props;
8
+
9
+ const theme_switcher = config.settings.theme_switcher;
10
+ const {
11
+ logo,
12
+ logo_darkmode,
13
+ logo_width,
14
+ logo_height,
15
+ logo_text,
16
+ title,
17
+ }: {
18
+ logo: string;
19
+ logo_darkmode: string;
20
+ logo_width: any;
21
+ logo_height: any;
22
+ logo_text: string;
23
+ title: string;
24
+ } = config.site;
25
+ ---
26
+
27
+ <a href={siteTitleHref} class="navbar-brand inline-block">
28
+ {
29
+ src || srcDarkmode || logo || logo_darkmode ? (
30
+ <>
31
+ <ImageMod
32
+ src={src ? src : logo}
33
+ class={`inline-block ${theme_switcher && "dark:hidden"}`}
34
+ width={logo_width.replace("px", "") * 2}
35
+ height={logo_height.replace("px", "") * 2}
36
+ alt={title}
37
+ style={{
38
+ height: logo_height.replace("px", "") + "px",
39
+ width: logo_width.replace("px", "") + "px",
40
+ }}
41
+ format="webp"
42
+ />
43
+
44
+ {theme_switcher && (
45
+ <ImageMod
46
+ src={srcDarkmode ? srcDarkmode : logo_darkmode}
47
+ class={`hidden dark:inline-block`}
48
+ width={logo_width.replace("px", "") * 2}
49
+ height={logo_height.replace("px", "") * 2}
50
+ alt={title}
51
+ style={{
52
+ height: logo_height.replace("px", "") + "px",
53
+ width: logo_width.replace("px", "") + "px",
54
+ }}
55
+ format="webp"
56
+ />
57
+ )}
58
+ </>
59
+ ) : logo_text ? (
60
+ logo_text
61
+ ) : (
62
+ siteTitle || title
63
+ )
64
+ }</a
65
+ >
@@ -0,0 +1,64 @@
1
+ ---
2
+ // DFM custom components
3
+ export interface Props {
4
+ title?: string;
5
+ specs: Record<string, string>;
6
+ }
7
+
8
+ const { title = "Hardware Specifications", specs } = Astro.props;
9
+ ---
10
+ <div class="spec-table-container">
11
+ <h3>{title}</h3>
12
+ <table class="spec-table">
13
+ <tbody>
14
+ {Object.entries(specs).map(([key, value]) => (
15
+ <tr>
16
+ <td class="spec-key">{key}</td>
17
+ <td class="spec-value">{value}</td>
18
+ </tr>
19
+ ))}
20
+ </tbody>
21
+ </table>
22
+ </div>
23
+
24
+ <style>
25
+ .spec-table-container {
26
+ margin: 2rem 0;
27
+ border: 1px solid var(--sl-color-gray-5, #444);
28
+ border-radius: 0.5rem;
29
+ overflow: hidden;
30
+ background: var(--sl-color-black, #111);
31
+ }
32
+
33
+ .spec-table-container h3 {
34
+ margin: 0;
35
+ padding: 1rem;
36
+ background: var(--sl-color-gray-6, #222);
37
+ border-bottom: 1px solid var(--sl-color-gray-5, #444);
38
+ font-size: 1.1rem;
39
+ color: var(--sl-color-white, #fff);
40
+ }
41
+
42
+ .spec-table {
43
+ width: 100%;
44
+ border-collapse: collapse;
45
+ }
46
+
47
+ .spec-table tr:not(:last-child) {
48
+ border-bottom: 1px solid var(--sl-color-gray-5, #444);
49
+ }
50
+
51
+ .spec-key {
52
+ padding: 1rem;
53
+ font-weight: 600;
54
+ color: var(--sl-color-gray-2, #ccc);
55
+ width: 35%;
56
+ background: var(--sl-color-gray-6, #1a1a1a);
57
+ }
58
+
59
+ .spec-value {
60
+ padding: 1rem;
61
+ color: var(--sl-color-white, #fff);
62
+ font-family: monospace;
63
+ }
64
+ </style>
@@ -0,0 +1,51 @@
1
+ ---
2
+ import { ttCharSpec, ttMosaicSpec } from "../../../src/lib/teletext-chars";
3
+
4
+ interface Props {
5
+ name?: string;
6
+ char?: string;
7
+ mosaic?: number | string;
8
+ separated?: boolean;
9
+ size?: string;
10
+ label?: string;
11
+ title?: string;
12
+ class?: string;
13
+ }
14
+
15
+ const { name, char, mosaic, separated = false, size, label, title, class: cls } = Astro.props;
16
+ const isMosaic = mosaic !== undefined;
17
+ const spec = isMosaic
18
+ ? ttMosaicSpec(mosaic, separated)
19
+ : ttCharSpec(name ?? char ?? "");
20
+ const fallback = !spec ? (char ?? name ?? "") : "";
21
+ const h = size ?? (isMosaic ? "var(--tt-mosaic-height, 1em)" : "var(--tt-char-size, 0.85em)");
22
+ const w = isMosaic ? `calc((${h}) * 2 / 3)` : h;
23
+ ---
24
+ {spec ? (
25
+ <svg
26
+ xmlns="http://www.w3.org/2000/svg"
27
+ class:list={["tt-char", isMosaic && "tt-mosaic", cls]}
28
+ viewBox={`0 0 ${spec.w} ${spec.h}`}
29
+ fill="currentColor"
30
+ shape-rendering="crispEdges"
31
+ style={`--_tt-w:${w};--_tt-h:${h}`}
32
+ role={label ? "img" : undefined}
33
+ aria-label={label || undefined}
34
+ aria-hidden={label ? undefined : "true"}
35
+ >
36
+ {title && <title>{title}</title>}
37
+ <path d={spec.d} />
38
+ </svg>
39
+ ) : (
40
+ <span class={cls} aria-label={label || undefined}>{fallback}</span>
41
+ )}
42
+
43
+ <style>
44
+ .tt-char {
45
+ display: inline-block;
46
+ width: var(--_tt-w, 0.85em);
47
+ height: var(--_tt-h, 0.85em);
48
+ vertical-align: calc(0.5cap - var(--_tt-h, 0.85em) / 2 + var(--tt-char-voffset, -0.02em));
49
+ flex: none;
50
+ }
51
+ </style>
@@ -0,0 +1,93 @@
1
+ ---
2
+ import { config } from "../../../src/wiki/config.ts";
3
+
4
+ const { theme_switcher }: { theme_switcher: boolean } = config.settings;
5
+ const { className }: { className?: string } = Astro.props;
6
+ ---
7
+
8
+ {
9
+ theme_switcher && (
10
+ <div class={`theme-switcher ${className}`}>
11
+ <input id="theme-switcher" data-theme-switcher type="checkbox" />
12
+ <label for="theme-switcher">
13
+ <span class="sr-only">theme switcher</span>
14
+ <svg
15
+ class="dark:opacity-40 opacity-0 size-4 ml-1"
16
+ viewBox="0 0 56 56"
17
+ fill="#fff"
18
+ height="16"
19
+ width="16"
20
+ >
21
+ <path d="M30 4.6c0-1-.9-2-2-2a2 2 0 0 0-2 2v5c0 1 .9 2 2 2s2-1 2-2Zm9.6 9a2 2 0 0 0 0 2.8c.8.8 2 .8 2.9 0L46 13a2 2 0 0 0 0-2.9 2 2 0 0 0-3 0Zm-26 2.8c.7.8 2 .8 2.8 0 .8-.7.8-2 0-2.9L13 10c-.7-.7-2-.8-2.9 0-.7.8-.7 2.1 0 3ZM28 16a12 12 0 0 0-12 12 12 12 0 0 0 12 12 12 12 0 0 0 12-12 12 12 0 0 0-12-12Zm23.3 14c1.1 0 2-.9 2-2s-.9-2-2-2h-4.9a2 2 0 0 0-2 2c0 1.1 1 2 2 2ZM4.7 26a2 2 0 0 0-2 2c0 1.1.9 2 2 2h4.9c1 0 2-.9 2-2s-1-2-2-2Zm37.8 13.6a2 2 0 0 0-3 0 2 2 0 0 0 0 2.9l3.6 3.5a2 2 0 0 0 2.9 0c.8-.8.8-2.1 0-3ZM10 43.1a2 2 0 0 0 0 2.9c.8.7 2.1.8 3 0l3.4-3.5c.8-.8.8-2.1 0-2.9-.8-.8-2-.8-2.9 0Zm20 3.4c0-1.1-.9-2-2-2a2 2 0 0 0-2 2v4.9c0 1 .9 2 2 2s2-1 2-2Z" />
22
+ </svg>
23
+ <span>
24
+ <svg
25
+ class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-10 opacity-100 dark:opacity-0"
26
+ viewBox="0 0 56 56"
27
+ fill="#fff"
28
+ height="16"
29
+ width="16"
30
+ >
31
+ <path d="M30 4.6c0-1-.9-2-2-2a2 2 0 0 0-2 2v5c0 1 .9 2 2 2s2-1 2-2Zm9.6 9a2 2 0 0 0 0 2.8c.8.8 2 .8 2.9 0L46 13a2 2 0 0 0 0-2.9 2 2 0 0 0-3 0Zm-26 2.8c.7.8 2 .8 2.8 0 .8-.7.8-2 0-2.9L13 10c-.7-.7-2-.8-2.9 0-.7.8-.7 2.1 0 3ZM28 16a12 12 0 0 0-12 12 12 12 0 0 0 12 12 12 12 0 0 0 12-12 12 12 0 0 0-12-12Zm23.3 14c1.1 0 2-.9 2-2s-.9-2-2-2h-4.9a2 2 0 0 0-2 2c0 1.1 1 2 2 2ZM4.7 26a2 2 0 0 0-2 2c0 1.1.9 2 2 2h4.9c1 0 2-.9 2-2s-1-2-2-2Zm37.8 13.6a2 2 0 0 0-3 0 2 2 0 0 0 0 2.9l3.6 3.5a2 2 0 0 0 2.9 0c.8-.8.8-2.1 0-3ZM10 43.1a2 2 0 0 0 0 2.9c.8.7 2.1.8 3 0l3.4-3.5c.8-.8.8-2.1 0-2.9-.8-.8-2-.8-2.9 0Zm20 3.4c0-1.1-.9-2-2-2a2 2 0 0 0-2 2v4.9c0 1 .9 2 2 2s2-1 2-2Z" />
32
+ </svg>
33
+ <svg
34
+ class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-10 opacity-0 dark:opacity-100"
35
+ viewBox="0 0 24 24"
36
+ fill="none"
37
+ height="16"
38
+ width="16"
39
+ >
40
+ <path
41
+ fill="#000"
42
+ fill-rule="evenodd"
43
+ clip-rule="evenodd"
44
+ d="M8.2 2.2c1-.4 2 .6 1.6 1.5-1 3-.4 6.4 1.8 8.7a8.4 8.4 0 0 0 8.7 1.8c1-.3 2 .5 1.5 1.5v.1a10.3 10.3 0 0 1-9.4 6.2A10.3 10.3 0 0 1 3.2 6.7c1-2 2.9-3.5 4.9-4.4Z"
45
+ />
46
+ </svg>
47
+ </span>
48
+ <svg
49
+ class="opacity-40 size-4 dark:opacity-0 mr-1"
50
+ viewBox="0 0 24 24"
51
+ fill="none"
52
+ height="16"
53
+ width="16"
54
+ >
55
+ <path
56
+ fill="#000"
57
+ fill-rule="evenodd"
58
+ clip-rule="evenodd"
59
+ d="M8.2 2.2c1-.4 2 .6 1.6 1.5-1 3-.4 6.4 1.8 8.7a8.4 8.4 0 0 0 8.7 1.8c1-.3 2 .5 1.5 1.5v.1a10.3 10.3 0 0 1-9.4 6.2A10.3 10.3 0 0 1 3.2 6.7c1-2 2.9-3.5 4.9-4.4Z"
60
+ />
61
+ </svg>
62
+ </label>
63
+ </div>
64
+ )
65
+ }
66
+
67
+
68
+
69
+ <script>
70
+ const themeSwitcher = document.querySelector(
71
+ "input[data-theme-switcher]"
72
+ ) as HTMLInputElement;
73
+ themeSwitcher?.addEventListener("change", (e) => {
74
+ if ((e.currentTarget as HTMLInputElement).checked) {
75
+ document.documentElement.dataset.theme = "dark";
76
+ localStorage.setItem("starlight-theme", "dark");
77
+ } else {
78
+ document.documentElement.dataset.theme = "light";
79
+ localStorage.setItem("starlight-theme", "light");
80
+ }
81
+ });
82
+
83
+ document.addEventListener("DOMContentLoaded", () => {
84
+ const savedTheme = localStorage.getItem("starlight-theme");
85
+ if (savedTheme === "light") {
86
+ themeSwitcher.checked = false;
87
+ document.documentElement.dataset.theme = "light";
88
+ } else {
89
+ themeSwitcher.checked = true;
90
+ document.documentElement.dataset.theme = "dark";
91
+ }
92
+ });
93
+ </script>