@beatzball/create-litro 0.3.0 → 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.
@@ -36,6 +36,7 @@ export class StarlightPage extends LitElement {
36
36
  currentSlug: { type: String },
37
37
  currentPath: { type: String },
38
38
  noSidebar: { type: Boolean },
39
+ _navOpen: { state: true },
39
40
  };
40
41
 
41
42
  static override styles = css`
@@ -102,6 +103,14 @@ export class StarlightPage extends LitElement {
102
103
  line-height: 1.15;
103
104
  }
104
105
 
106
+ .nav-backdrop {
107
+ position: fixed;
108
+ inset: 0;
109
+ top: var(--sl-nav-height, 3.5rem);
110
+ background: rgba(0, 0, 0, 0.4);
111
+ z-index: 49;
112
+ }
113
+
105
114
  /* Responsive: hide sidebar and TOC on narrow screens */
106
115
  @media (max-width: 72rem) {
107
116
  .body {
@@ -110,7 +119,20 @@ export class StarlightPage extends LitElement {
110
119
  }
111
120
 
112
121
  .sidebar-wrap {
113
- display: none;
122
+ grid-area: unset;
123
+ position: fixed;
124
+ top: var(--sl-nav-height, 3.5rem);
125
+ left: 0;
126
+ z-index: 50;
127
+ height: calc(100vh - var(--sl-nav-height, 3.5rem));
128
+ width: var(--sl-sidebar-width, 16rem);
129
+ transform: translateX(-100%);
130
+ transition: transform 0.2s ease;
131
+ box-shadow: 2px 0 16px rgba(0, 0, 0, 0.12);
132
+ }
133
+
134
+ .sidebar-wrap.nav-open {
135
+ transform: translateX(0);
114
136
  }
115
137
  }
116
138
 
@@ -134,18 +156,40 @@ export class StarlightPage extends LitElement {
134
156
  currentSlug = '';
135
157
  currentPath = '';
136
158
  noSidebar = false;
159
+ _navOpen = false;
160
+
161
+ override updated(changed: Map<string, unknown>) {
162
+ if (changed.has('currentPath') && this._navOpen) {
163
+ this._navOpen = false;
164
+ }
165
+ }
166
+
167
+ private _handleNavToggle() {
168
+ this._navOpen = !this._navOpen;
169
+ }
170
+
171
+ private _closeNav() {
172
+ this._navOpen = false;
173
+ }
137
174
 
138
175
  override render() {
176
+ const hasSidebar = !this.noSidebar;
139
177
  return html`
140
178
  <div class="page-wrap">
141
179
  <starlight-header
142
180
  siteTitle="${this.siteTitle}"
143
181
  .nav="${this.nav}"
144
182
  currentPath="${this.currentPath}"
183
+ .navOpen="${this._navOpen}"
184
+ .hasSidebar="${hasSidebar}"
185
+ @sl-nav-toggle="${this._handleNavToggle}"
145
186
  ></starlight-header>
187
+ ${hasSidebar && this._navOpen ? html`
188
+ <div class="nav-backdrop" @click="${this._closeNav}"></div>
189
+ ` : ''}
146
190
  <div class="body${this.noSidebar ? ' no-sidebar' : ''}">
