add theme
Some checks failed
Deploy / deploy (build, debian, 22) (push) Has been cancelled
Deploy / deploy (build:staging, school, 8822) (push) Has been cancelled

This commit is contained in:
2026-04-16 01:39:30 -06:00
parent 879f308871
commit 3fb59b9c89
7 changed files with 1059 additions and 525 deletions

34
CLAUDE.md Normal file
View File

@@ -0,0 +1,34 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Commands
```bash
npm start # Dev server (production mode)
npm run build # Production build
npm run build:staging # Staging build
npm run fmt # Format with Prettier (*.js, style.css, index.html)
```
No test suite exists. Prettier is the only code quality tool.
## Architecture
This is a static IoT learning portal — a single-page app built with vanilla JS and Vite. No framework.
**Module responsibilities:**
- `main.js` — entry point, calls `initApp()`
- `app.js` — application lifecycle: reads localStorage, wires event listeners, orchestrates everything
- `data.js` — registry of external learning platform links (OJ, code runners, books, etc.)
- `render.js` — generates HTML for site cards and pins from `data.js` entries
- `i18n.js` — translation system with a `t()` function; supports 11 language variants (Chinese variants, EN, JA, KO, plus joke languages: wenyan, mars, garbled, binary, meow, emoji)
- `theme.js` — manages 4 design themes (Fluent, Material You, Terminal, Cyberpunk) and dark/light toggle
**Data flow:** `app.js` → reads prefs from `localStorage` → calls `render.js` to build the card grid from `data.js` → applies theme via `theme.js` → uses `i18n.js` for all UI strings.
**State persistence:** language, design theme, and dark/light preference are all stored in `localStorage`.
**Site URLs** are injected at build time via `import.meta.env.VITE_*` environment variables.
**Themes:** Terminal and Cyberpunk force dark mode; the light/dark toggle is disabled for those themes.

11
i18n.js
View File

@@ -139,66 +139,77 @@ export const DESIGN_THEME_LABELS = {
"material-you": "Material You",
terminal: "终端",
cyberpunk: "赛博朋克",
nord: "Nord",
},
"zh-Hant": {
fluent: "Fluent",
"material-you": "Material You",
terminal: "終端",
cyberpunk: "賽博龐克",
nord: "Nord",
},
en: {
fluent: "Fluent",
"material-you": "Material You",
terminal: "Terminal",
cyberpunk: "Cyberpunk",
nord: "Nord",
},
ja: {
fluent: "Fluent",
"material-you": "Material You",
terminal: "ターミナル",
cyberpunk: "サイバーパンク",
nord: "Nord",
},
ko: {
fluent: "Fluent",
"material-you": "Material You",
terminal: "터미널",
cyberpunk: "사이버펑크",
nord: "Nord",
},
wenyan: {
fluent: "流光",
"material-you": "物材",
terminal: "终端",
cyberpunk: "赛博",
nord: "清寒",
},
mars: {
fluent: "流↗光",
"material-you": "材↘質",
terminal: "終↗★端",
cyberpunk: "賽↘!博",
nord: "清↗寒★",
},
garbled: {
fluent: "◼è▦",
"material-you": "拷▤屯ä锟◽",
terminal: "¥¬▤▨¿¿",
cyberpunk: "◼çæ¥烫¥",
nord: "æ◽屯¿",
},
bin: {
fluent: "0101",
"material-you": "010101",
terminal: "01010101",
cyberpunk: "0101010101",
nord: "0101010",
},
meow: {
fluent: "喵喵",
"material-you": "喵喵喵",
terminal: "喵喵",
cyberpunk: "喵喵喵喵",
nord: "喵喵喵",
},
emoji: {
fluent: "💧",
"material-you": "🧱",
terminal: "⌨️",
cyberpunk: "⚡",
nord: "❄️",
},
}

View File

@@ -42,6 +42,9 @@
<li role="option" data-value="cyberpunk" aria-selected="false">
Cyberpunk
</li>
<li role="option" data-value="nord" aria-selected="false">
Nord
</li>
</ul>
</label>
<label class="design-theme language-switch">

1353
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,7 +10,7 @@
"fmt": "prettier --write *.js style.css index.html"
},
"devDependencies": {
"prettier": "^3.7.4",
"vite": "^7.3.0"
"prettier": "^3.8.2",
"vite": "^8.0.8"
}
}

175
style.css
View File

