starlight-theme-bejamas 0.0.0-canary.0d34eb2

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.
@@ -0,0 +1,175 @@
1
+ ---
2
+ import { Image } from "astro:assets";
3
+ import { Button } from "virtual:starlight-theme-bejamas/components/button";
4
+
5
+ const { data } = Astro.locals.starlightRoute.entry;
6
+ const { title = data.title, tagline, image, actions = [] } = data.hero || {};
7
+
8
+ const PAGE_TITLE_ID = "_top";
9
+
10
+ const imageAttrs = {
11
+ loading: "eager" as const,
12
+ decoding: "async" as const,
13
+ width: 400,
14
+ height: 400,
15
+ alt: image?.alt || "",
16
+ };
17
+
18
+ let darkImage: ImageMetadata | undefined;
19
+ let lightImage: ImageMetadata | undefined;
20
+ let rawHtml: string | undefined;
21
+ if (image) {
22
+ if ("file" in image) {
23
+ darkImage = image.file;
24
+ } else if ("dark" in image) {
25
+ darkImage = image.dark;
26
+ lightImage = image.light;
27
+ } else {
28
+ rawHtml = image.html;
29
+ }
30
+ }
31
+
32
+ const startlightVariantToBejamasVariant = {
33
+ primary: "default",
34
+ secondary: "secondary",
35
+ minimal: "outline",
36
+ };
37
+ ---
38
+
39
+ <div class="hero">
40
+ {
41
+ darkImage && (
42
+ <Image
43
+ src={darkImage}
44
+ {...imageAttrs}
45
+ class:list={{ "light:sl-hidden": Boolean(lightImage) }}
46
+ />
47
+ )
48
+ }
49
+ {
50
+ lightImage && (
51
+ <Image src={lightImage} {...imageAttrs} class="dark:sl-hidden" />
52
+ )
53
+ }
54
+ {rawHtml && <div class="hero-html sl-flex" set:html={rawHtml} />}
55
+ <div class="sl-flex stack">
56
+ <div class="sl-flex copy">
57
+ <h1 id={PAGE_TITLE_ID} data-page-title set:html={title} />
58
+ {tagline && <div class="tagline" set:html={tagline} />}
59
+ </div>
60
+ {
61
+ actions.length > 0 && (
62
+ <div class="sl-flex actions">
63
+ {actions.map(
64
+ ({
65
+ attrs: { class: className, ...attrs } = {},
66
+ icon,
67
+ link: href,
68
+ text,
69
+ variant,
70
+ }) => (
71
+ <Button
72
+ {href}
73
+ variant={startlightVariantToBejamasVariant[variant]}
74
+ as="a"
75
+ icon={icon?.name}
76
+ class:list={[className]}
77
+ {...attrs}
78
+ >
79
+ {text}
80
+ {icon?.html && <Fragment set:html={icon.html} />}
81
+ </Button>
82
+ ),
83
+ )}
84
+ </div>
85
+ )
86
+ }
87
+ </div>
88
+ </div>
89
+
90
+ <style>
91
+ @layer starlight.core {
92
+ .hero {
93
+ display: grid;
94
+ align-items: center;
95
+ gap: 1rem;
96
+ padding-bottom: 1rem;
97
+ }
98
+
99
+ .hero > img,
100
+ .hero > .hero-html {
101
+ object-fit: contain;
102
+ width: min(70%, 20rem);
103
+ height: auto;
104
+ margin-inline: auto;
105
+ }
106
+
107
+ .stack {
108
+ flex-direction: column;
109
+ gap: clamp(1.5rem, calc(1.5rem + 1vw), 2rem);
110
+ text-align: center;
111
+ }
112
+
113
+ .copy {
114
+ flex-direction: column;
115
+ gap: 1rem;
116
+ align-items: center;
117
+ }
118
+
119
+ .copy > * {
120
+ max-width: 50ch;
121
+ }
122
+
123
+ h1 {
124
+ font-size: clamp(
125
+ var(--sl-text-3xl),
126
+ calc(0.25rem + 5vw),
127
+ var(--sl-text-6xl)
128
+ );
129
+ line-height: var(--sl-line-height-headings);
130
+ font-weight: 600;
131
+ color: var(--foreground);
132
+ }
133
+
134
+ .tagline {
135
+ font-size: clamp(
136
+ var(--sl-text-base),
137
+ calc(0.0625rem + 2vw),
138
+ var(--sl-text-xl)
139
+ );
140
+ color: var(--muted-foreground);
141
+ }
142
+
143
+ .actions {
144
+ gap: 1rem 0.75rem;
145
+ flex-wrap: wrap;
146
+ justify-content: center;
147
+ }
148
+
149
+ @media (min-width: 50rem) {
150
+ .hero {
151
+ grid-template-columns: 7fr 4fr;
152
+ gap: 3%;
153
+ padding-block: clamp(2.5rem, calc(1rem + 10vmin), 10rem);
154
+ }
155
+
156
+ .hero > img,
157
+ .hero > .hero-html {
158
+ order: 2;
159
+ width: min(100%, 25rem);
160
+ }
161
+
162
+ .stack {
163
+ text-align: start;
164
+ }
165
+
166
+ .copy {
167
+ align-items: flex-start;
168
+ }
169
+
170
+ .actions {
171
+ justify-content: flex-start;
172
+ }
173
+ }
174
+ }
175
+ </style>
@@ -0,0 +1,275 @@
1
+ ---
2
+ import TableOfContentsList from "./TableOfContents/TableOfContentsList.astro";
3
+
4
+ const { toc } = Astro.locals.starlightRoute;
5
+ ---
6
+
7
+ {
8
+ toc && (
9
+ <mobile-starlight-toc
10
+ data-min-h={toc.minHeadingLevel}
11
+ data-max-h={toc.maxHeadingLevel}
12
+ >
13
+ <nav aria-labelledby="starlight__on-this-page--mobile">
14
+ <div class="border-t starlight__separator" />
15
+ <details id="starlight__mobile-toc">
16
+ <summary id="starlight__on-this-page--mobile" class="sl-flex">
17
+ <span class="toggle sl-flex">
18
+ <span class="toggle-label sl-flex">
19
+ <span
20
+ class="toc-progress"
21
+ role="progressbar"
22
+ aria-label="Current section progress"
23
+ aria-valuemin="0"
24
+ aria-valuemax="100"
25
+ aria-valuenow="0"
26
+ >
27
+ <span class="toc-progress__fill" aria-hidden="true" />
28
+ </span>
29
+ <span class="toggle-text">
30
+ {Astro.locals.t("tableOfContents.onThisPage")}
31
+ </span>
32
+ </span>
33
+ </span>
34
+ <span class="display-current" />
35
+ </summary>
36
+ <div class="dropdown">
37
+ <TableOfContentsList toc={toc.items} isMobile />
38
+ </div>
39
+ </details>
40
+ </nav>
41
+ </mobile-starlight-toc>
42
+ )
43
+ }
44
+
45
+ <style>
46
+ @layer starlight.core {
47
+ nav {
48
+ position: fixed;
49
+ z-index: var(--sl-z-index-toc);
50
+ top: calc(var(--sl-nav-height) - 1px);
51
+ inset-inline: 0;
52
+ border-top: 1px solid var(--sl-color-gray-5);
53
+ background-color: var(--sl-color-bg-nav);
54
+ }
55
+ @media (min-width: 64rem) {
56
+ nav {
57
+ inset-inline-start: var(--sl-content-inline-start, 0);
58
+ }
59
+ }
60
+
61
+ summary {
62
+ gap: 0.5rem;
63
+ align-items: center;
64
+ height: var(--sl-mobile-toc-height);
65
+ border-bottom: 1px solid var(--sl-color-hairline-shade);
66
+ padding: 0.5rem calc(var(--sl-content-pad-x) + var(--spacing));
67
+ font-size: var(--sl-text-xs);
68
+ outline-offset: var(--sl-outline-offset-inside);
69
+ }
70
+ summary::marker,
71
+ summary::-webkit-details-marker {
72
+ display: none;
73
+ }
74
+
75
+ .starlight__separator {
76
+ margin-inline: calc(var(--spacing) * 6);
77
+ }
78
+ @media (min-width: 64rem) {
79
+ .starlight__separator {
80
+ margin-left: 0;
81
+ }
82
+ }
83
+
84
+ .toggle {
85
+ flex-shrink: 0;
86
+ gap: 1rem;
87
+ align-items: center;
88
+ justify-content: space-between;
89
+ border: 1px solid var(--sl-color-gray-5);
90
+ border-radius: 0.5rem;
91
+ padding-block: 0.5rem;
92
+ padding-inline-start: 0.75rem;
93
+ padding-inline-end: 0.5rem;
94
+ line-height: 1;
95
+ background-color: var(--sl-color-black);
96
+ user-select: none;
97
+ cursor: pointer;
98
+ font-weight: 500;
99
+ }
100
+ .toggle-label {
101
+ gap: calc(var(--spacing) * 2);
102
+ align-items: center;
103
+ }
104
+ .toc-progress {
105
+ --toc-progress-size: calc(var(--spacing) * 4);
106
+ --toc-progress-fill: var(--accent);
107
+ display: inline-flex;
108
+ width: var(--toc-progress-size);
109
+ height: var(--toc-progress-size);
110
+ border-radius: 999px;
111
+ border: 1px solid var(--toc-progress-fill);
112
+ align-items: center;
113
+ justify-content: center;
114
+ /* background: radial-gradient(
115
+ circle,
116
+ rgba(2, 123, 249, 0.25) 0,
117
+ rgba(2, 123, 249, 0.25) 60%,
118
+ transparent 60%
119
+ ); */
120
+ flex-shrink: 0;
121
+ }
122
+ .toc-progress__fill {
123
+ --toc-progress-angle: 0deg;
124
+ width: calc(var(--toc-progress-size) - calc(var(--spacing) * 1.5));
125
+ height: calc(var(--toc-progress-size) - calc(var(--spacing) * 1.5));
126
+ border-radius: inherit;
127
+ background: conic-gradient(
128
+ from -90deg,
129
+ var(--toc-progress-fill) var(--toc-progress-angle),
130
+ var(--background) var(--toc-progress-angle)
131
+ );
132
+ /* box-shadow: 0 0 2px rgba(2, 123, 249, 0.4); */
133
+ transition: background 200ms ease-out;
134
+ }
135
+ details[open] .toggle {
136
+ color: var(--sl-color-white);
137
+ border-color: var(--sl-color-accent);
138
+ }
139
+ details .toggle:hover {
140
+ color: var(--sl-color-white);
141
+ border-color: var(--sl-color-gray-2);
142
+ }
143
+
144
+ :global([dir="rtl"]) .caret {
145
+ transform: rotateZ(180deg);
146
+ }
147
+ details[open] .caret {
148
+ transform: rotateZ(90deg);
149
+ }
150
+
151
+ .display-current {
152
+ white-space: nowrap;
153
+ text-overflow: ellipsis;
154
+ overflow: hidden;
155
+ color: var(--sl-color-white);
156
+ }
157
+
158
+ .dropdown {
159
+ --border-top: 1px;
160
+ margin-top: calc(-1 * var(--border-top));
161
+ border: var(--border-top) solid var(--sl-color-gray-6);
162
+ border-top-color: var(--sl-color-hairline-shade);
163
+ max-height: calc(
164
+ 85vh - var(--sl-nav-height) - var(--sl-mobile-toc-height)
165
+ );
166
+ overflow-y: auto;
167
+ background-color: var(--sl-color-black);
168
+ box-shadow: var(--sl-shadow-md);
169
+ overscroll-behavior: contain;
170
+ }
171
+ }
172
+ </style>
173
+
174
+ <script>
175
+ import { StarlightTOC } from "./TableOfContents/starlight-toc";
176
+
177
+ class MobileStarlightTOC extends StarlightTOC {
178
+ private displayCurrent: HTMLSpanElement | null = null;
179
+ private indicator: HTMLSpanElement | null = null;
180
+ private indicatorFill: HTMLSpanElement | null = null;
181
+ private tocLinks: HTMLAnchorElement[] = [];
182
+
183
+ override set current(link: HTMLAnchorElement) {
184
+ super.current = link;
185
+ this.updateDisplay(link);
186
+ this.updateProgress(link);
187
+ }
188
+
189
+ constructor() {
190
+ super();
191
+ this.displayCurrent = this.querySelector(".display-current");
192
+ this.indicator = this.querySelector(".toc-progress");
193
+ this.indicatorFill = this.querySelector(".toc-progress__fill");
194
+
195
+ const links = this.getLinks();
196
+ const initial = links.find(
197
+ (a) => a.getAttribute("aria-current") === "true",
198
+ );
199
+ if (initial) {
200
+ this.updateDisplay(initial);
201
+ this.updateProgress(initial);
202
+ }
203
+
204
+ const details = this.querySelector("details");
205
+ if (!details) return;
206
+ const closeToC = () => {
207
+ details.open = false;
208
+ };
209
+ // Close the table of contents whenever a link is clicked.
210
+ links.forEach((a) => {
211
+ a.addEventListener("click", closeToC);
212
+ });
213
+ // Close the table of contents when a user clicks outside of it.
214
+ window.addEventListener("click", (e) => {
215
+ if (!details.contains(e.target as Node)) closeToC();
216
+ });
217
+ // Or when they press the escape key.
218
+ window.addEventListener("keydown", (e) => {
219
+ if (e.key === "Escape" && details.open) {
220
+ const hasFocus = details.contains(document.activeElement);
221
+ closeToC();
222
+ if (hasFocus) {
223
+ const summary = details.querySelector("summary");
224
+ if (summary) summary.focus();
225
+ }
226
+ }
227
+ });
228
+ }
229
+
230
+ private getLinks(): HTMLAnchorElement[] {
231
+ if (!this.tocLinks.length) {
232
+ this.tocLinks = Array.from(
233
+ this.querySelectorAll<HTMLAnchorElement>("details a"),
234
+ );
235
+ }
236
+ return this.tocLinks;
237
+ }
238
+
239
+ private updateDisplay(link: HTMLAnchorElement) {
240
+ if (!this.displayCurrent) {
241
+ this.displayCurrent = this.querySelector(
242
+ ".display-current",
243
+ ) as HTMLSpanElement | null;
244
+ }
245
+ if (!this.displayCurrent) return;
246
+ const text = (link.textContent || "").trim();
247
+ this.displayCurrent.textContent = text;
248
+ }
249
+
250
+ private updateProgress(link: HTMLAnchorElement) {
251
+ if (!this.indicator) {
252
+ this.indicator = this.querySelector(".toc-progress");
253
+ }
254
+ if (!this.indicatorFill) {
255
+ this.indicatorFill = this.querySelector(
256
+ ".toc-progress__fill",
257
+ ) as HTMLSpanElement | null;
258
+ }
259
+ const indicator = this.indicator;
260
+ const fill = this.indicatorFill;
261
+ const links = this.getLinks();
262
+ if (!indicator || !fill || !links.length) return;
263
+ const index = links.indexOf(link);
264
+ if (index === -1) return;
265
+ const progress = (index + 1) / links.length;
266
+ const percent = Math.round(progress * 100);
267
+ const angle = `${(progress * 360).toFixed(2)}deg`;
268
+ fill.style.setProperty("--toc-progress-angle", angle);
269
+ indicator.setAttribute("aria-valuenow", percent.toString());
270
+ indicator.setAttribute("aria-valuetext", `${percent}% of sections read`);
271
+ }
272
+ }
273
+
274
+ customElements.define("mobile-starlight-toc", MobileStarlightTOC);
275
+ </script>
@@ -0,0 +1,187 @@
1
+ ---
2
+ import MobileMenuToggle from "virtual:starlight/components/MobileMenuToggle";
3
+
4
+ const { hasSidebar } = Astro.locals.starlightRoute;
5
+ const { template } = Astro.locals.starlightRoute.entry.data;
6
+ ---
7
+
8
+ <div class="page sl-flex">
9
+ <header class="header">
10
+ <slot name="header" />
11
+ <div class="fade"></div>
12
+ </header>
13
+ {
14
+ hasSidebar && (
15
+ <nav
16
+ class="sidebar print:hidden"
17
+ aria-label={Astro.locals.t("sidebarNav.accessibleLabel")}
18
+ >
19
+ <MobileMenuToggle />
20
+ <div id="starlight__sidebar" class="sidebar-pane">
21
+ <div class="sidebar-content sl-flex">
22
+ <slot name="sidebar" />
23
+ </div>
24
+ </div>
25
+ </nav>
26
+ )
27
+ }
28
+
29
+ <div class="main-frame">
30
+ {
31
+ template === "splash" ? (
32
+ <slot />
33
+ ) : (
34
+ <div class="main-frame-inner">
35
+ <slot />
36
+ </div>
37
+ )
38
+ }
39
+ </div>
40
+ </div>
41
+
42
+ <style>
43
+ @layer bejamas {
44
+ .page {
45
+ flex-direction: column;
46
+ }
47
+
48
+ .fade {
49
+ --blur: 4px;
50
+ --stop: 50%;
51
+ --height: 72px;
52
+ position: fixed;
53
+ pointer-events: none;
54
+ width: 100%;
55
+ height: var(--height);
56
+ user-select: none;
57
+ -webkit-user-select: none;
58
+ left: 0;
59
+ -webkit-backdrop-filter: blur(var(--blur));
60
+ backdrop-filter: blur(var(--blur));
61
+
62
+ background: linear-gradient(to top, transparent, var(--background));
63
+ mask-image: linear-gradient(
64
+ to bottom,
65
+ var(--background) var(--stop),
66
+ transparent
67
+ );
68
+ top: 0;
69
+ }
70
+
71
+ .header {
72
+ z-index: var(--sl-z-index-navbar);
73
+ position: fixed;
74
+ inset-inline-start: 0;
75
+ inset-block-start: 0;
76
+ width: 100%;
77
+ height: var(--sl-nav-height);
78
+ /* border-bottom: 1px solid var(--sl-color-hairline-shade); */
79
+ padding: var(--sl-nav-pad-y) var(--sl-nav-pad-x);
80
+ padding-inline-end: var(--sl-nav-pad-x);
81
+ /* background-color: var(--sl-color-bg-nav); */
82
+ }
83
+
84
+ .header {
85
+ background-color: var(--background);
86
+ }
87
+
88
+ @media (min-width: 72rem) {
89
+ .header {
90
+ background-color: transparent;
91
+ }
92
+ }
93
+
94
+ :global([data-has-sidebar]) .header {
95
+ padding-inline-end: calc(
96
+ var(--sl-nav-gap) + var(--sl-nav-pad-x) + var(--sl-menu-button-size)
97
+ );
98
+ }
99
+
100
+ .sidebar-pane {
101
+ visibility: var(--sl-sidebar-visibility, hidden);
102
+ position: fixed;
103
+ z-index: var(--sl-z-index-menu);
104
+ inset-block: var(--sl-nav-height) 0;
105
+ inset-inline-start: 0;
106
+ width: 100%;
107
+ background-color: var(--background);
108
+ overflow-y: auto;
109
+ }
110
+
111
+ :global([aria-expanded="true"]) ~ .sidebar-pane {
112
+ --sl-sidebar-visibility: visible;
113
+ }
114
+
115
+ .sidebar-content {
116
+ height: 100%;
117
+ min-height: max-content;
118
+ padding: 1rem var(--sl-sidebar-pad-x) 0;
119
+ flex-direction: column;
120
+ gap: 1rem;
121
+ }
122
+
123
+ @media (min-width: 64rem) {
124
+ .sidebar-content::after {
125
+ content: "";
126
+ padding-bottom: 1px;
127
+ }
128
+ }
129
+
130
+ .main-frame {
131
+ padding-top: calc(var(--sl-nav-height) + var(--sl-mobile-toc-height));
132
+ /* padding-inline-start: var(--sl-content-inline-start); */
133
+ }
134
+
135
+ .main-frame-inner {
136
+ position: relative;
137
+ background-color: var(--sl-main-frame-inner-background);
138
+ border: 1px solid var(--sl-main-frame-inner-border);
139
+ border-bottom: 0;
140
+ min-height: calc(100svh - var(--sl-nav-height));
141
+ border-radius: calc(var(--radius) + 12px) calc(var(--radius) + 12px) 0 0;
142
+ margin-inline: calc(var(--spacing) * 1);
143
+ margin-top: calc(var(--spacing) * 4);
144
+ }
145
+
146
+ @media (min-width: 64rem) {
147
+ .main-frame-inner {
148
+ margin-inline: 0;
149
+ margin-top: 0;
150
+ }
151
+ }
152
+
153
+ .main-frame-inner::before,
154
+ .main-frame-inner::after {
155
+ content: "";
156
+ position: absolute;
157
+ width: 1px;
158
+ height: calc(var(--spacing) * 18);
159
+ background: linear-gradient(
160
+ to bottom,
161
+ var(--sl-main-frame-inner-border) 0%,
162
+ var(--muted) 100%
163
+ );
164
+ bottom: 0;
165
+ }
166
+
167
+ .main-frame-inner::before {
168
+ left: -1px;
169
+ }
170
+
171
+ .main-frame-inner::after {
172
+ right: -1px;
173
+ }
174
+
175
+ @media (min-width: 50rem) {
176
+ :global([data-has-sidebar]) .header {
177
+ padding-inline-end: var(--sl-nav-pad-x);
178
+ }
179
+ .sidebar-pane {
180
+ --sl-sidebar-visibility: visible;
181
+ width: var(--sl-sidebar-width);
182
+ background-color: var(--sl-color-bg-sidebar);
183
+ border-inline-end: 1px solid var(--sl-color-hairline-shade);
184
+ }
185
+ }
186
+ }
187
+ </style>