147
- ${!this.noSidebar ? html`
148
- <aside class="sidebar-wrap">
191
+ ${hasSidebar ? html`
192
+ <aside class="sidebar-wrap${this._navOpen ? ' nav-open' : ''}">
149
193
  <starlight-sidebar
150
194
  .groups="${this.sidebar}"
151
195
  currentSlug="${this.currentSlug}"
@@ -158,7 +202,7 @@ export class StarlightPage extends LitElement {
158
202
  <slot name="content"></slot>
159
203
  </div>
160
204
  </main>
161
- ${!this.noSidebar ? html`
205
+ ${hasSidebar ? html`
162
206
  <aside class="toc-wrap">
163
207
  <starlight-toc .entries="${this.toc}"></starlight-toc>
164
208
  </aside>
@@ -0,0 +1,40 @@
1
+ import hljs from 'highlight.js';
2
+
3
+ const NAMED_ENTITY_MAP: Record<string, string> = {
4
+ '&amp;': '&',
5
+ '&lt;': '<',
6
+ '&gt;': '>',
7
+ '&quot;': '"',
8
+ '&#39;': "'",
9
+ };
10
+
11
+ function decodeEntities(str: string): string {
12
+ return str.replace(/&#x[0-9a-fA-F]+;|&#[0-9]+;|&amp;|&lt;|&gt;|&quot;|&#39;/g, m => {
13
+ if (m.startsWith('&#x')) return String.fromCodePoint(parseInt(m.slice(3, -1), 16));
14
+ if (m.startsWith('&#')) return String.fromCodePoint(parseInt(m.slice(2, -1), 10));
15
+ return NAMED_ENTITY_MAP[m]!;
16
+ });
17
+ }
18
+
19
+ /**
20
+ * Post-processes an HTML string, replacing every
21
+ * <pre><code class="language-*">…</code></pre>
22
+ * with a syntax-highlighted version produced by highlight.js.
23
+ *
24
+ * Must be called server-side only (SSG build time).
25
+ */
26
+ export function applyHighlighting(html: string): string {
27
+ return html.replace(
28
+ /<pre><code class="language-([^"]+)">([\s\S]*?)<\/code><\/pre>/g,
29
+ (_match, lang: string, encoded: string) => {
30
+ const code = decodeEntities(encoded);
31
+ let highlighted: string;
32
+ try {
33
+ highlighted = hljs.highlight(code, { language: lang, ignoreIllegals: true }).value;
34
+ } catch {
35
+ highlighted = hljs.highlightAuto(code).value;
36
+ }
37
+ return `<pre><code class="hljs language-${lang}">${highlighted}</code></pre>`;
38
+ },
39
+ );
40
+ }
@@ -10,6 +10,7 @@
10
10
  export const starlightHead = [
11
11
  '<link rel="stylesheet" href="/shoelace/themes/light.css" />',
12
12
  '<link rel="stylesheet" href="/styles/starlight.css" />',
13
+ '<link rel="stylesheet" href="/styles/highlight.css" />',
13
14
  '<script>(function(){',
14
15
  'var s=localStorage.getItem("sl-theme");',
15
16
  'var t=s||(window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light");',
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@beatzball/create-litro",
3
- "version": "0.3.0",
4
- "description": "Create a new Litro app",
3
+ "version": "0.4.1",
4
+ "description": "Scaffold a new Litro app — fullstack SSR, Markdown blog, or docs site. Built on Lit web components and Nitro server.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",
@@ -15,6 +15,20 @@
15
15
  "bin": {
16
16
  "create-litro": "./dist/src/index.js"
17
17
  },
18
+ "keywords": [
19
+ "create",
20
+ "scaffold",
21
+ "cli",
22
+ "lit",
23
+ "web-components",
24
+ "fullstack",
25
+ "ssr",
26
+ "ssg",
27
+ "nitro",
28
+ "starter",
29
+ "template",
30
+ "typescript"
31
+ ],
18
32
  "devDependencies": {
19
33
  "@types/node": "^22.0.0",
20
34
  "typescript": "^5.7.3",
@@ -15,6 +15,7 @@
15
15
  "dependencies": {
16
16
  "@beatzball/litro": "latest",
17
17
  "@shoelace-style/shoelace": "^2.19.0",
18
+ "highlight.js": "^11.10.0",
18
19
  "lit": "^3.2.1",
19
20
  "@lit-labs/ssr": "^3.3.0",
20
21
  "@lit-labs/ssr-client": "^1.1.7",
@@ -1,4 +1,4 @@
1
- import { html } from 'lit';
1
+ import { html, css } from 'lit';
2
2
  import { unsafeHTML } from 'lit/directives/unsafe-html.js';
3
3
  import { customElement } from 'lit/decorators.js';
4
4
  import { LitroPage } from '@beatzball/litro/runtime';
@@ -8,6 +8,7 @@ import type { Post } from 'litro:content';
8
8
  import { getPosts } from 'litro:content';
9
9
  import { siteConfig } from '../../server/starlight.config.js';
10
10
  import { extractHeadings, addHeadingIds } from '../../src/extract-headings.js';
11
+ import { applyHighlighting } from '../../src/highlight.js';
11
12
  import { starlightHead } from '../../src/route-meta.js';
12
13
 
13
14
  // Register components used in render()
@@ -55,7 +56,7 @@ export const pageData = definePageData(async (event) => {
55
56
  }
56
57
 
57
58
  const toc = extractHeadings(doc.rawBody);
58
- const body = addHeadingIds(doc.body);
59
+ const body = applyHighlighting(addHeadingIds(doc.body));
59
60
  const { prevDoc, nextDoc } = computePrevNext(siteConfig.sidebar, slug);
60
61
  const editUrl = siteConfig.editUrlBase
61
62
  ? `${siteConfig.editUrlBase}/content/docs/${slug}.md`
@@ -89,6 +90,78 @@ export const routeMeta = {
89
90
 
90
91
  @customElement('page-docs-slug')
91
92
  export class DocPage extends LitroPage {
93
+ /**
94
+ * Styles injected into page-docs-slug's shadow root so they reach the
95
+ * <div slot="content"> subtree. Global stylesheets (starlight.css,
96
+ * highlight.css) cannot pierce shadow DOM boundaries.
97
+ */
98
+ static override styles = css`
99
+ /* ── Typography for slotted doc content ─────────────────────────── */
100
+ h1, h2, h3, h4, h5, h6 {
101
+ margin-top: 1.5em; margin-bottom: 0.5em;
102
+ font-weight: 600; line-height: 1.25;
103
+ color: var(--sl-color-text);
104
+ }
105
+ h1 { font-size: var(--sl-text-4xl, 2.25rem); }
106
+ h2 { font-size: var(--sl-text-2xl, 1.5rem); border-bottom: 1px solid var(--sl-color-border, #e8e8e8); padding-bottom: 0.25em; }
107
+ h3 { font-size: var(--sl-text-xl, 1.25rem); }
108
+ h4 { font-size: var(--sl-text-lg, 1.125rem); }
109
+ p { margin-top: 0; margin-bottom: 1rem; line-height: 1.7; }
110
+ a { color: var(--sl-color-text-accent, var(--sl-color-accent)); text-decoration: none; }
111
+ a:hover { text-decoration: underline; }
112
+ code {
113
+ font-family: var(--sl-font-mono, ui-monospace, monospace);
114
+ font-size: 0.875em;
115
+ background-color: var(--sl-color-bg-inline-code, #e8e8e8);
116
+ border: 1px solid var(--sl-color-border, #e8e8e8);
117
+ border-radius: 0.25rem;
118
+ padding: 0.15em 0.4em;
119
+ }
120
+ pre {
121
+ background-color: #0d0e11;
122
+ color: #e2e4e9;
123
+ border-radius: 0.375rem;
124
+ padding: 1rem 1.25rem;
125
+ overflow-x: auto;
126
+ margin: 1.5rem 0;
127
+ font-size: var(--sl-text-sm, 0.875rem);
128
+ line-height: 1.6;
129
+ }
130
+ pre code { background: none; border: none; padding: 0; font-size: inherit; }
131
+ ul, ol { padding-left: 1.5rem; margin: 0 0 1rem; }
132
+ li { margin-bottom: 0.25rem; line-height: 1.7; }
133
+ blockquote {
134
+ margin: 1.5rem 0; padding: 0.75rem 1rem;
135
+ border-left: 4px solid var(--sl-color-accent, #ea580c);
136
+ background-color: var(--sl-color-accent-low, #fff7ed);
137
+ border-radius: 0 0.375rem 0.375rem 0;
138
+ }
139
+ hr { border: none; border-top: 1px solid var(--sl-color-border, #e8e8e8); margin: 2rem 0; }
140
+ img { max-width: 100%; height: auto; }
141
+ table { width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: var(--sl-text-sm, 0.875rem); }
142
+ th, td { border: 1px solid var(--sl-color-border, #e8e8e8); padding: 0.5rem 0.75rem; text-align: left; }
143
+ th { background-color: var(--sl-color-gray-1, #f6f6f6); font-weight: 600; }
144
+
145
+ /* ── highlight.js fire theme ─────────────────────────────────────── */
146
+ pre:has(.hljs) { background-color: #0d0d10; color: #cbd5e1; }
147
+ .hljs { color: #cbd5e1; background: transparent; }
148
+ .hljs-keyword, .hljs-selector-tag, .hljs-tag { color: #f97316; }
149
+ .hljs-string, .hljs-attr, .hljs-attribute { color: #38bdf8; }
150
+ .hljs-number, .hljs-literal { color: #fbbf24; }
151
+ .hljs-title, .hljs-title.class_, .hljs-title.function_, .hljs-built_in { color: #fb923c; }
152
+ .hljs-comment { color: #6b7280; font-style: italic; }
153
+ .hljs-variable, .hljs-params { color: #cbd5e1; }
154
+ .hljs-operator, .hljs-punctuation { color: #94a3b8; }
155
+ .hljs-meta, .hljs-meta .hljs-keyword { color: #38bdf8; }
156
+ .hljs-type { color: #fb923c; }
157
+ .hljs-deletion { color: #f87171; background: rgba(248,113,113,.1); }
158
+ .hljs-addition { color: #4ade80; background: rgba(74,222,128,.1); }
159
+ .hljs-section, .hljs-selector-class, .hljs-selector-id { color: #fb923c; }
160
+ .hljs-symbol, .hljs-bullet, .hljs-link { color: #38bdf8; }
161
+ .hljs-emphasis { font-style: italic; }
162
+ .hljs-strong { font-weight: bold; }
163
+ `;
164
+
92
165
  override render() {
93
166
  const data = this.serverData as DocPageData | null;
94
167
  if (!data?.doc) return html`<p>Loading&hellip;</p>`;
@@ -0,0 +1,108 @@
1
+ /* highlight.js — fire theme (always dark background) */
2
+
3
+ pre:has(.hljs) {
4
+ background-color: #0d0d10;
5
+ color: #cbd5e1;
6
+ }
7
+
8
+ [data-theme="dark"] pre:has(.hljs) {
9
+ background-color: #0d0d10;
10
+ color: #cbd5e1;
11
+ }
12
+
13
+ .hljs {
14
+ color: #cbd5e1;
15
+ background: transparent;
16
+ }
17
+
18
+ /* Keywords: const, let, if, class, return … */
19
+ .hljs-keyword,
20
+ .hljs-selector-tag,
21
+ .hljs-tag {
22
+ color: #f97316;
23
+ }
24
+
25
+ /* Strings and attribute values */
26
+ .hljs-string,
27
+ .hljs-attr,
28
+ .hljs-attribute {
29
+ color: #38bdf8;
30
+ }
31
+
32
+ /* Numbers and boolean literals */
33
+ .hljs-number,
34
+ .hljs-literal {
35
+ color: #fbbf24;
36
+ }
37
+
38
+ /* Function/class names, built-ins */
39
+ .hljs-title,
40
+ .hljs-title.class_,
41
+ .hljs-title.function_,
42
+ .hljs-built_in {
43
+ color: #fb923c;
44
+ }
45
+
46
+ /* Comments */
47
+ .hljs-comment {
48
+ color: #6b7280;
49
+ font-style: italic;
50
+ }
51
+
52
+ /* Variables and parameters */
53
+ .hljs-variable,
54
+ .hljs-params {
55
+ color: #cbd5e1;
56
+ }
57
+
58
+ /* Operators and punctuation */
59
+ .hljs-operator,
60
+ .hljs-punctuation {
61
+ color: #94a3b8;
62
+ }
63
+
64
+ /* Meta / imports / decorators */
65
+ .hljs-meta,
66
+ .hljs-meta .hljs-keyword {
67
+ color: #38bdf8;
68
+ }
69
+
70
+ /* Type annotations */
71
+ .hljs-type {
72
+ color: #fb923c;
73
+ }
74
+
75
+ /* Diff deletions */
76
+ .hljs-deletion {
77
+ color: #f87171;
78
+ background: rgba(248, 113, 113, 0.1);
79
+ }
80
+
81
+ /* Diff additions */
82
+ .hljs-addition {
83
+ color: #4ade80;
84
+ background: rgba(74, 222, 128, 0.1);
85
+ }
86
+
87
+ /* Section / selector */
88
+ .hljs-section,
89
+ .hljs-selector-class,
90
+ .hljs-selector-id {
91
+ color: #fb923c;
92
+ }
93
+
94
+ /* Symbol / bullet */
95
+ .hljs-symbol,
96
+ .hljs-bullet,
97
+ .hljs-link {
98
+ color: #38bdf8;
99
+ }
100
+
101
+ /* Emphasis */
102
+ .hljs-emphasis {
103
+ font-style: italic;
104
+ }
105
+
106
+ .hljs-strong {
107
+ font-weight: bold;
108
+ }
@@ -1,5 +1,5 @@
1
- import { LitElement, html, css } from 'lit';
2
- import { customElement } from 'lit/decorators.js';
1
+ import { LitElement, html, css } from "lit";
2
+ import { customElement } from "lit/decorators.js";
3
3
 
4
4
  export interface NavItem {
5
5
  label: string;
@@ -10,12 +10,14 @@ export interface NavItem {
10
10
  * <starlight-header siteTitle="My Docs" .nav=${nav} currentPath="/docs/getting-started">
11
11
  * Top navigation bar with site title, nav links, and dark/light theme toggle.
12
12
  */
13
- @customElement('starlight-header')
13
+ @customElement("starlight-header")
14
14
  export class StarlightHeader extends LitElement {
15
15
  static override properties = {
16
16
  siteTitle: { type: String },
17
17
  nav: { type: Array },
18
18
  currentPath: { type: String },
19
+ navOpen: { type: Boolean },
20
+ hasSidebar: { type: Boolean },
19
21
  _theme: { type: String, state: true },
20
22
  };
21
23
 
@@ -34,7 +36,39 @@ export class StarlightHeader extends LitElement {
34
36
  display: flex;
35
37
  align-items: center;
36
38
  padding: 0 var(--sl-content-pad-x, 1.5rem);
37
- gap: 1.5rem;
39
+ gap: 1rem;
40
+ }
41
+
42
+ .menu-btn {
43
+ display: none;
44
+ appearance: none;
45
+ background: none;
46
+ border: 1px solid var(--sl-color-border, #e8e8e8);
47
+ border-radius: var(--sl-border-radius, 0.375rem);
48
+ width: 2.25rem;
49
+ height: 2.25rem;
50
+ align-items: center;
51
+ justify-content: center;
52
+ cursor: pointer;
53
+ color: var(--sl-color-text, #23262f);
54
+ transition: background-color 0.15s;
55
+ flex-shrink: 0;
56
+ padding: 0;
57
+ }
58
+
59
+ .menu-btn:hover {
60
+ background-color: var(--sl-color-gray-2, #e8e8e8);
61
+ }
62
+
63
+ .menu-btn svg {
64
+ width: 1.1rem;
65
+ height: 1.1rem;
66
+ }
67
+
68
+ @media (max-width: 72rem) {
69
+ .menu-btn {
70
+ display: flex;
71
+ }
38
72
  }
39
73
 
40
74
  .site-title {
@@ -45,7 +79,9 @@ export class StarlightHeader extends LitElement {
45
79
  white-space: nowrap;
46
80
  }
47
81
 
48
- .site-title:hover { opacity: 0.85; }
82
+ .site-title:hover {
83
+ opacity: 0.85;
84
+ }
49
85
 
50
86
  nav {
51
87
  display: flex;
@@ -61,7 +97,9 @@ export class StarlightHeader extends LitElement {
61
97
  color: var(--sl-color-gray-5, #4b4b4b);
62
98
  text-decoration: none;
63
99
  border-radius: var(--sl-border-radius, 0.375rem);
64
- transition: color 0.15s, background-color 0.15s;
100
+ transition:
101
+ color 0.15s,
102
+ background-color 0.15s;
65
103
  }
66
104
 
67
105
  nav a:hover {
@@ -69,7 +107,7 @@ export class StarlightHeader extends LitElement {
69
107
  background-color: var(--sl-color-gray-2, #e8e8e8);
70
108
  }
71
109
 
72
- nav a[aria-current='page'] {
110
+ nav a[aria-current="page"] {
73
111
  color: var(--sl-color-accent, #7c3aed);
74
112
  background-color: var(--sl-color-accent-low, #ede9fe);
75
113
  }
@@ -97,53 +135,111 @@ export class StarlightHeader extends LitElement {
97
135
  }
98
136
  `;
99
137
 
100
- siteTitle = '';
138
+ siteTitle = "";
101
139
  nav: NavItem[] = [];
102
- currentPath = '';
140
+ currentPath = "";
141
+ navOpen = false;
142
+ hasSidebar = false;
103
143
 
104
- _theme = 'light';
144
+ _theme = "light";
105
145
 
106
146
  override firstUpdated() {
107
- const stored = (typeof localStorage !== 'undefined'
108
- ? localStorage.getItem('sl-theme')
109
- : null) ?? 'light';
147
+ const stored =
148
+ (typeof localStorage !== "undefined"
149
+ ? localStorage.getItem("sl-theme")
150
+ : null) ?? "light";
110
151
  this._theme = stored;
111
- if (typeof document !== 'undefined') {
112
- document.documentElement.setAttribute('data-theme', stored);
152
+ if (typeof document !== "undefined") {
153
+ document.documentElement.setAttribute("data-theme", stored);
113
154
  }
114
155
  }
115
156
 
116
157
  private _toggleTheme() {
117
- const next = this._theme === 'light' ? 'dark' : 'light';
158
+ const next = this._theme === "light" ? "dark" : "light";
118
159
  this._theme = next;
119
- if (typeof localStorage !== 'undefined') {
120
- localStorage.setItem('sl-theme', next);
160
+ if (typeof localStorage !== "undefined") {
161
+ localStorage.setItem("sl-theme", next);
121
162
  }
122
- if (typeof document !== 'undefined') {
123
- document.documentElement.setAttribute('data-theme', next);
163
+ if (typeof document !== "undefined") {
164
+ document.documentElement.setAttribute("data-theme", next);
124
165
  }
125
166
  }
126
167
 
168
+ private _toggleNav() {
169
+ this.dispatchEvent(
170
+ new CustomEvent("sl-nav-toggle", { bubbles: true, composed: true }),
171
+ );
172
+ }
173
+
127
174
  override render() {
128
- const icon = this._theme === 'dark' ? '☀️' : '🌙';
129
- const label = this._theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode';
175
+ const icon = this._theme === "dark" ? "☀️" : "🌙";
176
+ const label =
177
+ this._theme === "dark" ? "Switch to light mode" : "Switch to dark mode";
130
178
 
131
179
  return html`
132
180
  <header>
181
+ ${this.hasSidebar
182
+ ? html`
183
+ <button
184
+ class="menu-btn"
185
+ aria-label="${this.navOpen
186
+ ? "Close navigation"
187
+ : "Open navigation"}"
188
+ aria-expanded="${this.navOpen}"
189
+ @click="${this._toggleNav}"
190
+ >
191
+ ${this.navOpen
192
+ ? html`
193
+ <svg
194
+ viewBox="0 0 24 24"
195
+ fill="none"
196
+ stroke="currentColor"
197
+ stroke-width="2"
198
+ stroke-linecap="round"
199
+ aria-hidden="true"
200
+ >
201
+ <line x1="18" y1="6" x2="6" y2="18" />
202
+ <line x1="6" y1="6" x2="18" y2="18" />
203
+ </svg>
204
+ `
205
+ : html`
206
+ <svg
207
+ viewBox="0 0 24 24"
208
+ fill="none"
209
+ stroke="currentColor"
210
+ stroke-width="2"
211
+ stroke-linecap="round"
212
+ aria-hidden="true"
213
+ >
214
+ <line x1="3" y1="6" x2="21" y2="6" />
215
+ <line x1="3" y1="12" x2="21" y2="12" />
216
+ <line x1="3" y1="18" x2="21" y2="18" />
217
+ </svg>
218
+ `}
219
+ </button>
220
+ `
221
+ : ""}
133
222
  <a class="site-title" href="/">${this.siteTitle}</a>
134
223
  <nav aria-label="Main navigation">
135
- ${this.nav.map(item => html`
136
- <a
137
- href="${item.href}"
138
- aria-current="${this.currentPath.startsWith(item.href) ? 'page' : 'false'}"
139
- >${item.label}</a>
140
- `)}
224
+ ${this.nav.map(
225
+ (item) => html`
226
+ <a
227
+ href="${item.href}"
228
+ aria-current="${this.currentPath.startsWith(item.href)
229
+ ? "page"
230
+ : "false"}"
231
+ >${item.label}</a
232
+ >
233
+ `,
234
+ )}
141
235
  </nav>
142
236
  <button
143
237
  class="theme-toggle"
144
238
  aria-label="${label}"
145
239
  @click="${this._toggleTheme}"
146
- >${icon}</button>
240
+ >
241
+ ${icon}
242
+ </button>
147
243
  </header>
148
244
  `;
149
245
  }