@@ -1405,3 +1405,178 @@ html[data-design-theme="material-you"] .beian a:hover {
transform: none !important;
}
}
/* ─── Nord (nordtheme.com) ──────────────────────────────── */
/* Default: Polar Night (dark) */
html[data-design-theme="nord"] {
color-scheme: dark;
--accent: #88c0d0;
--accent-rgb: 136, 192, 208;
--accent-2: #81a1c1;
--accent-3: #5e81ac;
--accent-secondary-rgb: 129, 161, 193;
--page-gradient: #2e3440;
--page-texture: none;
--title-gradient: linear-gradient(135deg, #88c0d0 0%, #81a1c1 50%, #5e81ac 100%);
--control-bg: rgba(59, 66, 82, 0.85);
--control-border: rgba(76, 86, 106, 0.6);
--control-inset: rgba(67, 76, 94, 0.5);
--control-fg: #eceff4;
}
/* Re-assert Nord dark variables after generic dark mode overrides them */
html[data-theme="dark"][data-design-theme="nord"] {
color-scheme: dark;
--accent: #88c0d0;
--accent-rgb: 136, 192, 208;
--accent-2: #81a1c1;
--accent-3: #5e81ac;
--accent-secondary-rgb: 129, 161, 193;
--page-gradient: #2e3440;
--page-texture: none;
--title-gradient: linear-gradient(135deg, #88c0d0 0%, #81a1c1 50%, #5e81ac 100%);
--control-bg: rgba(59, 66, 82, 0.85);
--control-border: rgba(76, 86, 106, 0.6);
--control-inset: rgba(67, 76, 94, 0.5);
--control-fg: #eceff4;
}
html[data-theme="dark"][data-design-theme="nord"] body {
color: #eceff4;
}
/* Snow Storm (light mode) */
html[data-theme="light"][data-design-theme="nord"] {
color-scheme: light;
--accent: #5e81ac;
--accent-rgb: 94, 129, 172;
--accent-2: #81a1c1;
--accent-3: #88c0d0;
--accent-secondary-rgb: 94, 129, 172;
--page-gradient: #eceff4;
--page-texture: none;
--title-gradient: linear-gradient(135deg, #5e81ac 0%, #81a1c1 50%, #88c0d0 100%);
--control-bg: rgba(229, 233, 240, 0.85);
--control-border: rgba(216, 222, 233, 0.8);
--control-inset: rgba(236, 239, 244, 0.9);
--control-fg: #2e3440;
}
html[data-theme="light"][data-design-theme="nord"] body {
color: #2e3440;
}
/* Dark cards (Polar Night) */
html[data-theme="dark"][data-design-theme="nord"] .card {
background: #3b4252;
backdrop-filter: none;
-webkit-backdrop-filter: none;
border: 1px solid #4c566a;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
html[data-theme="dark"][data-design-theme="nord"] .card::before {
display: none;
}
html[data-theme="dark"][data-design-theme="nord"] .card h2 {
color: #eceff4;
}
html[data-theme="dark"][data-design-theme="nord"] .card:hover h2,
html[data-theme="dark"][data-design-theme="nord"] .card:focus h2 {
color: #88c0d0;
}
html[data-theme="dark"][data-design-theme="nord"] .card p {
color: #d8dee9;
}
html[data-theme="dark"][data-design-theme="nord"] .card:hover p,
html[data-theme="dark"][data-design-theme="nord"] .card:focus p {
color: #e5e9f0;
}
html[data-theme="dark"][data-design-theme="nord"] .card:hover,
html[data-theme="dark"][data-design-theme="nord"] .card:focus {
border-color: #88c0d0;
box-shadow: 0 4px 16px rgba(136, 192, 208, 0.2);
}
html[data-theme="dark"][data-design-theme="nord"] .card.pin {
background: #434c5e;
backdrop-filter: none;
-webkit-backdrop-filter: none;
}
/* Light cards (Snow Storm) */
html[data-theme="light"][data-design-theme="nord"] .card {
background: #e5e9f0;
backdrop-filter: none;
-webkit-backdrop-filter: none;
border: 1px solid #d8dee9;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(46, 52, 64, 0.1);
}
html[data-theme="light"][data-design-theme="nord"] .card::before {
display: none;
}
html[data-theme="light"][data-design-theme="nord"] .card h2 {
color: #2e3440;
}
html[data-theme="light"][data-design-theme="nord"] .card:hover h2,
html[data-theme="light"][data-design-theme="nord"] .card:focus h2 {
color: #5e81ac;
}
html[data-theme="light"][data-design-theme="nord"] .card p {
color: #4c566a;
}
html[data-theme="light"][data-design-theme="nord"] .card:hover p,
html[data-theme="light"][data-design-theme="nord"] .card:focus p {
color: #3b4252;
}
html[data-theme="light"][data-design-theme="nord"] .card:hover,
html[data-theme="light"][data-design-theme="nord"] .card:focus {
border-color: #5e81ac;
box-shadow: 0 4px 16px rgba(94, 129, 172, 0.2);
}
html[data-theme="light"][data-design-theme="nord"] .card.pin {
background: #eceff4;
backdrop-filter: none;
-webkit-backdrop-filter: none;
border-color: #d8dee9;
}
/* Beian links */
html[data-theme="dark"][data-design-theme="nord"] .beian a {
color: #d8dee9;
}
html[data-theme="dark"][data-design-theme="nord"] .beian a:hover {
color: #88c0d0;
}
html[data-theme="light"][data-design-theme="nord"] .beian a {
color: #4c566a;
}
html[data-theme="light"][data-design-theme="nord"] .beian a:hover {
color: #5e81ac;
}

View File

@@ -1,4 +1,4 @@
const DESIGN_THEMES = ["fluent", "material-you", "terminal", "cyberpunk"]
const DESIGN_THEMES = ["fluent", "material-you", "terminal", "cyberpunk", "nord"]
const FORCED_DARK_DESIGN_THEMES = new Set(["terminal", "cyberpunk"])
const THEME_BEFORE_FORCED_KEY = "themeBeforeForcedDark"