File size: 10,381 Bytes
0af560f f2c15d5 60aea95 9f7c465 e55fad0 0af560f e55fad0 ed114b6 e55fad0 dfe72b9 e55fad0 dfe72b9 e55fad0 c93890d e55fad0 ed114b6 e55fad0 60aea95 f2c15d5 9f7c465 f2c15d5 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 |
// import { plotClusters } from './clusters'
import { init_memory_plot } from './memory'
import { loadFragments } from './fragmentLoader'
import { syncHFSpacesURLHash } from './syncHFSpacesURLHash'
// Dark mode is now handled manually via a CSS class on <html> and injected styles
document.addEventListener("DOMContentLoaded", () => {
console.log("DOMContentLoaded");
// Inject minimal styles for the theme toggle button
const styleEl = document.createElement('style');
styleEl.textContent = `
.theme-toggle-btn{position:absolute;top:16px;left:16px;z-index:10000;display:inline-flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:999px;background:rgba(255,255,255,0.9);backdrop-filter:saturate(150%) blur(6px);cursor:pointer;border:1px solid transparent;outline:none;box-shadow:none;-webkit-appearance:none;appearance:none;-webkit-tap-highlight-color:transparent}
.theme-toggle-btn:hover{border-color:transparent;box-shadow:none}
.theme-toggle-btn:focus,.theme-toggle-btn:focus-visible{outline:none;border-color:transparent;box-shadow:none}
.theme-toggle-btn img{width:22px;height:22px;transition:filter .15s ease}
.theme-toggle-btn.dark img{filter: brightness(0) invert(1)}
@media (prefers-color-scheme: dark){.theme-toggle-btn{background:rgba(30,30,30,0.85);border-color:transparent;box-shadow:none}}
`;
document.head.appendChild(styleEl);
// Inject dark mode CSS (scoped via html.dark)
const darkCSS = `
html.dark{color-scheme:dark}
html.dark body{background:#242525;color:#e5e7eb}
html.dark a{color:#93c5fd}
html.dark .figure-legend{color:#9ca3af}
html.dark d-article, html.dark d-article p, html.dark d-article aside{color:white !important;}
html.dark d-contents{background:#242525}
html.dark d-contents nav a{color:#cbd5e1}
html.dark d-contents nav a:hover{text-decoration:underline solid rgba(255,255,255,0.6)}
html.dark .note-box{background:#111;border-left-color:#888}
html.dark .note-box-title{color:#d1d5db}
html.dark .note-box-content{color:#e5e7eb}
html.dark .large-image-background{background:#242525}
html.dark .boxed-image{background:#111;border-color:#262626;box-shadow:0 4px 6px rgba(0,0,0,.6)}
html.dark #graph-all,html.dark #controls,html.dark .memory-block,html.dark .activation-memory,html.dark .gradient-memory{background:#111;border-color:#262626;box-shadow:0 4px 6px rgba(0,0,0,.6);color:#e5e7eb}
html.dark label,html.dark .memory-title{color:#e5e7eb}
html.dark .memory-value{color:#93c5fd}
html.dark input,html.dark select,html.dark textarea{background:#0f0f0f;color:#e5e7eb;border:1px solid #333}
html.dark input:hover,html.dark select:hover,html.dark textarea:hover{border-color:#60a5fa}
html.dark input:focus,html.dark select:focus,html.dark textarea:focus{border-color:#3b82f6;box-shadow:0 0 0 2px rgba(59,130,246,0.35)}
html.dark input[type=range]{background:#333}
html.dark input[type=range]::-webkit-slider-thumb{background:#3b82f6}
html.dark .plotly_caption{color:#9ca3af}
html.dark .theme-toggle-btn{background:rgba(30,30,30,0.85);border-color:transparent}
html.dark d-article img{background:white}
html.dark summary {color:black !important;}
html.dark .katex-container {color:white !important;}
html.dark d-code {background: white!important;}
html.dark .code-block div { background: white!important;}
html.dark .code-block div p { color: black!important;}
/* Table borders in dark mode */
html.dark table{border-color:rgba(255,255,255,0.3)}
html.dark th,html.dark td{border-color:rgba(255,255,255,0.3)}
html.dark thead tr,html.dark tbody tr{border-color:rgba(255,255,255,0.3)}
html.dark d-byline, html.dark d-article{border-top: 1px solid rgba(255, 255, 255, 0.5);}
html.dark d-byline h3{color:white;}
html.dark d-math *, html.dark span.katex{color:white !important;}
html.dark d-appendix { color: white}
html.dark h2 { border-bottom: 1px solid rgba(255, 255, 255, 0.25);}
html.dark h1, html.dark h2, html.dark h3, html.dark h4, html.dark h5, html.dark h6 { color: white}
html.dark .code-area { color: black;}
html.dark .code-area a { color: black!important;}
`;
const darkStyleEl = document.createElement('style');
darkStyleEl.id = 'darkmode-css';
darkStyleEl.textContent = darkCSS;
document.head.appendChild(darkStyleEl);
// Inject equivalent dark CSS into all ShadowRoots using :host-context(.dark)
// This ensures styles also apply inside web components with Shadow DOM
const shadowDarkCSS = darkCSS.replace(/html\.dark/g, ':host-context(.dark)');
const injectDarkStylesIntoRoot = (root) => {
// Only target ShadowRoots here
if (!root || !(root instanceof ShadowRoot)) return;
if (root.querySelector('style#darkmode-css-shadow')) return;
const style = document.createElement('style');
style.id = 'darkmode-css-shadow';
style.textContent = shadowDarkCSS;
root.appendChild(style);
};
// Normalize inline SVGs: ensure viewBox and preserveAspectRatio for responsiveness
const normalizeSvgElement = (svgEl) => {
try {
if (!svgEl || svgEl.hasAttribute('viewBox')) return;
const widthAttr = svgEl.getAttribute('width');
const heightAttr = svgEl.getAttribute('height');
if (!widthAttr || !heightAttr) return;
const width = parseFloat(widthAttr);
const height = parseFloat(heightAttr);
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return;
svgEl.setAttribute('viewBox', `0 0 ${width} ${height}`);
if (!svgEl.hasAttribute('preserveAspectRatio')) {
svgEl.setAttribute('preserveAspectRatio', 'xMidYMid meet');
}
} catch (_) {
// no-op
}
};
const processRootForSVGs = (root) => {
if (!root || typeof root.querySelectorAll !== 'function') return;
const svgs = root.querySelectorAll('svg:not([viewBox])');
svgs.forEach((svg) => normalizeSvgElement(svg));
};
const scanNodeForShadowRoots = (node) => {
if (!node) return;
if (node.shadowRoot) {
injectDarkStylesIntoRoot(node.shadowRoot);
processRootForSVGs(node.shadowRoot);
}
// Traverse children
if (node.childNodes && node.childNodes.length) {
node.childNodes.forEach((child) => {
// Process SVGs in this subtree as well
processRootForSVGs(child);
scanNodeForShadowRoots(child);
});
}
};
// Intercept future shadow roots
const originalAttachShadow = Element.prototype.attachShadow;
Element.prototype.attachShadow = function(init) {
const shadow = originalAttachShadow.call(this, init);
try {
injectDarkStylesIntoRoot(shadow);
processRootForSVGs(shadow);
} catch (e) {}
return shadow;
};
// Initial sweep for any existing shadow roots
scanNodeForShadowRoots(document.documentElement);
// Initial pass for regular DOM SVGs
processRootForSVGs(document);
// Observe DOM mutations to catch dynamically added components
const mo = new MutationObserver((mutations) => {
for (const m of mutations) {
m.addedNodes && m.addedNodes.forEach((n) => {
scanNodeForShadowRoots(n);
processRootForSVGs(n);
});
}
});
mo.observe(document.documentElement, { childList: true, subtree: true });
// Create the toggle button
const btn = document.createElement('button');
btn.className = 'theme-toggle-btn';
btn.setAttribute('type', 'button');
btn.setAttribute('aria-label', 'Basculer le mode sombre');
// Reuse icons declared in HTML and move them into the button
const sunIcon = document.getElementById('sunIcon');
const moonIcon = document.getElementById('moonIcon');
if (sunIcon && moonIcon) {
// Make sure they adopt button sizing
sunIcon.style.display = 'none';
sunIcon.style.width = '22px';
sunIcon.style.height = '22px';
moonIcon.style.display = 'none';
moonIcon.style.width = '22px';
moonIcon.style.height = '22px';
btn.appendChild(sunIcon);
btn.appendChild(moonIcon);
}
document.body.appendChild(btn);
const setIcon = (enabled) => {
// enabled = dark mode enabled -> show sun (to indicate turning off), hide moon
sunIcon.style.display = enabled ? '' : 'none';
moonIcon.style.display = enabled ? 'none' : '';
btn.setAttribute('title', enabled ? 'Désactiver le mode sombre' : 'Activer le mode sombre');
btn.setAttribute('aria-pressed', String(enabled));
btn.classList.toggle('dark', enabled);
};
const setDark = (enabled) => {
document.documentElement.classList.toggle('dark', enabled);
setIcon(enabled);
};
const THEME_KEY = 'theme';
let savedTheme = null;
try {
savedTheme = localStorage.getItem(THEME_KEY);
} catch (e) {}
const media = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)');
const prefersDark = media ? media.matches : false;
// Initialisation: priorité à la préférence sauvegardée, sinon préférence système
if (savedTheme === 'dark') {
setDark(true);
} else if (savedTheme === 'light') {
setDark(false);
} else {
setDark(prefersDark);
}
// Si l'utilisateur a déjà choisi manuellement, on ne suit plus la préférence système
let manualOverride = savedTheme === 'dark' || savedTheme === 'light';
// React to system preference changes dynamically (no persistence)
if (media && typeof media.addEventListener === 'function') {
media.addEventListener('change', (e) => {
if (!manualOverride) {
setDark(e.matches);
}
});
} else if (media && typeof media.addListener === 'function') {
// Fallback for older browsers
media.addListener((e) => {
if (!manualOverride) {
setDark(e.matches);
}
});
}
// Toggle handler — for réduire les glitches, attendre le next frame avant d'ajuster l'icône
btn.addEventListener('click', () => {
manualOverride = true;
const next = !document.documentElement.classList.contains('dark');
setDark(next);
try {
localStorage.setItem(THEME_KEY, next ? 'dark' : 'light');
} catch (e) {}
});
loadFragments();
init_memory_plot();
syncHFSpacesURLHash();
}, { once: true });
|