Skip to content

Commit e80f2e8

Browse files
committed
tmp
1 parent a09c359 commit e80f2e8

16 files changed

+3489
-0
lines changed
Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
2+
3+
<!-- Early inline script to avoid flash: set the attribute before styles run (default: basic/off) -->
4+
<script is:inline>
5+
try {
6+
const stored = localStorage.getItem('advanced');
7+
const value = (stored === 'true' || stored === 'false') ? stored : 'false';
8+
document.documentElement.dataset.advanced = value;
9+
} catch {}
10+
// Default is "false" (basic mode) when not set
11+
</script>
12+
13+
<!-- Hide the toggle until the state is applied to avoid visual flash -->
14+
<div id="advanced-toggle-wrapper" hidden aria-hidden="true">
15+
<input
16+
data-advanced-input
17+
type="checkbox"
18+
class="advanced-toggle"
19+
id="advanced-switch"
20+
role="switch"
21+
aria-checked="false"
22+
aria-label="Toggle advanced content"
23+
/>
24+
<label for="advanced-switch" class="sr-only">Toggle advanced content</label>
25+
</div>
26+
27+
<script is:inline>
28+
const __initAdvancedToggle = () => {
29+
const input = document.querySelector('[data-advanced-input]');
30+
const wrapper = document.getElementById('advanced-toggle-wrapper');
31+
// Initialize from dataset set above (default false)
32+
const isOn = (document.documentElement.dataset.advanced || 'false') !== 'false';
33+
34+
// Build a registry of advanced blocks and their removable wrapper targets
35+
/** @type {{ el: Element, target: Element, placeholder: Comment }[]} */
36+
const registry = [];
37+
const prepared = new WeakSet();
38+
const isWhitespaceOnly = (node) => (node?.textContent || '').trim() === '';
39+
const getWrapperCandidate = (el) => {
40+
const p = el.parentElement;
41+
if (!p) return null;
42+
const tag = p.tagName;
43+
// Collapse common markdown wrappers if they only contain Advanced
44+
if (tag !== 'P' && tag !== 'DIV') return null;
45+
const onlyChild = p.childElementCount === 1 && p.firstElementChild === el;
46+
if (!onlyChild) return null;
47+
if (!isWhitespaceOnly(p)) return null;
48+
return p;
49+
};
50+
const prepareRegistry = () => {
51+
const nodes = document.querySelectorAll('.advanced-title');
52+
for (const el of nodes) {
53+
if (prepared.has(el)) continue;
54+
const wrap = getWrapperCandidate(el);
55+
const target = wrap || el;
56+
const placeholder = document.createComment('advanced:placeholder');
57+
prepared.add(el);
58+
registry.push({ el, target, placeholder });
59+
}
60+
};
61+
62+
// Keep tooltip text in sync with state; uses both native title and data-tooltip
63+
const setTooltip = (on) => {
64+
if (!input) return;
65+
const tip = on ? 'Showing advanced content' : 'Hiding advanced content';
66+
input.setAttribute('title', tip);
67+
input.setAttribute('data-tooltip', tip);
68+
// Clarify SR label to indicate action on toggle
69+
input.setAttribute('aria-label', on ? 'Hide advanced content' : 'Show advanced content');
70+
};
71+
72+
const applyVisibility = (on) => {
73+
// Advanced content blocks: remove or restore by swapping with placeholders
74+
prepareRegistry();
75+
if (on) {
76+
for (const { target, placeholder } of registry) {
77+
if (placeholder.parentNode) placeholder.replaceWith(target);
78+
}
79+
} else {
80+
for (const { target, placeholder } of registry) {
81+
if (target.parentNode) target.replaceWith(placeholder);
82+
}
83+
}
84+
// Advanced-only TOC items (supports Shadow DOM and mobile dropdown)
85+
const setLinks = (root) => {
86+
// Hide/show both anchors and <li> marked as advanced-only
87+
const links = root.querySelectorAll('li.advanced-only, .advanced-only');
88+
for (const el of links) {
89+
el.style.display = on ? '' : 'none';
90+
}
91+
};
92+
setLinks(document);
93+
const tocEls = document.querySelectorAll('starlight-toc');
94+
for (const toc of tocEls) {
95+
const root = toc.shadowRoot || toc;
96+
if (root) setLinks(root);
97+
}
98+
99+
// Keep the current (active) link in view in the mobile dropdown
100+
requestAnimationFrame(() => {
101+
const current = document.querySelector('ul.isMobile a[aria-current="true"]');
102+
current?.scrollIntoView({ block: 'nearest', inline: 'nearest' });
103+
});
104+
};
105+
106+
const setState = (on) => {
107+
const value = on ? 'true' : 'false';
108+
document.documentElement.dataset.advanced = value;
109+
if (input) {
110+
input.checked = on;
111+
input.setAttribute('aria-checked', String(on));
112+
}
113+
setTooltip(on);
114+
applyVisibility(on);
115+
};
116+
117+
// Mark advanced-only TOC links once (standard TOC + mobile dropdown lists)
118+
const tagAdvancedTocLinks = () => {
119+
try {
120+
const advancedHeadings = document.querySelectorAll(
121+
'.advanced-title h1, .advanced-title h2, .advanced-title h3, .advanced-title h4, .advanced-title h5, .advanced-title h6'
122+
);
123+
const ids = new Set(
124+
Array.from(advancedHeadings)
125+
.map(h => h.id && `#${h.id}`)
126+
.filter(Boolean)
127+
);
128+
// If there are no advanced headings with IDs, nothing to tag
129+
if (ids.size === 0) return;
130+
const tagRoot = (root) => {
131+
const tocLinks = root.querySelectorAll('nav ul li a');
132+
for (const a of tocLinks) {
133+
if (ids.has(a.hash)) {
134+
// Mark the whole list item and the anchor for robustness
135+
a.classList.add('advanced-only');
136+
a.closest('li')?.classList.add('advanced-only');
137+
}
138+
}
139+
};
140+
// Light DOM links (if any)
141+
const lightLinksRoot = document.querySelector('starlight-toc');
142+
if (lightLinksRoot) tagRoot(lightLinksRoot);
143+
// Shadow DOM links
144+
const tocEls = document.querySelectorAll('starlight-toc');
145+
for (const toc of tocEls) {
146+
const root = toc.shadowRoot;
147+
if (root) tagRoot(root);
148+
}
149+
150+
// Tag mobile dropdown lists matching ul.isMobile
151+
const mobileLists = document.querySelectorAll('ul.isMobile');
152+
for (const ul of mobileLists) {
153+
const anchors = ul.querySelectorAll('li > a[href^="#"]');
154+
for (const a of anchors) {
155+
const href = a.getAttribute('href') || '';
156+
if (ids.has(href)) {
157+
// Mark the LI so it collapses cleanly
158+
a.closest('li')?.classList.add('advanced-only');
159+
}
160+
}
161+
}
162+
} catch {}
163+
};
164+
tagAdvancedTocLinks();
165+
applyVisibility(isOn);
166+
// Observe TOC for changes (if hydrated or updated later)
167+
try {
168+
const tocEls = document.querySelectorAll('starlight-toc');
169+
if (tocEls.length && 'MutationObserver' in window) {
170+
const handler = () => {
171+
tagAdvancedTocLinks();
172+
applyVisibility((document.documentElement.dataset.advanced || 'true') !== 'false');
173+
};
174+
for (const toc of tocEls) {
175+
const mo1 = new MutationObserver(handler);
176+
mo1.observe(toc, { childList: true, subtree: true });
177+
if (toc.shadowRoot) {
178+
const mo2 = new MutationObserver(handler);
179+
mo2.observe(toc.shadowRoot, { childList: true, subtree: true });
180+
}
181+
}
182+
}
183+
} catch {}
184+
185+
setState(isOn);
186+
187+
// Reveal the toggle only after the correct state is applied
188+
if (wrapper) {
189+
wrapper.hidden = false;
190+
wrapper.removeAttribute('aria-hidden');
191+
wrapper.classList.add('fade-in');
192+
}
193+
194+
if (input) {
195+
let _reloading = false;
196+
const handler = () => {
197+
const next = input.checked;
198+
try { localStorage.setItem('advanced', next ? 'true' : 'false'); } catch {}
199+
if (next) { // turning ON advanced
200+
if (_reloading) return; // guard against duplicate events
201+
_reloading = true;
202+
// Reflect state for a11y just before navigation
203+
input.setAttribute('aria-checked', 'true');
204+
document.documentElement.dataset.advanced = 'true';
205+
// Full reload to allow late-hydrated components to render in advanced mode without manual refresh
206+
window.location.reload();
207+
} else {
208+
setState(next);
209+
}
210+
};
211+
// 'change' covers user interaction; avoid also listening to 'click' to prevent double firing
212+
input.addEventListener('change', handler);
213+
}
214+
};
215+
216+
if (document.readyState === 'loading') {
217+
document.addEventListener('DOMContentLoaded', __initAdvancedToggle, { once: true });
218+
} else {
219+
__initAdvancedToggle();
220+
}
221+
</script>
222+
223+
<style is:global lang="css">
224+
/* Accessible visually-hidden utility */
225+
.sr-only {
226+
position: absolute;
227+
width: 1px;
228+
height: 1px;
229+
padding: 0;
230+
margin: -1px;
231+
overflow: hidden;
232+
clip: rect(0, 0, 0, 0);
233+
white-space: nowrap;
234+
border: 0;
235+
}
236+
237+
/* Fade-in for the toggle wrapper once state is known */
238+
#advanced-toggle-wrapper { opacity: 0; }
239+
#advanced-toggle-wrapper.fade-in { animation: advToggleFade .2s ease-out forwards; }
240+
@keyframes advToggleFade { to { opacity: 1; } }
241+
242+
/* Global visibility rules driven by the data attribute */
243+
html:not([data-advanced]) .advanced-title { display: none !important; }
244+
html:not([data-advanced]) .advanced-only { display: none !important; }
245+
html[data-advanced="false"] .advanced-title { display: none !important; }
246+
html[data-advanced="false"] .advanced-only { display: none !important; }
247+
248+
/* Explicitly show when on (some themes may hide by default) */
249+
html:not([data-advanced="false"]) .advanced-title { display: revert !important; }
250+
html:not([data-advanced="false"]) .advanced-only { display: revert !important; }
251+
252+
.advanced-toggle {
253+
-webkit-appearance: none;
254+
-moz-appearance: none;
255+
appearance: none;
256+
-webkit-tap-highlight-color: transparent;
257+
cursor: pointer;
258+
height: 26px;
259+
width: 46px;
260+
border-radius: 16px;
261+
display: inline-block;
262+
position: relative;
263+
border: 2px solid #474755;
264+
background: linear-gradient(180deg, #2D2F39 0%, #1F2027 100%);
265+
border-color: #aa4bb3;
266+
}
267+
.advanced-toggle:focus { outline: 0; }
268+
.advanced-toggle:focus-visible {
269+
box-shadow: 0 0 0 3px rgba(132, 100, 198, .45);
270+
border-color: #8464C6;
271+
}
272+
.advanced-toggle::after {
273+
content: 'B';
274+
position: absolute;
275+
top: 2px;
276+
left: 2px;
277+
width: 18px;
278+
height: 18px;
279+
border-radius: 50%;
280+
background: #C7A06F;
281+
box-shadow: 0 1px 2px rgba(44,44,44,.2);
282+
transition: transform .15s ease, background-color .15s ease;
283+
display: grid;
284+
place-items: center;
285+
font-weight: 700;
286+
font-size: 12px;
287+
line-height: 1;
288+
color: #fff;
289+
user-select: none;
290+
}
291+
.advanced-toggle:checked { border-color: #54C59F; }
292+
.advanced-toggle:checked::after {
293+
content: 'A';
294+
background: #8464C6;
295+
transform: translateX(20px);
296+
}
297+
298+
/* Theme-aware knob letter color */
299+
/* Starlight sets data-theme on html; prefer that if present */
300+
html[data-theme="dark"] .advanced-toggle::after { color: #fff; }
301+
html[data-theme="dark"] .advanced-toggle:checked::after { color: #BDBDBD; }
302+
html[data-theme="light"] .advanced-toggle::after { color: #000000; }
303+
/* Ensure contrast on purple knob in light theme */
304+
html[data-theme="light"] .advanced-toggle:checked::after { color: #1f2027; }
305+
306+
/* Fallback to system preference when data-theme is not set */
307+
@media (prefers-color-scheme: dark) {
308+
:root:not([data-theme]) .advanced-toggle::after { color: #BDBDBD; }
309+
:root:not([data-theme]) .advanced-toggle:checked::after { color: #BDBDBD; }
310+
}
311+
@media (prefers-color-scheme: light) {
312+
:root:not([data-theme]) .advanced-toggle::after { color: #136454; }
313+
:root:not([data-theme]) .advanced-toggle:checked::after { color: #136454; }
314+
}
315+
</style>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
3+
---
4+
5+
<span class="Advanced-class advanced-title" style="display: contents;">
6+
<slot />
7+
</span>

0 commit comments

Comments
 (0)