Add light/dark theme toggle to docs UI

Introduced a theme toggle button in the docs header, allowing users to switch between light and dark modes. Theme preference is saved in localStorage and applied on load. Updated CSS to support theme variables and improved code block styling for both themes.
This commit is contained in:
Thomas Ricouard 2025-12-30 20:39:11 +01:00
parent 4a708c65f3
commit 2b99f8520f
3 changed files with 128 additions and 10 deletions

View file

@ -86,6 +86,7 @@ const skills = [
const repoInfo = getRepoInfo();
const githubLink = document.getElementById("githubLink");
const themeToggle = document.getElementById("themeToggle");
const skillsList = document.getElementById("skillsList");
const skillTitle = document.getElementById("skillTitle");
const skillDescription = document.getElementById("skillDescription");
@ -99,6 +100,7 @@ githubLink.href = repoInfo
renderSkillList();
selectSkill(skills[0]);
initTheme();
function renderSkillList() {
skillsList.innerHTML = "";
@ -319,3 +321,23 @@ function getRepoInfo() {
return { owner, repo };
}
function initTheme() {
const saved = localStorage.getItem("codex-theme");
if (saved) {
setTheme(saved);
} else {
const prefersLight = window.matchMedia("(prefers-color-scheme: light)").matches;
setTheme(prefersLight ? "light" : "dark");
}
themeToggle.addEventListener("click", () => {
const next = document.documentElement.getAttribute("data-theme") === "dark" ? "light" : "dark";
setTheme(next);
localStorage.setItem("codex-theme", next);
});
}
function setTheme(theme) {
document.documentElement.setAttribute("data-theme", theme);
}

View file

@ -16,13 +16,31 @@
Built to help you review, debug, and ship faster.
</p>
</div>
<a class="github-link" href="#" id="githubLink" aria-label="View on GitHub">
<svg aria-hidden="true" viewBox="0 0 16 16" role="img">
<path
d="M8 0C3.58 0 0 3.58 0 8a8 8 0 0 0 5.47 7.59c.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2 .37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8 8 0 0 0 16 8c0-4.42-3.58-8-8-8Z"
/>
</svg>
</a>
<div class="topbar__actions">
<button class="theme-toggle" id="themeToggle" type="button" aria-label="Toggle theme">
<svg class="theme-toggle__icon theme-toggle__icon--sun" viewBox="0 0 24 24" aria-hidden="true">
<circle cx="12" cy="12" r="4"></circle>
<path
d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"
stroke-linecap="round"
></path>
</svg>
<svg class="theme-toggle__icon theme-toggle__icon--moon" viewBox="0 0 24 24" aria-hidden="true">
<path
d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79"
stroke-linecap="round"
stroke-linejoin="round"
></path>
</svg>
</button>
<a class="github-link" href="#" id="githubLink" aria-label="View on GitHub">
<svg aria-hidden="true" viewBox="0 0 16 16" role="img">
<path
d="M8 0C3.58 0 0 3.58 0 8a8 8 0 0 0 5.47 7.59c.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2 .37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8 8 0 0 0 16 8c0-4.42-3.58-8-8-8Z"
/>
</svg>
</a>
</div>
</div>
</header>

View file

@ -16,7 +16,7 @@
}
@media (prefers-color-scheme: light) {
:root {
:root:not([data-theme]) {
--bg: #f7f4f0;
--bg-2: #efe8df;
--panel: #ffffff;
@ -26,6 +26,8 @@
--accent: #1f7ae0;
--accent-2: #f08123;
--shadow: rgba(0, 0, 0, 0.1);
--code-bg: #f0ece6;
--code-text: #1c1c22;
}
}
@ -55,7 +57,7 @@ h3 {
position: relative;
padding: 1.2rem 1.5rem 0.9rem;
border-bottom: 1px solid var(--panel-border);
background: rgba(15, 17, 21, 0.85);
background: var(--topbar-bg, rgba(15, 17, 21, 0.85));
backdrop-filter: blur(8px);
}
@ -69,6 +71,51 @@ h3 {
gap: 1.5rem;
}
.topbar__actions {
display: flex;
align-items: center;
gap: 0.65rem;
}
.theme-toggle {
width: 44px;
height: 44px;
border-radius: 8px;
border: 1px solid var(--panel-border);
background: rgba(255, 255, 255, 0.03);
color: var(--text);
display: grid;
place-items: center;
cursor: pointer;
transition: transform 0.2s ease, border 0.2s ease, background 0.2s ease;
position: relative;
}
.theme-toggle:hover {
transform: translateY(-2px);
border-color: var(--accent);
background: rgba(100, 210, 255, 0.12);
}
.theme-toggle__icon {
width: 20px;
height: 20px;
fill: none;
stroke: currentColor;
stroke-width: 1.6;
position: absolute;
inset: 0;
margin: auto;
opacity: 0;
transition: opacity 0.2s ease;
}
[data-theme="dark"] .theme-toggle__icon--moon,
[data-theme="light"] .theme-toggle__icon--sun {
opacity: 1;
}
.eyebrow {
font-family: var(--font-display);
text-transform: uppercase;
@ -108,6 +155,36 @@ h3 {
background: rgba(100, 210, 255, 0.15);
}
[data-theme="light"] {
--bg: #f7f4f0;
--bg-2: #efe8df;
--panel: #ffffff;
--panel-border: #e0d7cc;
--text: #1c1c22;
--muted: #5a5e66;
--accent: #1f7ae0;
--accent-2: #f08123;
--shadow: rgba(0, 0, 0, 0.1);
--code-bg: #f0ece6;
--code-text: #1c1c22;
--topbar-bg: #e9e4de;
}
[data-theme="dark"] {
--bg: #0f1115;
--bg-2: #171a21;
--panel: #1d222c;
--panel-border: #2b3342;
--text: #f2f5f9;
--muted: #b5c0d4;
--accent: #64d2ff;
--accent-2: #ffb347;
--shadow: rgba(5, 8, 12, 0.2);
--code-bg: #0b0d12;
--code-text: #f2f5f9;
--topbar-bg: rgba(15, 17, 21, 0.9);
}
.page {
max-width: 100%;
margin: 0;
@ -278,7 +355,7 @@ h3 {
}
.markdown-body pre {
background: #0b0d12;
background: var(--code-bg, #0b0d12);
padding: 1rem;
border-radius: 0;
overflow-x: auto;
@ -290,6 +367,7 @@ h3 {
font-family: "SF Mono", "Fira Code", ui-monospace, monospace;
white-space: pre-wrap;
word-break: break-word;
color: var(--code-text, var(--text));
}
.markdown-body pre code {