Compare commits
8 Commits
main
...
346dc71d2e
| Author | SHA1 | Date | |
|---|---|---|---|
| 346dc71d2e | |||
| d9414a9781 | |||
| 5b03465fc7 | |||
| 7fd89bfaf2 | |||
| 8dbedff306 | |||
| 77085efaca | |||
| 0f453cba75 | |||
| 37253011b4 |
@@ -1,9 +1,8 @@
|
|||||||
VITE_OJ=https://oj.xuyue.cc
|
VITE_OJ=https://oj.xuyue.cc
|
||||||
VITE_CODE=https://code.xuyue.cc
|
VITE_CODE=https://code.xuyue.cc
|
||||||
VITE_WEB=https://web.xuyue.cc
|
VITE_WEB=https://web.xuyue.cc
|
||||||
VITE_SHUATI=http://47.115.221.142:5000
|
VITE_PLAY=https://play.xuyue.cc
|
||||||
VITE_BOOK=https://book.xuyue.cc
|
VITE_BOOK=https://book.xuyue.cc
|
||||||
VITE_HUABU=https://huabu.xuyue.cc
|
VITE_HUABU=https://huabu.xuyue.cc
|
||||||
VITE_PPT=https://ppt.xuyue.cc/py
|
VITE_PPT=https://ppt.xuyue.cc/py
|
||||||
VITE_PY=https://python.xuyue.cc
|
VITE_PY=https://python.xuyue.cc
|
||||||
VITE_BLOCKLY=https://lego.xuyue.cc
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
VITE_OJ=http://10.13.114.114:81
|
VITE_OJ=http://10.13.114.114:81
|
||||||
VITE_CODE=http://10.13.114.114:82
|
VITE_CODE=http://10.13.114.114:82
|
||||||
VITE_WEB=http://10.13.114.114:91
|
VITE_WEB=http://10.13.114.114:91
|
||||||
|
# VITE_PLAY=http://10.13.114.114:83
|
||||||
VITE_BOOK=http://10.13.114.114:84
|
VITE_BOOK=http://10.13.114.114:84
|
||||||
VITE_HUABU=http://10.13.114.114:85
|
VITE_HUABU=http://10.13.114.114:85
|
||||||
VITE_SHUATI=http://10.13.114.114:86
|
VITE_SHUATI=http://10.13.114.114:86
|
||||||
VITE_BLOCKLY=http://10.13.114.114:98
|
|
||||||
27
.github/workflows/deploy.yml
vendored
@@ -5,39 +5,24 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
build-and-deploy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- name: debian
|
|
||||||
build_command: build
|
|
||||||
remote_port: 22
|
|
||||||
- name: school
|
|
||||||
build_command: build:staging
|
|
||||||
remote_port: 8822
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 24
|
||||||
cache: npm
|
cache: 'npm'
|
||||||
- run: npm install
|
- run: npm install
|
||||||
- run: npm run ${{ matrix.build_command }}
|
- run: CI=false npm run build
|
||||||
env:
|
|
||||||
CI: false
|
|
||||||
|
|
||||||
- uses: easingthemes/ssh-deploy@main
|
- uses: easingthemes/ssh-deploy@main
|
||||||
with:
|
env:
|
||||||
SSH_PRIVATE_KEY: ${{ secrets.KEY }}
|
SSH_PRIVATE_KEY: ${{ secrets.KEY }}
|
||||||
REMOTE_HOST: ${{ secrets.HOST }}
|
REMOTE_HOST: ${{ secrets.HOST }}
|
||||||
REMOTE_PORT: ${{ matrix.remote_port }}
|
|
||||||
ARGS: "-avzr --delete"
|
ARGS: "-avzr --delete"
|
||||||
SOURCE: dist/
|
SOURCE: dist/
|
||||||
REMOTE_USER: root
|
REMOTE_USER: root
|
||||||
TARGET: /root/Home/build
|
TARGET: /root/Home/build
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
<h2>3 Concrete Approaches</h2>
|
|
||||||
<p class="subtitle">All share: Dracula bg (#282a36), dot matrix texture, rounded soft cards. They differ in accent color strategy.</p>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
/* Shared base */
|
|
||||||
.mockup-wrap {
|
|
||||||
font-family: ui-monospace, 'JetBrains Mono', 'Fira Code', monospace;
|
|
||||||
background: #282a36;
|
|
||||||
background-image: radial-gradient(circle, rgba(98,114,164,0.55) 1px, transparent 1px);
|
|
||||||
background-size: 20px 20px;
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 18px 16px 14px;
|
|
||||||
margin-top: 12px;
|
|
||||||
}
|
|
||||||
.mockup-title {
|
|
||||||
text-align: center;
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: 0.04em;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
.mockup-cards {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
.mc {
|
|
||||||
width: 120px;
|
|
||||||
padding: 10px 11px;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-size: 11px;
|
|
||||||
}
|
|
||||||
.mc h5 { margin: 0 0 4px; font-size: 11px; }
|
|
||||||
.mc p { margin: 0; font-size: 10px; opacity: 0.6; line-height: 1.4; }
|
|
||||||
|
|
||||||
/* ── Approach A: Purple Focus ── */
|
|
||||||
.a-title {
|
|
||||||
background: linear-gradient(90deg, #bd93f9 0%, #ff79c6 100%);
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
background-clip: text;
|
|
||||||
}
|
|
||||||
.a-title::after { content: " ▌"; -webkit-text-fill-color: #bd93f9; animation: blink 1s step-end infinite; }
|
|
||||||
@keyframes blink { 50% { opacity: 0; } }
|
|
||||||
.a-card {
|
|
||||||
background: #313244;
|
|
||||||
border: 1px solid #44475a;
|
|
||||||
box-shadow: 0 4px 14px rgba(0,0,0,0.4);
|
|
||||||
color: #f8f8f2;
|
|
||||||
}
|
|
||||||
.a-card h5 { color: #bd93f9; }
|
|
||||||
|
|
||||||
/* ── Approach B: Cyan Dev ── */
|
|
||||||
.b-title { color: #8be9fd; text-shadow: 0 0 12px rgba(139,233,253,0.5); }
|
|
||||||
.b-title::after { content: " ▌"; animation: blink 1s step-end infinite; }
|
|
||||||
.b-card {
|
|
||||||
background: #313244;
|
|
||||||
border: 1px solid #44475a;
|
|
||||||
border-left: 2px solid #8be9fd;
|
|
||||||
box-shadow: 0 4px 14px rgba(0,0,0,0.4);
|
|
||||||
color: #f8f8f2;
|
|
||||||
}
|
|
||||||
.b-card h5 { color: #8be9fd; }
|
|
||||||
|
|
||||||
/* ── Approach C: Multi-accent ── */
|
|
||||||
.c-title {
|
|
||||||
background: linear-gradient(90deg, #ff79c6 0%, #bd93f9 33%, #8be9fd 66%, #50fa7b 100%);
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
background-clip: text;
|
|
||||||
}
|
|
||||||
.c-card {
|
|
||||||
background: #313244;
|
|
||||||
border: 1px solid #44475a;
|
|
||||||
box-shadow: 0 4px 14px rgba(0,0,0,0.4);
|
|
||||||
color: #f8f8f2;
|
|
||||||
}
|
|
||||||
.c-card-1 { border-top: 2px solid #ff79c6; }
|
|
||||||
.c-card-1 h5 { color: #ff79c6; }
|
|
||||||
.c-card-2 { border-top: 2px solid #bd93f9; }
|
|
||||||
.c-card-2 h5 { color: #bd93f9; }
|
|
||||||
.c-card-3 { border-top: 2px solid #8be9fd; }
|
|
||||||
.c-card-3 h5 { color: #8be9fd; }
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="options">
|
|
||||||
<div class="option" data-choice="purple" onclick="toggleSelect(this)">
|
|
||||||
<div class="letter">A</div>
|
|
||||||
<div class="content">
|
|
||||||
<h3>Purple Focus <span style="font-size:11px;opacity:0.6;font-weight:normal">(recommended)</span></h3>
|
|
||||||
<p>#bd93f9 mauve as the single accent. Gradient title (purple→pink). Clean card hover with purple glow. Cohesive and refined.</p>
|
|
||||||
<div class="mockup-wrap">
|
|
||||||
<div class="mockup-title a-title">IoT Learning</div>
|
|
||||||
<div class="mockup-cards">
|
|
||||||
<div class="mc a-card"><h5>OJ Platform</h5><p>Online judge</p></div>
|
|
||||||
<div class="mc a-card"><h5>Code Runner</h5><p>Run snippets</p></div>
|
|
||||||
<div class="mc a-card"><h5>Textbooks</h5><p>Reference books</p></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="option" data-choice="cyan" onclick="toggleSelect(this)">
|
|
||||||
<div class="letter">B</div>
|
|
||||||
<div class="content">
|
|
||||||
<h3>Cyan Dev</h3>
|
|
||||||
<p>#8be9fd cyan as accent. Glowing title, left-border accent on cards. Closer to the old terminal vibe — just upgraded. More "code editor", less "app".</p>
|
|
||||||
<div class="mockup-wrap">
|
|
||||||
<div class="mockup-title b-title">IoT Learning</div>
|
|
||||||
<div class="mockup-cards">
|
|
||||||
<div class="mc b-card"><h5>OJ Platform</h5><p>Online judge</p></div>
|
|
||||||
<div class="mc b-card"><h5>Code Runner</h5><p>Run snippets</p></div>
|
|
||||||
<div class="mc b-card"><h5>Textbooks</h5><p>Reference books</p></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="option" data-choice="multi" onclick="toggleSelect(this)">
|
|
||||||
<div class="letter">C</div>
|
|
||||||
<div class="content">
|
|
||||||
<h3>Multi-Accent Rainbow</h3>
|
|
||||||
<p>Each card gets a different Dracula color top border (pink, purple, cyan…). Title uses all colors. Most colorful — looks like syntax highlighting applied to the UI.</p>
|
|
||||||
<div class="mockup-wrap">
|
|
||||||
<div class="mockup-title c-title">IoT Learning</div>
|
|
||||||
<div class="mockup-cards">
|
|
||||||
<div class="mc c-card c-card-1"><h5>OJ Platform</h5><p>Online judge</p></div>
|
|
||||||
<div class="mc c-card c-card-2"><h5>Code Runner</h5><p>Run snippets</p></div>
|
|
||||||
<div class="mc c-card c-card-3"><h5>Textbooks</h5><p>Reference books</p></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
<h2>Background Treatment</h2>
|
|
||||||
<p class="subtitle">The background sets the whole mood. All options use Dracula's base color (#282a36) as the foundation.</p>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.bg-preview {
|
|
||||||
width: 100%;
|
|
||||||
height: 110px;
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-top: 10px;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
.bg-label {
|
|
||||||
font-family: ui-monospace, monospace;
|
|
||||||
font-size: 11px;
|
|
||||||
color: rgba(248,248,242,0.5);
|
|
||||||
letter-spacing: 0.08em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* A: flat clean */
|
|
||||||
.bg-flat {
|
|
||||||
background: #282a36;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* B: radial aurora glow */
|
|
||||||
.bg-aurora {
|
|
||||||
background: #282a36;
|
|
||||||
background-image:
|
|
||||||
radial-gradient(ellipse at 20% 30%, rgba(189,147,249,0.18) 0%, transparent 55%),
|
|
||||||
radial-gradient(ellipse at 80% 70%, rgba(255,121,198,0.12) 0%, transparent 50%),
|
|
||||||
radial-gradient(ellipse at 55% 90%, rgba(139,233,253,0.08) 0%, transparent 45%);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* C: subtle grid */
|
|
||||||
.bg-grid {
|
|
||||||
background: #282a36;
|
|
||||||
background-image:
|
|
||||||
linear-gradient(rgba(68,71,90,0.4) 1px, transparent 1px),
|
|
||||||
linear-gradient(90deg, rgba(68,71,90,0.4) 1px, transparent 1px);
|
|
||||||
background-size: 24px 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* D: dot matrix */
|
|
||||||
.bg-dots {
|
|
||||||
background: #282a36;
|
|
||||||
background-image: radial-gradient(circle, rgba(98,114,164,0.5) 1px, transparent 1px);
|
|
||||||
background-size: 20px 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="options">
|
|
||||||
<div class="option" data-choice="flat" onclick="toggleSelect(this)">
|
|
||||||
<div class="letter">A</div>
|
|
||||||
<div class="content">
|
|
||||||
<h3>Flat & Clean</h3>
|
|
||||||
<p>Pure #282a36, no texture. Maximum focus on content. Closest to how most Dracula apps look.</p>
|
|
||||||
<div class="bg-preview bg-flat">
|
|
||||||
<span class="bg-label">#282a36 — pure</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="option" data-choice="aurora" onclick="toggleSelect(this)">
|
|
||||||
<div class="letter">B</div>
|
|
||||||
<div class="content">
|
|
||||||
<h3>Aurora Glow</h3>
|
|
||||||
<p>Subtle radial gradients using Dracula's purple, pink, and cyan — like a soft aurora bleeding through the dark. Gives depth without noise.</p>
|
|
||||||
<div class="bg-preview bg-aurora">
|
|
||||||
<span class="bg-label">radial glows — purple / pink / cyan</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="option" data-choice="grid" onclick="toggleSelect(this)">
|
|
||||||
<div class="letter">C</div>
|
|
||||||
<div class="content">
|
|
||||||
<h3>Subtle Grid</h3>
|
|
||||||
<p>Faint 24px grid overlay on the dark background. Keeps a technical/terminal feel while the cards are soft. Nod to graph paper or circuit boards.</p>
|
|
||||||
<div class="bg-preview bg-grid">
|
|
||||||
<span class="bg-label">24px grid — #44475a lines</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="option" data-choice="dots" onclick="toggleSelect(this)">
|
|
||||||
<div class="letter">D</div>
|
|
||||||
<div class="content">
|
|
||||||
<h3>Dot Matrix</h3>
|
|
||||||
<p>Subtle dot pattern — evokes old terminal screens and LED matrices. Quiet texture, doesn't compete with content.</p>
|
|
||||||
<div class="bg-preview bg-dots">
|
|
||||||
<span class="bg-label">20px dot grid — comment color dots</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
<h2>Terminal Theme — Redesign Direction</h2>
|
|
||||||
<p class="subtitle">The current theme is functional but sparse: green-on-black, square borders, blinking cursor. Which direction feels right?</p>
|
|
||||||
|
|
||||||
<div class="options">
|
|
||||||
<div class="option" data-choice="crt" onclick="toggleSelect(this)">
|
|
||||||
<div class="letter">A</div>
|
|
||||||
<div class="content">
|
|
||||||
<h3>CRT Phosphor Glow</h3>
|
|
||||||
<p>Keep the green palette but push the retro CRT aesthetic much further — stronger scanlines, phosphor bloom on text, vignette darkening at corners, subtle screen curvature illusion. Very nostalgic, visually rich.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="option" data-choice="bios" onclick="toggleSelect(this)">
|
|
||||||
<div class="letter">B</div>
|
|
||||||
<div class="content">
|
|
||||||
<h3>Retro BIOS / DOS Amber</h3>
|
|
||||||
<p>Shift the palette from green to amber/orange (#ffb000) — the classic monochrome phosphor of early PCs. Or go full IBM-BIOS blue (dark blue bg, bright white text, cyan highlights). A completely different retro personality.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="option" data-choice="modern" onclick="toggleSelect(this)">
|
|
||||||
<div class="letter">C</div>
|
|
||||||
<div class="content">
|
|
||||||
<h3>Modern Terminal (Dracula / Catppuccin)</h3>
|
|
||||||
<p>Inspired by popular terminal themes (Dracula, Catppuccin Mocha, One Dark). Multiple accent colors, not just green — purple, cyan, pink, yellow all used. Clean, developer-aesthetic, but rich with color. Less "retro", more "everyday dev tool".</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="option" data-choice="matrix" onclick="toggleSelect(this)">
|
|
||||||
<div class="letter">D</div>
|
|
||||||
<div class="content">
|
|
||||||
<h3>Matrix / Hacker Aesthetic</h3>
|
|
||||||
<p>Stay green but add drama — cascading character rain animation in the background (subtle, faint), stronger glow effects, maybe ASCII art borders on cards. Very cinematic, very "movie hacker screen".</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,232 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Terminal Theme — Full Mockup</title>
|
|
||||||
<style>
|
|
||||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
||||||
body {
|
|
||||||
font-family: ui-monospace, 'JetBrains Mono', 'Fira Code', monospace;
|
|
||||||
background: #282a36;
|
|
||||||
background-image: radial-gradient(circle, rgba(98,114,164,0.55) 1px, transparent 1px);
|
|
||||||
background-size: 20px 20px;
|
|
||||||
min-height: 100vh;
|
|
||||||
color: #f8f8f2;
|
|
||||||
letter-spacing: 0.01em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Controls */
|
|
||||||
.controls {
|
|
||||||
position: fixed;
|
|
||||||
top: 20px;
|
|
||||||
right: 20px;
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
align-items: center;
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
.ctrl-btn {
|
|
||||||
height: 44px;
|
|
||||||
padding: 0 16px;
|
|
||||||
background: rgba(40,42,54,0.92);
|
|
||||||
border: 1px solid #44475a;
|
|
||||||
border-radius: 8px;
|
|
||||||
color: #f8f8f2;
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: 13px;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
transition: all 0.2s;
|
|
||||||
min-width: 118px;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
.ctrl-btn:hover {
|
|
||||||
border-color: #bd93f9;
|
|
||||||
color: #bd93f9;
|
|
||||||
background: rgba(189,147,249,0.08);
|
|
||||||
}
|
|
||||||
.ctrl-icon {
|
|
||||||
width: 44px;
|
|
||||||
height: 44px;
|
|
||||||
padding: 0;
|
|
||||||
min-width: unset;
|
|
||||||
justify-content: center;
|
|
||||||
opacity: 0.4;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
.ctrl-icon:hover { border-color: #44475a; color: #f8f8f2; background: rgba(40,42,54,0.92); }
|
|
||||||
.chevron { opacity: 0.6; font-size: 10px; }
|
|
||||||
|
|
||||||
/* Main */
|
|
||||||
.main {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
padding: 5rem 2rem 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Title */
|
|
||||||
.title {
|
|
||||||
font-size: 2.5rem;
|
|
||||||
font-weight: 700;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.06em;
|
|
||||||
background: linear-gradient(90deg, #bd93f9 0%, #ff79c6 100%);
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
background-clip: text;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.title-cursor {
|
|
||||||
-webkit-text-fill-color: #bd93f9;
|
|
||||||
animation: blink 1s step-end infinite;
|
|
||||||
}
|
|
||||||
@keyframes blink { 50% { opacity: 0; } }
|
|
||||||
|
|
||||||
/* Description */
|
|
||||||
.description {
|
|
||||||
color: #6272a4;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
margin-bottom: 3rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Grid */
|
|
||||||
.grid {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 1rem;
|
|
||||||
justify-content: center;
|
|
||||||
max-width: 860px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Cards */
|
|
||||||
.card {
|
|
||||||
width: 260px;
|
|
||||||
padding: 1.5rem;
|
|
||||||
background: #313244;
|
|
||||||
border: 1px solid #44475a;
|
|
||||||
border-radius: 10px;
|
|
||||||
box-shadow: 0 4px 16px rgba(0,0,0,0.45);
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: none;
|
|
||||||
color: inherit;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.card:hover {
|
|
||||||
border-color: #bd93f9;
|
|
||||||
box-shadow:
|
|
||||||
0 0 0 1px rgba(189,147,249,0.25),
|
|
||||||
0 8px 28px rgba(189,147,249,0.15),
|
|
||||||
0 4px 12px rgba(0,0,0,0.5);
|
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
|
||||||
.card.pin { background: #383a4a; }
|
|
||||||
.card h2 {
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #f8f8f2;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
transition: color 0.2s;
|
|
||||||
}
|
|
||||||
.card:hover h2 { color: #bd93f9; }
|
|
||||||
.card p {
|
|
||||||
font-size: 0.85rem;
|
|
||||||
color: #6272a4;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
.card:hover p { color: #cdd6f4; }
|
|
||||||
|
|
||||||
/* Beian */
|
|
||||||
.beian {
|
|
||||||
margin: 2rem 0 1.5rem;
|
|
||||||
color: #6272a4;
|
|
||||||
font-size: 13px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.beian a { color: #6272a4; text-decoration: none; transition: color 0.2s; }
|
|
||||||
.beian a:hover { color: #bd93f9; }
|
|
||||||
|
|
||||||
/* Annotation layer */
|
|
||||||
.annotation {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
background: rgba(30,32,43,0.97);
|
|
||||||
border-top: 1px solid #44475a;
|
|
||||||
padding: 14px 24px;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #6272a4;
|
|
||||||
display: flex;
|
|
||||||
gap: 24px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
font-family: ui-monospace, monospace;
|
|
||||||
z-index: 200;
|
|
||||||
}
|
|
||||||
.ann-item { display: flex; align-items: center; gap: 8px; }
|
|
||||||
.swatch { width: 12px; height: 12px; border-radius: 2px; display: inline-block; flex-shrink: 0; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div class="controls">
|
|
||||||
<button class="ctrl-btn">Terminal <span class="chevron">▾</span></button>
|
|
||||||
<button class="ctrl-btn ctrl-icon">☽</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="main">
|
|
||||||
<h1 class="title">IoT Learning<span class="title-cursor"> ▌</span></h1>
|
|
||||||
<p class="description">// your gateway to embedded systems & IoT resources</p>
|
|
||||||
|
|
||||||
<div class="grid">
|
|
||||||
<a class="card pin" href="#">
|
|
||||||
<h2>OJ Platform</h2>
|
|
||||||
<p>Online judge — submit and test your code against real problems</p>
|
|
||||||
</a>
|
|
||||||
<a class="card" href="#">
|
|
||||||
<h2>Code Runner</h2>
|
|
||||||
<p>Instant code execution in-browser for quick experiments</p>
|
|
||||||
</a>
|
|
||||||
<a class="card" href="#">
|
|
||||||
<h2>Reference Books</h2>
|
|
||||||
<p>Curated textbooks and documentation for IoT & embedded dev</p>
|
|
||||||
</a>
|
|
||||||
<a class="card" href="#">
|
|
||||||
<h2>Video Lectures</h2>
|
|
||||||
<p>Course recordings and tutorials from the lab</p>
|
|
||||||
</a>
|
|
||||||
<a class="card" href="#">
|
|
||||||
<h2>Arduino Docs</h2>
|
|
||||||
<p>Official Arduino reference and community guides</p>
|
|
||||||
</a>
|
|
||||||
<a class="card" href="#">
|
|
||||||
<h2>Forum</h2>
|
|
||||||
<p>Ask questions, share projects with classmates</p>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="beian">
|
|
||||||
<a href="#">备案号 12345678</a> · <a href="#">粤ICP备XXXXXXXX号</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Color annotations at bottom -->
|
|
||||||
<div class="annotation">
|
|
||||||
<div class="ann-item"><span class="swatch" style="background:#282a36;border:1px solid #44475a"></span> bg #282a36</div>
|
|
||||||
<div class="ann-item"><span class="swatch" style="background:#313244"></span> card #313244</div>
|
|
||||||
<div class="ann-item"><span class="swatch" style="background:#44475a"></span> border #44475a</div>
|
|
||||||
<div class="ann-item"><span class="swatch" style="background:#bd93f9"></span> accent #bd93f9</div>
|
|
||||||
<div class="ann-item"><span class="swatch" style="background:#ff79c6"></span> pink #ff79c6</div>
|
|
||||||
<div class="ann-item"><span class="swatch" style="background:#6272a4"></span> muted #6272a4</div>
|
|
||||||
<div class="ann-item"><span class="swatch" style="background:#f8f8f2"></span> fg #f8f8f2</div>
|
|
||||||
<div class="ann-item" style="margin-left:auto">hover a card to preview interaction →</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
<h2>Which Palette?</h2>
|
|
||||||
<p class="subtitle">Modern terminal themes each have a distinct personality. Which palette should drive the redesign?</p>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.swatch-row { display: flex; gap: 6px; margin: 10px 0 6px; flex-wrap: wrap; }
|
|
||||||
.swatch { width: 28px; height: 28px; border-radius: 4px; display: inline-block; }
|
|
||||||
.palette-preview {
|
|
||||||
font-family: ui-monospace, 'JetBrains Mono', monospace;
|
|
||||||
font-size: 12px;
|
|
||||||
padding: 10px 14px;
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-top: 8px;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="options">
|
|
||||||
<div class="option" data-choice="dracula" onclick="toggleSelect(this)">
|
|
||||||
<div class="letter">A</div>
|
|
||||||
<div class="content">
|
|
||||||
<h3>Dracula</h3>
|
|
||||||
<p>Dark purple background, vibrant pink/cyan/green accents. The most iconic dark theme — moody yet colorful.</p>
|
|
||||||
<div class="swatch-row">
|
|
||||||
<span class="swatch" style="background:#282a36" title="Background"></span>
|
|
||||||
<span class="swatch" style="background:#44475a" title="Current Line"></span>
|
|
||||||
<span class="swatch" style="background:#ff79c6" title="Pink"></span>
|
|
||||||
<span class="swatch" style="background:#8be9fd" title="Cyan"></span>
|
|
||||||
<span class="swatch" style="background:#50fa7b" title="Green"></span>
|
|
||||||
<span class="swatch" style="background:#bd93f9" title="Purple"></span>
|
|
||||||
<span class="swatch" style="background:#ffb86c" title="Orange"></span>
|
|
||||||
<span class="swatch" style="background:#f1fa8c" title="Yellow"></span>
|
|
||||||
</div>
|
|
||||||
<div class="palette-preview" style="background:#282a36;color:#f8f8f2">
|
|
||||||
<span style="color:#ff79c6">fn</span> <span style="color:#50fa7b">visit</span>(<span style="color:#8be9fd">url</span>: <span style="color:#bd93f9">str</span>) {<br>
|
|
||||||
<span style="color:#ffb86c">// navigate to resource</span><br>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="option" data-choice="catppuccin" onclick="toggleSelect(this)">
|
|
||||||
<div class="letter">B</div>
|
|
||||||
<div class="content">
|
|
||||||
<h3>Catppuccin Mocha</h3>
|
|
||||||
<p>Warm, desaturated dark background with soft pastel accents. Gentle on the eyes, cozy and modern.</p>
|
|
||||||
<div class="swatch-row">
|
|
||||||
<span class="swatch" style="background:#1e1e2e" title="Base"></span>
|
|
||||||
<span class="swatch" style="background:#313244" title="Surface0"></span>
|
|
||||||
<span class="swatch" style="background:#f38ba8" title="Red"></span>
|
|
||||||
<span class="swatch" style="background:#89dceb" title="Sky"></span>
|
|
||||||
<span class="swatch" style="background:#a6e3a1" title="Green"></span>
|
|
||||||
<span class="swatch" style="background:#cba6f7" title="Mauve"></span>
|
|
||||||
<span class="swatch" style="background:#fab387" title="Peach"></span>
|
|
||||||
<span class="swatch" style="background:#f9e2af" title="Yellow"></span>
|
|
||||||
</div>
|
|
||||||
<div class="palette-preview" style="background:#1e1e2e;color:#cdd6f4">
|
|
||||||
<span style="color:#f38ba8">fn</span> <span style="color:#a6e3a1">visit</span>(<span style="color:#89dceb">url</span>: <span style="color:#cba6f7">str</span>) {<br>
|
|
||||||
<span style="color:#fab387">// navigate to resource</span><br>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="option" data-choice="tokyonight" onclick="toggleSelect(this)">
|
|
||||||
<div class="letter">C</div>
|
|
||||||
<div class="content">
|
|
||||||
<h3>Tokyo Night</h3>
|
|
||||||
<p>Deep blue-navy background, electric blue and magenta highlights. Calm, cool, city-at-night aesthetic.</p>
|
|
||||||
<div class="swatch-row">
|
|
||||||
<span class="swatch" style="background:#1a1b26" title="Background"></span>
|
|
||||||
<span class="swatch" style="background:#24283b" title="Surface"></span>
|
|
||||||
<span class="swatch" style="background:#f7768e" title="Red"></span>
|
|
||||||
<span class="swatch" style="background:#7dcfff" title="Cyan"></span>
|
|
||||||
<span class="swatch" style="background:#9ece6a" title="Green"></span>
|
|
||||||
<span class="swatch" style="background:#bb9af7" title="Purple"></span>
|
|
||||||
<span class="swatch" style="background:#ff9e64" title="Orange"></span>
|
|
||||||
<span class="swatch" style="background:#e0af68" title="Yellow"></span>
|
|
||||||
</div>
|
|
||||||
<div class="palette-preview" style="background:#1a1b26;color:#c0caf5">
|
|
||||||
<span style="color:#f7768e">fn</span> <span style="color:#9ece6a">visit</span>(<span style="color:#7dcfff">url</span>: <span style="color:#bb9af7">str</span>) {<br>
|
|
||||||
<span style="color:#ff9e64">// navigate to resource</span><br>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
<h2>Dracula — How Far to Lean Into "Terminal"?</h2>
|
|
||||||
<p class="subtitle">Same palette, different personality. Which card style feels right?</p>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.preview-grid {
|
|
||||||
display: flex;
|
|
||||||
gap: 20px;
|
|
||||||
margin-top: 16px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
.preview-card-wrap { display: flex; flex-direction: column; align-items: center; gap: 10px; }
|
|
||||||
.preview-card-wrap .label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.1em; opacity: 0.6; }
|
|
||||||
|
|
||||||
/* Style A: still terminal — square, monospace, minimal */
|
|
||||||
.card-terminal {
|
|
||||||
width: 200px;
|
|
||||||
padding: 16px;
|
|
||||||
font-family: ui-monospace, 'JetBrains Mono', monospace;
|
|
||||||
background: transparent;
|
|
||||||
border: 1px solid rgba(80,250,123,0.4);
|
|
||||||
color: #f8f8f2;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
.card-terminal h4 { margin: 0 0 6px; color: #50fa7b; font-size: 13px; text-transform: uppercase; letter-spacing: 0.06em; }
|
|
||||||
.card-terminal p { margin: 0; color: #6272a4; font-size: 12px; }
|
|
||||||
.card-terminal:hover { background: rgba(80,250,123,0.06); border-color: #50fa7b; }
|
|
||||||
|
|
||||||
/* Style B: soft modern — rounded, card bg, subtle */
|
|
||||||
.card-modern {
|
|
||||||
width: 200px;
|
|
||||||
padding: 16px;
|
|
||||||
font-family: ui-monospace, 'JetBrains Mono', monospace;
|
|
||||||
background: #313244;
|
|
||||||
border: 1px solid #44475a;
|
|
||||||
border-radius: 10px;
|
|
||||||
color: #f8f8f2;
|
|
||||||
font-size: 13px;
|
|
||||||
box-shadow: 0 4px 16px rgba(0,0,0,0.4);
|
|
||||||
}
|
|
||||||
.card-modern h4 { margin: 0 0 6px; color: #bd93f9; font-size: 13px; }
|
|
||||||
.card-modern p { margin: 0; color: #6272a4; font-size: 12px; }
|
|
||||||
|
|
||||||
/* Style C: vibrant accented — colored top border, gradient glow */
|
|
||||||
.card-vibrant {
|
|
||||||
width: 200px;
|
|
||||||
padding: 16px;
|
|
||||||
font-family: ui-monospace, 'JetBrains Mono', monospace;
|
|
||||||
background: #282a36;
|
|
||||||
border: 1px solid #44475a;
|
|
||||||
border-top: 2px solid #ff79c6;
|
|
||||||
border-radius: 0 0 8px 8px;
|
|
||||||
color: #f8f8f2;
|
|
||||||
font-size: 13px;
|
|
||||||
box-shadow: 0 0 0 1px rgba(255,121,198,0.08), 0 8px 24px rgba(0,0,0,0.5);
|
|
||||||
}
|
|
||||||
.card-vibrant h4 { margin: 0 0 6px; color: #ff79c6; font-size: 13px; }
|
|
||||||
.card-vibrant p { margin: 0; color: #6272a4; font-size: 12px; }
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="options">
|
|
||||||
<div class="option" data-choice="terminal-dracula" onclick="toggleSelect(this)">
|
|
||||||
<div class="letter">A</div>
|
|
||||||
<div class="content">
|
|
||||||
<h3>Still Terminal</h3>
|
|
||||||
<p>Square corners, transparent cards, Dracula green as primary accent. Same structural DNA as today — just a better palette and more glow.</p>
|
|
||||||
<div style="background:#282a36;padding:16px;margin-top:10px;border-radius:6px">
|
|
||||||
<div style="color:#6272a4;font-family:monospace;font-size:11px;margin-bottom:8px"># IoT Learning Portal</div>
|
|
||||||
<div class="card-terminal">
|
|
||||||
<h4>▶ OJ Platform</h4>
|
|
||||||
<p>Online judge for practice</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="option" data-choice="modern-dracula" onclick="toggleSelect(this)">
|
|
||||||
<div class="letter">B</div>
|
|
||||||
<div class="content">
|
|
||||||
<h3>Soft Modern</h3>
|
|
||||||
<p>Rounded cards with Dracula surface colors, purple accent, subtle shadows. Feels like a polished app — less "green terminal", more "Dracula theme everything".</p>
|
|
||||||
<div style="background:#282a36;padding:16px;margin-top:10px;border-radius:6px">
|
|
||||||
<div style="color:#6272a4;font-family:monospace;font-size:11px;margin-bottom:8px"># IoT Learning Portal</div>
|
|
||||||
<div class="card-modern">
|
|
||||||
<h4>OJ Platform</h4>
|
|
||||||
<p>Online judge for practice</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="option" data-choice="vibrant-dracula" onclick="toggleSelect(this)">
|
|
||||||
<div class="letter">C</div>
|
|
||||||
<div class="content">
|
|
||||||
<h3>Vibrant & Accented</h3>
|
|
||||||
<p>Colored top border on cards (like Cyberpunk's treatment), pink/cyan/purple accents used actively, neon glow on hover. Most visually punchy of the three.</p>
|
|
||||||
<div style="background:#282a36;padding:16px;margin-top:10px;border-radius:6px">
|
|
||||||
<div style="color:#6272a4;font-family:monospace;font-size:11px;margin-bottom:8px"># IoT Learning Portal</div>
|
|
||||||
<div class="card-vibrant">
|
|
||||||
<h4>OJ Platform</h4>
|
|
||||||
<p>Online judge for practice</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<div style="display:flex;align-items:center;justify-content:center;min-height:60vh">
|
|
||||||
<p class="subtitle">Continuing in terminal…</p>
|
|
||||||
</div>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"reason":"owner process exited","timestamp":1779101686437}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
73384
|
|
||||||
34
CLAUDE.md
@@ -1,34 +0,0 @@
|
|||||||
# 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.
|
|
||||||
372
app.js
@@ -1,372 +0,0 @@
|
|||||||
import { pins, sites } from "./data.js"
|
|
||||||
import {
|
|
||||||
getDesignThemeLabel,
|
|
||||||
getInitialLanguage,
|
|
||||||
LANGUAGE_KEY,
|
|
||||||
LANGUAGE_NAMES,
|
|
||||||
SUPPORTED_LANGUAGES,
|
|
||||||
t,
|
|
||||||
} from "./i18n.js"
|
|
||||||
import { renderSites } from "./render.js"
|
|
||||||
import {
|
|
||||||
getCurrentDesignTheme,
|
|
||||||
getInitialDesignTheme,
|
|
||||||
getInitialTheme,
|
|
||||||
setDesignTheme,
|
|
||||||
setDesignThemeMenuOpen,
|
|
||||||
setSelectedDesignThemeUI,
|
|
||||||
setTheme,
|
|
||||||
toggleTheme,
|
|
||||||
updateDesignThemeOptions,
|
|
||||||
} from "./theme.js"
|
|
||||||
|
|
||||||
export function initApp() {
|
|
||||||
const CAT_ICON = "/icons/noto--cat-face.svg"
|
|
||||||
const themeToggle = document.getElementById("themeToggle")
|
|
||||||
const designThemeButton = document.getElementById("designThemeButton")
|
|
||||||
const designThemeList = document.getElementById("designThemeList")
|
|
||||||
const languageButton = document.getElementById("languageButton")
|
|
||||||
const languageList = document.getElementById("languageList")
|
|
||||||
const titleEl = document.querySelector(".title")
|
|
||||||
const designThemeLabelEl = document.querySelector(
|
|
||||||
'[data-i18n="designThemeLabel"]',
|
|
||||||
)
|
|
||||||
const languageLabelEl = document.querySelector('[data-i18n="languageLabel"]')
|
|
||||||
const moonIcon = document.querySelector(".theme-icon-moon")
|
|
||||||
const sunIcon = document.querySelector(".theme-icon-sun")
|
|
||||||
const sitesContainer = document.querySelector("#sites")
|
|
||||||
const faviconEl = document.querySelector('link[rel~="icon"]')
|
|
||||||
const beianIcpEl = document.querySelector('[data-i18n="beianIcp"]')
|
|
||||||
const beianMpsEl = document.querySelector('[data-i18n="beianMps"]')
|
|
||||||
|
|
||||||
let currentLanguage = getInitialLanguage()
|
|
||||||
|
|
||||||
const getThemeLabel = (designTheme, language = currentLanguage) =>
|
|
||||||
getDesignThemeLabel(designTheme, language)
|
|
||||||
|
|
||||||
function setSelectedLanguageUI(language) {
|
|
||||||
if (!languageList) return
|
|
||||||
const options = [...languageList.querySelectorAll('[role="option"]')]
|
|
||||||
options.forEach((el) => {
|
|
||||||
const value = el.getAttribute("data-value")
|
|
||||||
const label = LANGUAGE_NAMES[value] || value || ""
|
|
||||||
el.setAttribute("aria-selected", value === language ? "true" : "false")
|
|
||||||
el.textContent = label
|
|
||||||
})
|
|
||||||
if (languageButton) {
|
|
||||||
languageButton.textContent = LANGUAGE_NAMES[language] || language
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setLanguageMenuOpen(open) {
|
|
||||||
if (!languageButton || !languageList) return
|
|
||||||
languageButton.setAttribute("aria-expanded", open ? "true" : "false")
|
|
||||||
languageList.hidden = !open
|
|
||||||
if (open) {
|
|
||||||
languageList.focus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setTitleText(titleText) {
|
|
||||||
if (!titleEl) return
|
|
||||||
const cursorEl = titleEl.querySelector(".title-cursor")
|
|
||||||
if (cursorEl) {
|
|
||||||
let textNode = [...titleEl.childNodes].find(
|
|
||||||
(node) => node.nodeType === Node.TEXT_NODE && node.nodeValue?.trim(),
|
|
||||||
)
|
|
||||||
if (!textNode) {
|
|
||||||
textNode = document.createTextNode(titleText)
|
|
||||||
titleEl.insertBefore(textNode, cursorEl)
|
|
||||||
} else {
|
|
||||||
textNode.nodeValue = titleText
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
titleEl.textContent = titleText
|
|
||||||
}
|
|
||||||
titleEl.dataset.text = titleText
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDocumentLang(language) {
|
|
||||||
if (language === "zh-Hant") return "zh-Hant"
|
|
||||||
if (language === "zh-Hans") return "zh-Hans"
|
|
||||||
if (language === "wenyan") return "zh-Hans"
|
|
||||||
if (language === "mars") return "zh-Hans"
|
|
||||||
if (language === "ja") return "ja"
|
|
||||||
if (language === "ko") return "ko"
|
|
||||||
return "en"
|
|
||||||
}
|
|
||||||
|
|
||||||
function setSwappableIcon(element, language) {
|
|
||||||
if (!element) return
|
|
||||||
if (!element.dataset.defaultSrc) {
|
|
||||||
element.dataset.defaultSrc = element.getAttribute("src") || ""
|
|
||||||
}
|
|
||||||
element.setAttribute(
|
|
||||||
"src",
|
|
||||||
language === "meow" ? CAT_ICON : element.dataset.defaultSrc,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function setFavicon(language) {
|
|
||||||
if (!faviconEl) return
|
|
||||||
if (!faviconEl.dataset.defaultHref) {
|
|
||||||
faviconEl.dataset.defaultHref = faviconEl.getAttribute("href") || ""
|
|
||||||
}
|
|
||||||
faviconEl.setAttribute(
|
|
||||||
"href",
|
|
||||||
language === "meow" ? CAT_ICON : faviconEl.dataset.defaultHref,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyTranslations() {
|
|
||||||
const language = currentLanguage
|
|
||||||
document.documentElement.setAttribute("lang", getDocumentLang(language))
|
|
||||||
document.title = t("appTitle", language)
|
|
||||||
if (titleEl) {
|
|
||||||
const titleText = t("appTitle", language)
|
|
||||||
setTitleText(titleText)
|
|
||||||
}
|
|
||||||
if (designThemeLabelEl) {
|
|
||||||
designThemeLabelEl.textContent = t("designThemeLabel", language)
|
|
||||||
}
|
|
||||||
if (languageLabelEl) {
|
|
||||||
languageLabelEl.textContent = t("languageLabel", language)
|
|
||||||
}
|
|
||||||
if (beianIcpEl) {
|
|
||||||
beianIcpEl.textContent = t("beianIcp", language)
|
|
||||||
}
|
|
||||||
if (beianMpsEl) {
|
|
||||||
beianMpsEl.textContent = t("beianMps", language)
|
|
||||||
}
|
|
||||||
if (designThemeButton) {
|
|
||||||
designThemeButton.setAttribute(
|
|
||||||
"aria-label",
|
|
||||||
t("designThemeLabel", language),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (languageButton) {
|
|
||||||
languageButton.setAttribute("aria-label", t("languageLabel", language))
|
|
||||||
}
|
|
||||||
if (themeToggle) {
|
|
||||||
themeToggle.setAttribute("aria-label", t("themeToggleLabel", language))
|
|
||||||
themeToggle.setAttribute("title", t("themeToggleTitle", language))
|
|
||||||
}
|
|
||||||
if (moonIcon) {
|
|
||||||
moonIcon.setAttribute("alt", t("moonAlt", language))
|
|
||||||
}
|
|
||||||
if (sunIcon) {
|
|
||||||
sunIcon.setAttribute("alt", t("sunAlt", language))
|
|
||||||
}
|
|
||||||
setSwappableIcon(moonIcon, language)
|
|
||||||
setSwappableIcon(sunIcon, language)
|
|
||||||
setFavicon(language)
|
|
||||||
updateDesignThemeOptions({
|
|
||||||
designThemeList,
|
|
||||||
getLabel: getThemeLabel,
|
|
||||||
language,
|
|
||||||
})
|
|
||||||
setSelectedDesignThemeUI({
|
|
||||||
designThemeList,
|
|
||||||
designThemeButton,
|
|
||||||
designTheme: getCurrentDesignTheme(),
|
|
||||||
getLabel: getThemeLabel,
|
|
||||||
})
|
|
||||||
setSelectedLanguageUI(language)
|
|
||||||
renderSites({ container: sitesContainer, sites, pins, language })
|
|
||||||
}
|
|
||||||
|
|
||||||
function setLanguage(language) {
|
|
||||||
const safeLanguage = SUPPORTED_LANGUAGES.includes(language)
|
|
||||||
? language
|
|
||||||
: "zh-Hans"
|
|
||||||
currentLanguage = safeLanguage
|
|
||||||
localStorage.setItem(LANGUAGE_KEY, safeLanguage)
|
|
||||||
applyTranslations()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (titleEl && !titleEl.dataset.text) {
|
|
||||||
titleEl.dataset.text = titleEl.textContent?.trim() || ""
|
|
||||||
}
|
|
||||||
|
|
||||||
const initialTheme = getInitialTheme()
|
|
||||||
setTheme(initialTheme)
|
|
||||||
|
|
||||||
const initialDesignTheme = getInitialDesignTheme()
|
|
||||||
setDesignTheme(initialDesignTheme, themeToggle)
|
|
||||||
setSelectedDesignThemeUI({
|
|
||||||
designThemeList,
|
|
||||||
designThemeButton,
|
|
||||||
designTheme: initialDesignTheme,
|
|
||||||
getLabel: getThemeLabel,
|
|
||||||
})
|
|
||||||
setDesignThemeMenuOpen({
|
|
||||||
designThemeButton,
|
|
||||||
designThemeList,
|
|
||||||
open: false,
|
|
||||||
})
|
|
||||||
setLanguage(currentLanguage)
|
|
||||||
|
|
||||||
if (designThemeButton && designThemeList) {
|
|
||||||
designThemeButton.addEventListener("click", () => {
|
|
||||||
const isOpen = designThemeButton.getAttribute("aria-expanded") === "true"
|
|
||||||
setDesignThemeMenuOpen({
|
|
||||||
designThemeButton,
|
|
||||||
designThemeList,
|
|
||||||
open: !isOpen,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
designThemeList.addEventListener("click", (e) => {
|
|
||||||
const option = e.target.closest?.('[role="option"][data-value]')
|
|
||||||
if (!option) return
|
|
||||||
const value = option.getAttribute("data-value")
|
|
||||||
setDesignTheme(value, themeToggle)
|
|
||||||
setSelectedDesignThemeUI({
|
|
||||||
designThemeList,
|
|
||||||
designThemeButton,
|
|
||||||
designTheme: value,
|
|
||||||
getLabel: getThemeLabel,
|
|
||||||
})
|
|
||||||
setDesignThemeMenuOpen({
|
|
||||||
designThemeButton,
|
|
||||||
designThemeList,
|
|
||||||
open: false,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
document.addEventListener("click", (e) => {
|
|
||||||
if (!designThemeButton || !designThemeList) return
|
|
||||||
const clickedInside =
|
|
||||||
designThemeButton.contains(e.target) ||
|
|
||||||
designThemeList.contains(e.target)
|
|
||||||
if (!clickedInside) {
|
|
||||||
setDesignThemeMenuOpen({
|
|
||||||
designThemeButton,
|
|
||||||
designThemeList,
|
|
||||||
open: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
document.addEventListener("keydown", (e) => {
|
|
||||||
const isOpen = designThemeButton.getAttribute("aria-expanded") === "true"
|
|
||||||
if (!isOpen) return
|
|
||||||
|
|
||||||
if (e.key === "Escape") {
|
|
||||||
e.preventDefault()
|
|
||||||
setDesignThemeMenuOpen({
|
|
||||||
designThemeButton,
|
|
||||||
designThemeList,
|
|
||||||
open: false,
|
|
||||||
})
|
|
||||||
designThemeButton.focus()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const options = [...designThemeList.querySelectorAll('[role="option"]')]
|
|
||||||
if (!options.length) return
|
|
||||||
const current = getCurrentDesignTheme()
|
|
||||||
const currentIndex = Math.max(
|
|
||||||
0,
|
|
||||||
options.findIndex((el) => el.getAttribute("data-value") === current),
|
|
||||||
)
|
|
||||||
|
|
||||||
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
|
||||||
e.preventDefault()
|
|
||||||
const delta = e.key === "ArrowDown" ? 1 : -1
|
|
||||||
const nextIndex =
|
|
||||||
(currentIndex + delta + options.length) % options.length
|
|
||||||
const nextValue = options[nextIndex].getAttribute("data-value")
|
|
||||||
setDesignTheme(nextValue, themeToggle)
|
|
||||||
setSelectedDesignThemeUI({
|
|
||||||
designThemeList,
|
|
||||||
designThemeButton,
|
|
||||||
designTheme: nextValue,
|
|
||||||
getLabel: getThemeLabel,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.key === "Enter" || e.key === " ") {
|
|
||||||
e.preventDefault()
|
|
||||||
setDesignThemeMenuOpen({
|
|
||||||
designThemeButton,
|
|
||||||
designThemeList,
|
|
||||||
open: false,
|
|
||||||
})
|
|
||||||
designThemeButton.focus()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (languageButton && languageList) {
|
|
||||||
languageButton.addEventListener("click", () => {
|
|
||||||
const isOpen = languageButton.getAttribute("aria-expanded") === "true"
|
|
||||||
setLanguageMenuOpen(!isOpen)
|
|
||||||
})
|
|
||||||
|
|
||||||
languageList.addEventListener("click", (e) => {
|
|
||||||
const option = e.target.closest?.('[role="option"][data-value]')
|
|
||||||
if (!option) return
|
|
||||||
const value = option.getAttribute("data-value")
|
|
||||||
setLanguage(value)
|
|
||||||
setLanguageMenuOpen(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
document.addEventListener("click", (e) => {
|
|
||||||
if (!languageButton || !languageList) return
|
|
||||||
const clickedInside =
|
|
||||||
languageButton.contains(e.target) || languageList.contains(e.target)
|
|
||||||
if (!clickedInside) setLanguageMenuOpen(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
document.addEventListener("keydown", (e) => {
|
|
||||||
const isOpen = languageButton.getAttribute("aria-expanded") === "true"
|
|
||||||
if (!isOpen) return
|
|
||||||
|
|
||||||
if (e.key === "Escape") {
|
|
||||||
e.preventDefault()
|
|
||||||
setLanguageMenuOpen(false)
|
|
||||||
languageButton.focus()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const options = [...languageList.querySelectorAll('[role="option"]')]
|
|
||||||
if (!options.length) return
|
|
||||||
const currentIndex = Math.max(
|
|
||||||
0,
|
|
||||||
options.findIndex(
|
|
||||||
(el) => el.getAttribute("data-value") === currentLanguage,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
|
||||||
e.preventDefault()
|
|
||||||
const delta = e.key === "ArrowDown" ? 1 : -1
|
|
||||||
const nextIndex =
|
|
||||||
(currentIndex + delta + options.length) % options.length
|
|
||||||
const nextValue = options[nextIndex].getAttribute("data-value")
|
|
||||||
setLanguage(nextValue)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.key === "Enter" || e.key === " ") {
|
|
||||||
e.preventDefault()
|
|
||||||
setLanguageMenuOpen(false)
|
|
||||||
languageButton.focus()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
window
|
|
||||||
.matchMedia("(prefers-color-scheme: dark)")
|
|
||||||
.addEventListener("change", (e) => {
|
|
||||||
if (!localStorage.getItem("theme")) {
|
|
||||||
setTheme(e.matches ? "dark" : "light")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (themeToggle) {
|
|
||||||
themeToggle.addEventListener("click", toggleTheme)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
279
data.js
@@ -1,279 +0,0 @@
|
|||||||
export const pins = [
|
|
||||||
// {
|
|
||||||
// url: "https://code.xuyue.cc?query=30",
|
|
||||||
// description: "示例代码",
|
|
||||||
// },
|
|
||||||
]
|
|
||||||
|
|
||||||
export const sites = [
|
|
||||||
{
|
|
||||||
url: import.meta.env.VITE_OJ,
|
|
||||||
title: {
|
|
||||||
"zh-Hans": "判题狗",
|
|
||||||
mars: "判↗題★犬",
|
|
||||||
wenyan: "判题犬",
|
|
||||||
garbled: "è½◽",
|
|
||||||
bin: "011",
|
|
||||||
"zh-Hant": "判題狗",
|
|
||||||
en: "Judge Dog",
|
|
||||||
ja: "判定犬",
|
|
||||||
ko: "판정개",
|
|
||||||
meow: "喵喵喵",
|
|
||||||
emoji: "⚖️",
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
"zh-Hans": "在线判题网站",
|
|
||||||
mars: "線↗上氵☆判↘題钅★網",
|
|
||||||
wenyan: "判题之所",
|
|
||||||
garbled: "烫锟ä¢烫糊",
|
|
||||||
bin: "010011",
|
|
||||||
"zh-Hant": "在線判題網站",
|
|
||||||
en: "Online judge platform",
|
|
||||||
ja: "オンライン判定サイト",
|
|
||||||
ko: "온라인 판정 사이트",
|
|
||||||
meow: "喵喵喵喵喵喵",
|
|
||||||
emoji: "🌐🧪✅",
|
|
||||||
},
|
|
||||||
icon: "noto--dog-face.svg",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: import.meta.env.VITE_CODE,
|
|
||||||
title: {
|
|
||||||
"zh-Hans": "自测猫",
|
|
||||||
mars: "自↘測☆~貓",
|
|
||||||
wenyan: "自试猫",
|
|
||||||
garbled: "ä¢å",
|
|
||||||
bin: "001",
|
|
||||||
"zh-Hant": "自測貓",
|
|
||||||
en: "Self Test Cat",
|
|
||||||
ja: "自テスト猫",
|
|
||||||
ko: "자가테스트猫",
|
|
||||||
meow: "喵喵喵",
|
|
||||||
emoji: "🧪",
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
"zh-Hans": "代码运行网站",
|
|
||||||
mars: "代↗碼☆運↘?行→★站",
|
|
||||||
wenyan: "行码之所",
|
|
||||||
garbled: "¿屯屯糊¢◾",
|
|
||||||
bin: "100011",
|
|
||||||
"zh-Hant": "代碼運行網站",
|
|
||||||
en: "Code runner",
|
|
||||||
ja: "コード実行サイト",
|
|
||||||
ko: "코드 실행 사이트",
|
|
||||||
meow: "喵喵喵喵喵喵",
|
|
||||||
emoji: "💻▶️",
|
|
||||||
},
|
|
||||||
icon: "noto--cat-face.svg",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: import.meta.env.VITE_WEB,
|
|
||||||
title: {
|
|
||||||
"zh-Hans": "哈基米",
|
|
||||||
mars: "哈↗基?★米",
|
|
||||||
wenyan: "哈基米",
|
|
||||||
garbled: "¿▣▦",
|
|
||||||
bin: "010",
|
|
||||||
"zh-Hant": "哈基米",
|
|
||||||
en: "Hakimi",
|
|
||||||
ja: "ハキミ",
|
|
||||||
ko: "하키미",
|
|
||||||
meow: "喵喵喵",
|
|
||||||
emoji: "🌐🧩",
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
"zh-Hans": "Web 前端开发",
|
|
||||||
mars: "網☆↗頁☆前↘端孒★開↗發",
|
|
||||||
wenyan: "网页前端之作",
|
|
||||||
garbled: "ä▦▨斤¨è□¿",
|
|
||||||
bin: "01111110",
|
|
||||||
"zh-Hant": "Web 前端開發",
|
|
||||||
en: "Web frontend development",
|
|
||||||
ja: "Webフロントエンド開発",
|
|
||||||
ko: "웹 프론트엔드 개발",
|
|
||||||
meow: "喵喵喵喵喵喵喵喵",
|
|
||||||
emoji: "🖥️🎨",
|
|
||||||
},
|
|
||||||
icon: "noto--honeybee.svg",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: import.meta.env.VITE_SHUATI,
|
|
||||||
title: {
|
|
||||||
"zh-Hans": "刷题鸭",
|
|
||||||
mars: "刷勒↘題★鴨",
|
|
||||||
wenyan: "习题鸭",
|
|
||||||
garbled: "¿æä",
|
|
||||||
bin: "110",
|
|
||||||
"zh-Hant": "刷題鴨",
|
|
||||||
en: "Practice Duck",
|
|
||||||
ja: "演習アヒル",
|
|
||||||
ko: "문제풀이오리",
|
|
||||||
meow: "喵喵喵",
|
|
||||||
emoji: "🦆📝",
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
"zh-Hans": "梁老师的刷题网站",
|
|
||||||
mars: "梁師↗刷钅↘題←★網",
|
|
||||||
wenyan: "梁师习题之所",
|
|
||||||
garbled: "糊¿拷èç¿□¨",
|
|
||||||
bin: "00011101",
|
|
||||||
"zh-Hant": "梁老師的刷題網站",
|
|
||||||
en: "Practice problems by Mr. Liang",
|
|
||||||
ja: "梁先生の演習サイト",
|
|
||||||
ko: "량 선생님의 문제풀이 사이트",
|
|
||||||
meow: "喵喵喵喵喵喵喵喵",
|
|
||||||
emoji: "📚📝",
|
|
||||||
},
|
|
||||||
icon: "noto--paintbrush.svg",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: import.meta.env.VITE_BOOK,
|
|
||||||
title: {
|
|
||||||
"zh-Hans": "编程书",
|
|
||||||
mars: "編~↗程☆書☆",
|
|
||||||
wenyan: "程式书",
|
|
||||||
garbled: "¬拷拷",
|
|
||||||
bin: "000",
|
|
||||||
"zh-Hant": "編程書",
|
|
||||||
en: "Coding Books",
|
|
||||||
ja: "プログラミング書",
|
|
||||||
ko: "프로그래밍 책",
|
|
||||||
meow: "喵喵喵",
|
|
||||||
emoji: "📚💻",
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
"zh-Hans": "编程和计算机相关知识汇总",
|
|
||||||
mars: "編↗程→☆計↘算★機☆知↗識→↘彙→★總",
|
|
||||||
wenyan: "程式及计算机知识总览",
|
|
||||||
garbled: "拷¨锟斤锟¬锟¬ä▧斤¨",
|
|
||||||
bin: "100101111000",
|
|
||||||
"zh-Hant": "編程和計算機相關知識匯總",
|
|
||||||
en: "CS knowledge summary",
|
|
||||||
ja: "プログラミング/コンピュータ知識まとめ",
|
|
||||||
ko: "프로그래밍/컴퓨터 지식 모음",
|
|
||||||
meow: "喵喵喵喵喵喵喵喵喵喵喵喵",
|
|
||||||
emoji: "🧠📚",
|
|
||||||
},
|
|
||||||
icon: "noto--bookmark-tabs.svg",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: import.meta.env.VITE_BLOCKLY,
|
|
||||||
title: {
|
|
||||||
"zh-Hans": "小方块",
|
|
||||||
mars: "小↘方★塊",
|
|
||||||
wenyan: "小方块",
|
|
||||||
garbled: "¨糊",
|
|
||||||
bin: "110",
|
|
||||||
"zh-Hant": "小方塊",
|
|
||||||
en: "Little Blocks",
|
|
||||||
ja: "小さなブロック",
|
|
||||||
ko: "작은 블록",
|
|
||||||
meow: "喵喵喵",
|
|
||||||
emoji: "🧱🧩",
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
"zh-Hans": "搭积木,学编程",
|
|
||||||
mars: "搭↗積★木↘學訁☆?編↗★程",
|
|
||||||
wenyan: "以积木习程式",
|
|
||||||
garbled: "◽¬拷ç¿斤锟",
|
|
||||||
bin: "1101101",
|
|
||||||
"zh-Hant": "搭積木,學編程",
|
|
||||||
en: "Learn coding with blocks",
|
|
||||||
ja: "ブロックでプログラミング",
|
|
||||||
ko: "블록으로 프로그래밍 배우기",
|
|
||||||
meow: "喵喵喵喵喵喵喵",
|
|
||||||
emoji: "🧱💻",
|
|
||||||
},
|
|
||||||
icon: "twemoji--brick.svg",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: import.meta.env.VITE_HUABU,
|
|
||||||
title: {
|
|
||||||
"zh-Hans": "白板",
|
|
||||||
mars: "白↗板☆",
|
|
||||||
wenyan: "白板",
|
|
||||||
garbled: "¿拷",
|
|
||||||
bin: "01",
|
|
||||||
"zh-Hant": "白板",
|
|
||||||
en: "Whiteboard",
|
|
||||||
ja: "ホワイトボード",
|
|
||||||
ko: "화이트보드",
|
|
||||||
meow: "喵喵",
|
|
||||||
emoji: "🧑🏫📋",
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
"zh-Hans": "在线板书",
|
|
||||||
mars: "線↗上☆☆板↘書",
|
|
||||||
wenyan: "线上板书",
|
|
||||||
garbled: "¬¿■¿",
|
|
||||||
bin: "0001",
|
|
||||||
"zh-Hant": "在線板書",
|
|
||||||
en: "Online whiteboard",
|
|
||||||
ja: "オンライン板書",
|
|
||||||
ko: "온라인 판서",
|
|
||||||
meow: "喵喵喵喵",
|
|
||||||
emoji: "📝🌐",
|
|
||||||
},
|
|
||||||
icon: "noto--artist-palette.svg",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: import.meta.env.VITE_PPT,
|
|
||||||
title: {
|
|
||||||
"zh-Hans": "Python PPT",
|
|
||||||
mars: "P☆ytho→n↗PPT★",
|
|
||||||
wenyan: "蟒语课札",
|
|
||||||
garbled: "锟¢¥烫拷□锟▧▢è",
|
|
||||||
bin: "1001011111",
|
|
||||||
"zh-Hant": "Python PPT",
|
|
||||||
en: "Python PPT",
|
|
||||||
ja: "Python PPT",
|
|
||||||
ko: "Python PPT",
|
|
||||||
meow: "喵喵喵喵喵喵",
|
|
||||||
emoji: "🐍📽️",
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
"zh-Hans": "Python 第一学期上课用",
|
|
||||||
mars: "Pyt?h★o★n↗←首學★期↘課☆用",
|
|
||||||
wenyan: "蟒语首学期课用",
|
|
||||||
garbled: "糊èå▢噪□¬▦◽烫拷ä¬",
|
|
||||||
bin: "00000010111011",
|
|
||||||
"zh-Hant": "Python 第一學期上課用",
|
|
||||||
en: "Python semester 1 materials",
|
|
||||||
ja: "Python 1学期授業用",
|
|
||||||
ko: "Python 1학기 수업용",
|
|
||||||
meow: "喵喵喵喵喵喵喵喵喵喵喵喵喵喵",
|
|
||||||
emoji: "🐍🎓1️⃣",
|
|
||||||
},
|
|
||||||
icon: "material-icon-theme--python.svg",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: import.meta.env.VITE_PY,
|
|
||||||
title: {
|
|
||||||
"zh-Hans": "Python 项目",
|
|
||||||
mars: "P→ytho吖n↘項★目",
|
|
||||||
wenyan: "蟒语诸业",
|
|
||||||
garbled: "▧ç¬ä▧◾斤糊",
|
|
||||||
bin: "101110111",
|
|
||||||
"zh-Hant": "Python 項目",
|
|
||||||
en: "Python Projects",
|
|
||||||
ja: "Python プロジェクト",
|
|
||||||
ko: "Python 프로젝트",
|
|
||||||
meow: "喵喵喵喵喵喵喵喵",
|
|
||||||
emoji: "🐍🧰",
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
"zh-Hans": "Python 第二学期上课用",
|
|
||||||
mars: "Python↘次→學孒★期↗課☆用",
|
|
||||||
wenyan: "蟒语次学期课用",
|
|
||||||
garbled: "屯▤½¨ååç锟■噪屯屯◼¿",
|
|
||||||
bin: "11110110110010",
|
|
||||||
"zh-Hant": "Python 第二學期上課用",
|
|
||||||
en: "Python semester 2 materials",
|
|
||||||
ja: "Python 2学期授業用",
|
|
||||||
ko: "Python 2학기 수업용",
|
|
||||||
meow: "喵喵喵喵喵喵喵",
|
|
||||||
emoji: "🐍🎓2️⃣",
|
|
||||||
},
|
|
||||||
icon: "material-icon-theme--folder-python-open.svg",
|
|
||||||
},
|
|
||||||
].filter((site) => !!site.url)
|
|
||||||
@@ -1,485 +0,0 @@
|
|||||||
# Animal Crossing Theme Implementation Plan
|
|
||||||
|
|
||||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
||||||
|
|
||||||
**Goal:** Add an "animal-crossing" design theme (warm cream light / deep forest dark) to the hyyzhome IoT portal.
|
|
||||||
|
|
||||||
**Architecture:** Four small, independent file edits. No new files created. The theme slug is registered in `theme.js`, a dropdown option added in `index.html`, CSS variables + component overrides added in `style.css`, and display labels added in `i18n.js`. Changes are order-independent but committing theme.js + index.html first lets you visually verify the option appears before styling is complete.
|
|
||||||
|
|
||||||
**Tech Stack:** Vanilla JS, Vite, CSS custom properties. No framework. No test suite — verification is manual in the dev server (`npm start`).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Files Modified
|
|
||||||
|
|
||||||
| File | Change |
|
|
||||||
|---|---|
|
|
||||||
| `theme.js:1` | Add `"animal-crossing"` to `DESIGN_THEMES` array |
|
|
||||||
| `index.html:45` | Add `<li role="option" data-value="animal-crossing">` after the Nord option |
|
|
||||||
| `i18n.js:136–213` | Add `"animal-crossing"` key to every language in `DESIGN_THEME_LABELS` |
|
|
||||||
| `style.css` | Append light + dark variable blocks and component overrides at end of file |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 1: Register the theme slug
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `theme.js:1`
|
|
||||||
- Modify: `index.html:45`
|
|
||||||
|
|
||||||
- [ ] **Step 1: Add slug to DESIGN_THEMES**
|
|
||||||
|
|
||||||
In `theme.js`, change line 1 from:
|
|
||||||
```js
|
|
||||||
const DESIGN_THEMES = ["fluent", "material-you", "terminal", "cyberpunk", "nord"]
|
|
||||||
```
|
|
||||||
to:
|
|
||||||
```js
|
|
||||||
const DESIGN_THEMES = ["fluent", "material-you", "terminal", "cyberpunk", "nord", "animal-crossing"]
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Add dropdown option in index.html**
|
|
||||||
|
|
||||||
In `index.html`, after the Nord `<li>` (currently line 45):
|
|
||||||
```html
|
|
||||||
<li role="option" data-value="nord" aria-selected="false">
|
|
||||||
Nord
|
|
||||||
</li>
|
|
||||||
```
|
|
||||||
add immediately after:
|
|
||||||
```html
|
|
||||||
<li role="option" data-value="animal-crossing" aria-selected="false">
|
|
||||||
动森
|
|
||||||
</li>
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 3: Start dev server and verify option appears**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm start
|
|
||||||
```
|
|
||||||
|
|
||||||
Open the app in a browser. Click the design theme dropdown — "动森" should appear as a new option. Selecting it will show unstyled (inherits Fluent variables) — that's expected at this stage.
|
|
||||||
|
|
||||||
- [ ] **Step 4: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add theme.js index.html
|
|
||||||
git commit -m "feat: register animal-crossing theme slug"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 2: Add i18n labels
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `i18n.js:136–213`
|
|
||||||
|
|
||||||
- [ ] **Step 1: Add label to every language in DESIGN_THEME_LABELS**
|
|
||||||
|
|
||||||
In `i18n.js`, add `"animal-crossing"` to each language object inside `DESIGN_THEME_LABELS`. The full updated constant (lines 136–214) should be:
|
|
||||||
|
|
||||||
```js
|
|
||||||
export const DESIGN_THEME_LABELS = {
|
|
||||||
"zh-Hans": {
|
|
||||||
fluent: "Fluent",
|
|
||||||
"material-you": "Material You",
|
|
||||||
terminal: "终端",
|
|
||||||
cyberpunk: "赛博朋克",
|
|
||||||
nord: "Nord",
|
|
||||||
"animal-crossing": "动森",
|
|
||||||
},
|
|
||||||
"zh-Hant": {
|
|
||||||
fluent: "Fluent",
|
|
||||||
"material-you": "Material You",
|
|
||||||
terminal: "終端",
|
|
||||||
cyberpunk: "賽博龐克",
|
|
||||||
nord: "Nord",
|
|
||||||
"animal-crossing": "動森",
|
|
||||||
},
|
|
||||||
en: {
|
|
||||||
fluent: "Fluent",
|
|
||||||
"material-you": "Material You",
|
|
||||||
terminal: "Terminal",
|
|
||||||
cyberpunk: "Cyberpunk",
|
|
||||||
nord: "Nord",
|
|
||||||
"animal-crossing": "Animal Crossing",
|
|
||||||
},
|
|
||||||
ja: {
|
|
||||||
fluent: "Fluent",
|
|
||||||
"material-you": "Material You",
|
|
||||||
terminal: "ターミナル",
|
|
||||||
cyberpunk: "サイバーパンク",
|
|
||||||
nord: "Nord",
|
|
||||||
"animal-crossing": "どうぶつの森",
|
|
||||||
},
|
|
||||||
ko: {
|
|
||||||
fluent: "Fluent",
|
|
||||||
"material-you": "Material You",
|
|
||||||
terminal: "터미널",
|
|
||||||
cyberpunk: "사이버펑크",
|
|
||||||
nord: "Nord",
|
|
||||||
"animal-crossing": "동물의 숲",
|
|
||||||
},
|
|
||||||
wenyan: {
|
|
||||||
fluent: "流光",
|
|
||||||
"material-you": "物材",
|
|
||||||
terminal: "终端",
|
|
||||||
cyberpunk: "赛博",
|
|
||||||
nord: "清寒",
|
|
||||||
"animal-crossing": "森友",
|
|
||||||
},
|
|
||||||
mars: {
|
|
||||||
fluent: "流↗光",
|
|
||||||
"material-you": "材↘質",
|
|
||||||
terminal: "終↗★端",
|
|
||||||
cyberpunk: "賽↘!博",
|
|
||||||
nord: "清↗寒★",
|
|
||||||
"animal-crossing": "动↗森★",
|
|
||||||
},
|
|
||||||
garbled: {
|
|
||||||
fluent: "◼è▦",
|
|
||||||
"material-you": "拷▤屯ä锟◽",
|
|
||||||
terminal: "¥¬▤▨¿¿",
|
|
||||||
cyberpunk: "◼çæ¥烫¥",
|
|
||||||
nord: "æ◽屯¿",
|
|
||||||
"animal-crossing": "ä◼▤è",
|
|
||||||
},
|
|
||||||
bin: {
|
|
||||||
fluent: "0101",
|
|
||||||
"material-you": "010101",
|
|
||||||
terminal: "01010101",
|
|
||||||
cyberpunk: "0101010101",
|
|
||||||
nord: "0101010",
|
|
||||||
"animal-crossing": "01010101",
|
|
||||||
},
|
|
||||||
meow: {
|
|
||||||
fluent: "喵喵",
|
|
||||||
"material-you": "喵喵喵",
|
|
||||||
terminal: "喵喵",
|
|
||||||
cyberpunk: "喵喵喵喵",
|
|
||||||
nord: "喵喵喵",
|
|
||||||
"animal-crossing": "喵喵喵喵",
|
|
||||||
},
|
|
||||||
emoji: {
|
|
||||||
fluent: "💧",
|
|
||||||
"material-you": "🧱",
|
|
||||||
terminal: "⌨️",
|
|
||||||
cyberpunk: "⚡",
|
|
||||||
nord: "❄️",
|
|
||||||
"animal-crossing": "🌿",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Verify labels in browser**
|
|
||||||
|
|
||||||
With the dev server still running, switch language to English — the dropdown option should now read "Animal Crossing" instead of "动森". Switch to Japanese — should read "どうぶつの森". Switch to emoji — should read "🌿".
|
|
||||||
|
|
||||||
- [ ] **Step 3: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add i18n.js
|
|
||||||
git commit -m "feat: add animal-crossing labels for all 11 languages"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 3: Add CSS — light mode
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `style.css` (append after the Nord section, before the `@media (max-width: 600px)` block)
|
|
||||||
|
|
||||||
- [ ] **Step 1: Append light-mode CSS block**
|
|
||||||
|
|
||||||
At the end of the Nord section in `style.css` (before the `@media (max-width: 600px)` rule), append:
|
|
||||||
|
|
||||||
```css
|
|
||||||
/* ─── Animal Crossing — Sunny Meadow ───────────────────────── */
|
|
||||||
html[data-design-theme="animal-crossing"] {
|
|
||||||
color-scheme: light;
|
|
||||||
|
|
||||||
--accent: #6dbc7e;
|
|
||||||
--accent-rgb: 109, 188, 126;
|
|
||||||
--accent-2: #5aad6d;
|
|
||||||
--accent-3: #4a9e5d;
|
|
||||||
--accent-secondary-rgb: 244, 200, 66;
|
|
||||||
|
|
||||||
--page-gradient: linear-gradient(
|
|
||||||
135deg,
|
|
||||||
#fdf6e3 0%,
|
|
||||||
#f5f0d8 25%,
|
|
||||||
#fdf6e3 50%,
|
|
||||||
#f8f2e0 75%,
|
|
||||||
#fdf6e3 100%
|
|
||||||
);
|
|
||||||
--page-texture:
|
|
||||||
radial-gradient(
|
|
||||||
circle at 20% 50%,
|
|
||||||
rgba(109, 188, 126, 0.08) 0%,
|
|
||||||
transparent 50%
|
|
||||||
),
|
|
||||||
radial-gradient(
|
|
||||||
circle at 80% 80%,
|
|
||||||
rgba(244, 200, 66, 0.06) 0%,
|
|
||||||
transparent 50%
|
|
||||||
);
|
|
||||||
--title-gradient: linear-gradient(
|
|
||||||
135deg,
|
|
||||||
#6dbc7e 0%,
|
|
||||||
#5aad6d 40%,
|
|
||||||
#f4c842 100%
|
|
||||||
);
|
|
||||||
|
|
||||||
--control-bg: rgba(255, 252, 240, 0.82);
|
|
||||||
--control-border: rgba(109, 188, 126, 0.35);
|
|
||||||
--control-inset: rgba(255, 255, 255, 0.85);
|
|
||||||
--control-fg: #5c4824;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-design-theme="animal-crossing"] body {
|
|
||||||
color: #5c4824;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-design-theme="animal-crossing"] .design-theme-button,
|
|
||||||
html[data-design-theme="animal-crossing"] .theme-toggle {
|
|
||||||
border-radius: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-design-theme="animal-crossing"] .card {
|
|
||||||
background: rgba(255, 252, 240, 0.85);
|
|
||||||
border: 1px solid rgba(109, 188, 126, 0.25);
|
|
||||||
border-radius: 16px;
|
|
||||||
box-shadow:
|
|
||||||
0 2px 8px rgba(0, 0, 0, 0.06),
|
|
||||||
0 1px 2px rgba(0, 0, 0, 0.03),
|
|
||||||
inset 0 1px 0 rgba(255, 255, 255, 0.9);
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-design-theme="animal-crossing"] .card::before {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-design-theme="animal-crossing"] .card.pin {
|
|
||||||
background: rgba(245, 242, 228, 0.88);
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-design-theme="animal-crossing"] .card h2 {
|
|
||||||
color: #5c4824;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-design-theme="animal-crossing"] .card p {
|
|
||||||
color: #7a6040;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-design-theme="animal-crossing"] .card:hover,
|
|
||||||
html[data-design-theme="animal-crossing"] .card:focus {
|
|
||||||
color: var(--accent);
|
|
||||||
transform: translateY(-2px);
|
|
||||||
border-color: rgba(109, 188, 126, 0.45);
|
|
||||||
box-shadow:
|
|
||||||
0 8px 24px rgba(109, 188, 126, 0.2),
|
|
||||||
0 4px 8px rgba(0, 0, 0, 0.08),
|
|
||||||
inset 0 1px 0 rgba(255, 255, 255, 0.95);
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-design-theme="animal-crossing"] .card:hover h2,
|
|
||||||
html[data-design-theme="animal-crossing"] .card:focus h2 {
|
|
||||||
color: var(--accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-design-theme="animal-crossing"] .card:hover p,
|
|
||||||
html[data-design-theme="animal-crossing"] .card:focus p {
|
|
||||||
color: #5c4824;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-design-theme="animal-crossing"] .beian a {
|
|
||||||
color: #7a6040;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-design-theme="animal-crossing"] .beian a:hover {
|
|
||||||
color: var(--accent);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Verify light mode in browser**
|
|
||||||
|
|
||||||
Select "Animal Crossing" / "动森" in the dropdown with light mode active. Verify:
|
|
||||||
- Background is warm cream (not white/dark)
|
|
||||||
- Title has green-to-yellow gradient
|
|
||||||
- Cards are creamy with rounded corners (16px)
|
|
||||||
- Card hover shows green glow and lifts up
|
|
||||||
- Control buttons have 14px border radius
|
|
||||||
|
|
||||||
- [ ] **Step 3: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add style.css
|
|
||||||
git commit -m "feat: add animal-crossing light mode CSS"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 4: Add CSS — dark mode
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `style.css` (append immediately after the light-mode block from Task 3)
|
|
||||||
|
|
||||||
- [ ] **Step 1: Append dark-mode CSS block**
|
|
||||||
|
|
||||||
Immediately after the light-mode block (still before `@media (max-width: 600px)`), append:
|
|
||||||
|
|
||||||
```css
|
|
||||||
html[data-theme="dark"][data-design-theme="animal-crossing"] {
|
|
||||||
color-scheme: dark;
|
|
||||||
|
|
||||||
--accent: #9ed98a;
|
|
||||||
--accent-rgb: 158, 217, 138;
|
|
||||||
--accent-2: #86cb72;
|
|
||||||
--accent-3: #6dbc5e;
|
|
||||||
--accent-secondary-rgb: 245, 210, 90;
|
|
||||||
|
|
||||||
--page-gradient: #1e2d1e;
|
|
||||||
--page-texture:
|
|
||||||
radial-gradient(
|
|
||||||
circle at 20% 50%,
|
|
||||||
rgba(158, 217, 138, 0.06) 0%,
|
|
||||||
transparent 50%
|
|
||||||
),
|
|
||||||
radial-gradient(
|
|
||||||
circle at 80% 80%,
|
|
||||||
rgba(245, 210, 90, 0.04) 0%,
|
|
||||||
transparent 50%
|
|
||||||
);
|
|
||||||
--title-gradient: linear-gradient(
|
|
||||||
135deg,
|
|
||||||
#9ed98a 0%,
|
|
||||||
#86cb72 40%,
|
|
||||||
#f5d25a 100%
|
|
||||||
);
|
|
||||||
|
|
||||||
--control-bg: rgba(25, 40, 25, 0.82);
|
|
||||||
--control-border: rgba(158, 217, 138, 0.25);
|
|
||||||
--control-inset: rgba(255, 255, 255, 0.04);
|
|
||||||
--control-fg: #e8f4d8;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="dark"][data-design-theme="animal-crossing"] body {
|
|
||||||
color: #e8f4d8;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="dark"][data-design-theme="animal-crossing"] .card {
|
|
||||||
background: rgba(30, 48, 30, 0.82);
|
|
||||||
border: 1px solid rgba(158, 217, 138, 0.18);
|
|
||||||
border-radius: 16px;
|
|
||||||
box-shadow:
|
|
||||||
0 2px 8px rgba(0, 0, 0, 0.3),
|
|
||||||
0 1px 2px rgba(0, 0, 0, 0.2),
|
|
||||||
inset 0 1px 0 rgba(255, 255, 255, 0.04);
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="dark"][data-design-theme="animal-crossing"] .card::before {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="dark"][data-design-theme="animal-crossing"] .card.pin {
|
|
||||||
background: rgba(38, 58, 38, 0.88);
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="dark"][data-design-theme="animal-crossing"] .card h2 {
|
|
||||||
color: #e8f4d8;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="dark"][data-design-theme="animal-crossing"] .card p {
|
|
||||||
color: #b0c8a0;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="dark"][data-design-theme="animal-crossing"] .card:hover,
|
|
||||||
html[data-theme="dark"][data-design-theme="animal-crossing"] .card:focus {
|
|
||||||
border-color: rgba(158, 217, 138, 0.45);
|
|
||||||
box-shadow:
|
|
||||||
0 8px 24px rgba(158, 217, 138, 0.2),
|
|
||||||
0 4px 8px rgba(0, 0, 0, 0.3),
|
|
||||||
inset 0 1px 0 rgba(255, 255, 255, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="dark"][data-design-theme="animal-crossing"] .card:hover h2,
|
|
||||||
html[data-theme="dark"][data-design-theme="animal-crossing"] .card:focus h2 {
|
|
||||||
color: var(--accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="dark"][data-design-theme="animal-crossing"] .card:hover p,
|
|
||||||
html[data-theme="dark"][data-design-theme="animal-crossing"] .card:focus p {
|
|
||||||
color: #c8deb8;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="dark"][data-design-theme="animal-crossing"] .design-theme-list {
|
|
||||||
background: rgba(22, 36, 22, 0.9);
|
|
||||||
box-shadow:
|
|
||||||
0 12px 30px rgba(0, 0, 0, 0.55),
|
|
||||||
0 6px 12px rgba(0, 0, 0, 0.35),
|
|
||||||
inset 0 1px 0 rgba(255, 255, 255, 0.04);
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="dark"][data-design-theme="animal-crossing"] .beian a {
|
|
||||||
color: #b0c8a0;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="dark"][data-design-theme="animal-crossing"] .beian a:hover {
|
|
||||||
color: var(--accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
|
||||||
html[data-design-theme="animal-crossing"] .card,
|
|
||||||
html[data-design-theme="animal-crossing"] .design-theme-button,
|
|
||||||
html[data-design-theme="animal-crossing"] .theme-toggle {
|
|
||||||
transition-duration: 0.01ms !important;
|
|
||||||
transform: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Verify dark mode in browser**
|
|
||||||
|
|
||||||
Toggle to dark mode while "Animal Crossing" theme is active. Verify:
|
|
||||||
- Background is deep forest green `#1e2d1e` (not generic dark grey)
|
|
||||||
- Title has soft lime-to-yellow gradient
|
|
||||||
- Cards are dark forest green with subtle lime border
|
|
||||||
- Card hover shows lime glow
|
|
||||||
- Toggling back to light mode restores the cream palette
|
|
||||||
|
|
||||||
- [ ] **Step 3: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add style.css
|
|
||||||
git commit -m "feat: add animal-crossing dark mode CSS"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 5: Final verification
|
|
||||||
|
|
||||||
- [ ] **Step 1: Check theme persists across reload**
|
|
||||||
|
|
||||||
Select "Animal Crossing" in dark mode. Reload the page. Theme and dark/light preference should both be restored from `localStorage`.
|
|
||||||
|
|
||||||
- [ ] **Step 2: Check theme switching is clean**
|
|
||||||
|
|
||||||
Cycle through all 6 themes (Fluent → Material You → Terminal → Cyberpunk → Nord → Animal Crossing → Fluent). No visual artifacts or broken styles should appear at any step.
|
|
||||||
|
|
||||||
- [ ] **Step 3: Check forced-dark themes restore correctly**
|
|
||||||
|
|
||||||
While on Animal Crossing (light mode), switch to Terminal (forces dark). Then switch back to Animal Crossing — it should restore the light mode you had before.
|
|
||||||
|
|
||||||
- [ ] **Step 4: Run formatter**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run fmt
|
|
||||||
```
|
|
||||||
|
|
||||||
Confirm no diff beyond whitespace normalisation. Commit if formatter changed anything:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add -p
|
|
||||||
git commit -m "style: run prettier after animal-crossing theme"
|
|
||||||
```
|
|
||||||
@@ -1,540 +0,0 @@
|
|||||||
# Minecraft Theme Implementation Plan
|
|
||||||
|
|
||||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
||||||
|
|
||||||
**Goal:** Add a "minecraft" design theme — zero border-radius, Minecraft GUI bevel shadows, 16px pixel grid background, monospace font, dark (Stone & Cave) + light (Overworld Day) modes.
|
|
||||||
|
|
||||||
**Architecture:** Four small file edits following the established theme pattern. Dark mode uses `html[data-theme="dark"][data-design-theme="minecraft"]` selectors (same specificity as the generic dark override rules, but placed later in the file so they win). Light mode uses `html[data-theme="light"][data-design-theme="minecraft"]`. No font imports — system monospace only.
|
|
||||||
|
|
||||||
**Tech Stack:** Vanilla JS, Vite, CSS custom properties. No test suite — verification is manual via `npm start`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Files Modified
|
|
||||||
|
|
||||||
| File | Change |
|
|
||||||
|---|---|
|
|
||||||
| `theme.js:1` | Add `"minecraft"` to `DESIGN_THEMES` |
|
|
||||||
| `index.html:48` | Add `<li role="option" data-value="minecraft">` after animal-crossing |
|
|
||||||
| `i18n.js:136–226` | Add `minecraft` key in all 11 language objects of `DESIGN_THEME_LABELS` |
|
|
||||||
| `style.css` | Append dark vars + light vars + body/grid/control/card overrides + reduced-motion |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 1: Register the theme slug
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `theme.js:1`
|
|
||||||
- Modify: `index.html:48`
|
|
||||||
|
|
||||||
- [ ] **Step 1: Add slug to DESIGN_THEMES in theme.js**
|
|
||||||
|
|
||||||
Change line 1 from:
|
|
||||||
```js
|
|
||||||
const DESIGN_THEMES = ["fluent", "material-you", "terminal", "cyberpunk", "nord", "animal-crossing"]
|
|
||||||
```
|
|
||||||
to:
|
|
||||||
```js
|
|
||||||
const DESIGN_THEMES = ["fluent", "material-you", "terminal", "cyberpunk", "nord", "animal-crossing", "minecraft"]
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Add dropdown option in index.html**
|
|
||||||
|
|
||||||
After the animal-crossing `<li>` (currently lines 46–48):
|
|
||||||
```html
|
|
||||||
<li role="option" data-value="animal-crossing" aria-selected="false">
|
|
||||||
Animal Crossing
|
|
||||||
</li>
|
|
||||||
```
|
|
||||||
add immediately after:
|
|
||||||
```html
|
|
||||||
<li role="option" data-value="minecraft" aria-selected="false">
|
|
||||||
Minecraft
|
|
||||||
</li>
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 3: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add theme.js index.html
|
|
||||||
git commit -m "feat: register minecraft theme slug"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 2: Add i18n labels
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `i18n.js:136–226`
|
|
||||||
|
|
||||||
- [ ] **Step 1: Add `minecraft` key to every language object in DESIGN_THEME_LABELS**
|
|
||||||
|
|
||||||
Replace the entire `DESIGN_THEME_LABELS` constant with:
|
|
||||||
|
|
||||||
```js
|
|
||||||
export const DESIGN_THEME_LABELS = {
|
|
||||||
"zh-Hans": {
|
|
||||||
fluent: "Fluent",
|
|
||||||
"material-you": "Material You",
|
|
||||||
terminal: "终端",
|
|
||||||
cyberpunk: "赛博朋克",
|
|
||||||
nord: "Nord",
|
|
||||||
"animal-crossing": "动森",
|
|
||||||
minecraft: "我的世界",
|
|
||||||
},
|
|
||||||
"zh-Hant": {
|
|
||||||
fluent: "Fluent",
|
|
||||||
"material-you": "Material You",
|
|
||||||
terminal: "終端",
|
|
||||||
cyberpunk: "賽博龐克",
|
|
||||||
nord: "Nord",
|
|
||||||
"animal-crossing": "動森",
|
|
||||||
minecraft: "我的世界",
|
|
||||||
},
|
|
||||||
en: {
|
|
||||||
fluent: "Fluent",
|
|
||||||
"material-you": "Material You",
|
|
||||||
terminal: "Terminal",
|
|
||||||
cyberpunk: "Cyberpunk",
|
|
||||||
nord: "Nord",
|
|
||||||
"animal-crossing": "Animal Crossing",
|
|
||||||
minecraft: "Minecraft",
|
|
||||||
},
|
|
||||||
ja: {
|
|
||||||
fluent: "Fluent",
|
|
||||||
"material-you": "Material You",
|
|
||||||
terminal: "ターミナル",
|
|
||||||
cyberpunk: "サイバーパンク",
|
|
||||||
nord: "Nord",
|
|
||||||
"animal-crossing": "どうぶつの森",
|
|
||||||
minecraft: "マインクラフト",
|
|
||||||
},
|
|
||||||
ko: {
|
|
||||||
fluent: "Fluent",
|
|
||||||
"material-you": "Material You",
|
|
||||||
terminal: "터미널",
|
|
||||||
cyberpunk: "사이버펑크",
|
|
||||||
nord: "Nord",
|
|
||||||
"animal-crossing": "동물의 숲",
|
|
||||||
minecraft: "마인크래프트",
|
|
||||||
},
|
|
||||||
wenyan: {
|
|
||||||
fluent: "流光",
|
|
||||||
"material-you": "物材",
|
|
||||||
terminal: "终端",
|
|
||||||
cyberpunk: "赛博",
|
|
||||||
nord: "清寒",
|
|
||||||
"animal-crossing": "森友",
|
|
||||||
minecraft: "方块世界",
|
|
||||||
},
|
|
||||||
mars: {
|
|
||||||
fluent: "流↗光",
|
|
||||||
"material-you": "材↘質",
|
|
||||||
terminal: "終↗★端",
|
|
||||||
cyberpunk: "賽↘!博",
|
|
||||||
nord: "清↗寒★",
|
|
||||||
"animal-crossing": "动↗森★",
|
|
||||||
minecraft: "我↗的★世界",
|
|
||||||
},
|
|
||||||
garbled: {
|
|
||||||
fluent: "◼è▦",
|
|
||||||
"material-you": "拷▤屯ä锟◽",
|
|
||||||
terminal: "¥¬▤▨¿¿",
|
|
||||||
cyberpunk: "◼çæ¥烫¥",
|
|
||||||
nord: "æ◽屯¿",
|
|
||||||
"animal-crossing": "ä◼▤è",
|
|
||||||
minecraft: "▤◼▦è屯",
|
|
||||||
},
|
|
||||||
bin: {
|
|
||||||
fluent: "0101",
|
|
||||||
"material-you": "010101",
|
|
||||||
terminal: "01010101",
|
|
||||||
cyberpunk: "0101010101",
|
|
||||||
nord: "0101010",
|
|
||||||
"animal-crossing": "01010101",
|
|
||||||
minecraft: "0101010",
|
|
||||||
},
|
|
||||||
meow: {
|
|
||||||
fluent: "喵喵",
|
|
||||||
"material-you": "喵喵喵",
|
|
||||||
terminal: "喵喵",
|
|
||||||
cyberpunk: "喵喵喵喵",
|
|
||||||
nord: "喵喵喵",
|
|
||||||
"animal-crossing": "喵喵喵喵",
|
|
||||||
minecraft: "喵喵喵",
|
|
||||||
},
|
|
||||||
emoji: {
|
|
||||||
fluent: "💧",
|
|
||||||
"material-you": "🧱",
|
|
||||||
terminal: "⌨️",
|
|
||||||
cyberpunk: "⚡",
|
|
||||||
nord: "❄️",
|
|
||||||
"animal-crossing": "🌿",
|
|
||||||
minecraft: "⛏️",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add i18n.js
|
|
||||||
git commit -m "feat: add minecraft labels for all 11 languages"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 3: Add CSS — dark mode (Stone & Cave)
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `style.css` (append after the `prefers-reduced-motion` block at end of file)
|
|
||||||
|
|
||||||
- [ ] **Step 1: Append dark mode CSS block at end of style.css**
|
|
||||||
|
|
||||||
```css
|
|
||||||
/* ─── Minecraft — Stone & Cave / Overworld Day ──────────────── */
|
|
||||||
html[data-design-theme="minecraft"] {
|
|
||||||
color-scheme: dark;
|
|
||||||
|
|
||||||
--accent: #5aaa3a;
|
|
||||||
--accent-rgb: 90, 170, 58;
|
|
||||||
--accent-2: #3d8a2a;
|
|
||||||
--accent-3: #2a6e1a;
|
|
||||||
--accent-secondary-rgb: 91, 191, 232;
|
|
||||||
|
|
||||||
--page-gradient: #1a1a1a;
|
|
||||||
--page-texture: none;
|
|
||||||
--title-gradient: linear-gradient(135deg, #5bbfe8 0%, #5aaa3a 100%);
|
|
||||||
|
|
||||||
--control-bg: rgba(62, 62, 62, 0.92);
|
|
||||||
--control-border: #1a1a1a;
|
|
||||||
--control-inset: rgba(255, 255, 255, 0.2);
|
|
||||||
--control-fg: #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="dark"][data-design-theme="minecraft"] {
|
|
||||||
color-scheme: dark;
|
|
||||||
|
|
||||||
--accent: #5aaa3a;
|
|
||||||
--accent-rgb: 90, 170, 58;
|
|
||||||
--accent-2: #3d8a2a;
|
|
||||||
--accent-3: #2a6e1a;
|
|
||||||
--accent-secondary-rgb: 91, 191, 232;
|
|
||||||
|
|
||||||
--page-gradient: #1a1a1a;
|
|
||||||
--page-texture: none;
|
|
||||||
--title-gradient: linear-gradient(135deg, #5bbfe8 0%, #5aaa3a 100%);
|
|
||||||
|
|
||||||
--control-bg: rgba(62, 62, 62, 0.92);
|
|
||||||
--control-border: #1a1a1a;
|
|
||||||
--control-inset: rgba(255, 255, 255, 0.2);
|
|
||||||
--control-fg: #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-design-theme="minecraft"] body {
|
|
||||||
font-family: ui-monospace, "Courier New", Courier, monospace;
|
|
||||||
letter-spacing: 0.04em;
|
|
||||||
color: #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="dark"][data-design-theme="minecraft"] body {
|
|
||||||
color: #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-design-theme="minecraft"] body::before {
|
|
||||||
background-image:
|
|
||||||
linear-gradient(rgba(255, 255, 255, 0.025) 1px, transparent 1px),
|
|
||||||
linear-gradient(90deg, rgba(255, 255, 255, 0.025) 1px, transparent 1px);
|
|
||||||
background-size: 16px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-design-theme="minecraft"] .design-theme-button,
|
|
||||||
html[data-design-theme="minecraft"] .theme-toggle {
|
|
||||||
border-radius: 0;
|
|
||||||
backdrop-filter: none;
|
|
||||||
-webkit-backdrop-filter: none;
|
|
||||||
box-shadow:
|
|
||||||
inset 2px 2px 0 rgba(255, 255, 255, 0.25),
|
|
||||||
inset -2px -2px 0 rgba(0, 0, 0, 0.5);
|
|
||||||
transition: all 0.1s steps(2, end);
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-design-theme="minecraft"] .design-theme-button:hover,
|
|
||||||
html[data-design-theme="minecraft"] .theme-toggle:hover {
|
|
||||||
background: rgba(82, 82, 82, 0.95);
|
|
||||||
box-shadow:
|
|
||||||
inset 2px 2px 0 rgba(255, 255, 255, 0.35),
|
|
||||||
inset -2px -2px 0 rgba(0, 0, 0, 0.4);
|
|
||||||
transform: none;
|
|
||||||
border-color: rgba(var(--accent-rgb), 0.7);
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-design-theme="minecraft"] .design-theme-button:active,
|
|
||||||
html[data-design-theme="minecraft"] .theme-toggle:active {
|
|
||||||
box-shadow:
|
|
||||||
inset 2px 2px 0 rgba(0, 0, 0, 0.4),
|
|
||||||
inset -2px -2px 0 rgba(255, 255, 255, 0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-design-theme="minecraft"] .design-theme-list {
|
|
||||||
border-radius: 0;
|
|
||||||
backdrop-filter: none;
|
|
||||||
-webkit-backdrop-filter: none;
|
|
||||||
background: rgba(30, 30, 30, 0.96);
|
|
||||||
border: 2px solid #111111;
|
|
||||||
box-shadow:
|
|
||||||
inset 2px 2px 0 rgba(255, 255, 255, 0.1),
|
|
||||||
inset -2px -2px 0 rgba(0, 0, 0, 0.6);
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-design-theme="minecraft"] .design-theme-list [role="option"] {
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-design-theme="minecraft"]
|
|
||||||
.design-theme-list
|
|
||||||
[role="option"]:hover,
|
|
||||||
html[data-design-theme="minecraft"]
|
|
||||||
.design-theme-list
|
|
||||||
[role="option"][aria-selected="true"] {
|
|
||||||
background: rgba(var(--accent-rgb), 0.25);
|
|
||||||
color: var(--control-fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="dark"][data-design-theme="minecraft"] .card {
|
|
||||||
background: #3c3c3c;
|
|
||||||
border: 2px solid #111111;
|
|
||||||
border-radius: 0;
|
|
||||||
backdrop-filter: none;
|
|
||||||
-webkit-backdrop-filter: none;
|
|
||||||
box-shadow:
|
|
||||||
inset 2px 2px 0 rgba(255, 255, 255, 0.2),
|
|
||||||
inset -2px -2px 0 rgba(0, 0, 0, 0.5);
|
|
||||||
transition: all 0.1s steps(2, end);
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="dark"][data-design-theme="minecraft"] .card::before {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="dark"][data-design-theme="minecraft"] .card.pin {
|
|
||||||
background: #454545;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="dark"][data-design-theme="minecraft"] .card h2 {
|
|
||||||
color: #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="dark"][data-design-theme="minecraft"] .card p {
|
|
||||||
color: #c8c8c8;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="dark"][data-design-theme="minecraft"] .card:hover,
|
|
||||||
html[data-theme="dark"][data-design-theme="minecraft"] .card:focus {
|
|
||||||
background: #484848;
|
|
||||||
transform: none;
|
|
||||||
border-color: #5aaa3a;
|
|
||||||
box-shadow:
|
|
||||||
inset 2px 2px 0 rgba(0, 0, 0, 0.35),
|
|
||||||
inset -2px -2px 0 rgba(255, 255, 255, 0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="dark"][data-design-theme="minecraft"] .card:hover h2,
|
|
||||||
html[data-theme="dark"][data-design-theme="minecraft"] .card:focus h2 {
|
|
||||||
color: #5aaa3a;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="dark"][data-design-theme="minecraft"] .card:hover p,
|
|
||||||
html[data-theme="dark"][data-design-theme="minecraft"] .card:focus p {
|
|
||||||
color: #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="dark"][data-design-theme="minecraft"] .beian a {
|
|
||||||
color: #c8c8c8;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="dark"][data-design-theme="minecraft"] .beian a:hover {
|
|
||||||
color: #5aaa3a;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Verify dark mode in browser**
|
|
||||||
|
|
||||||
Start dev server (`npm start`). Select "我的世界" in dark mode. Verify:
|
|
||||||
- Background is `#1a1a1a` dark stone (not generic dark gray)
|
|
||||||
- Title has blue-to-green gradient
|
|
||||||
- Cards are `#3c3c3c` with visible bevel (lighter top-left, darker bottom-right inset)
|
|
||||||
- Cards have NO rounded corners (sharp square edges)
|
|
||||||
- Card hover inverts the bevel and border turns green
|
|
||||||
- Controls have zero border-radius and bevel effect
|
|
||||||
|
|
||||||
- [ ] **Step 3: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add style.css
|
|
||||||
git commit -m "feat: add minecraft dark mode CSS"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 4: Add CSS — light mode (Overworld Day)
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `style.css` (append after dark mode block from Task 3)
|
|
||||||
|
|
||||||
- [ ] **Step 1: Append light mode CSS block**
|
|
||||||
|
|
||||||
```css
|
|
||||||
html[data-theme="light"][data-design-theme="minecraft"] {
|
|
||||||
color-scheme: light;
|
|
||||||
|
|
||||||
--accent: #5c9e40;
|
|
||||||
--accent-rgb: 92, 158, 64;
|
|
||||||
--accent-2: #4a8a30;
|
|
||||||
--accent-3: #8b6914;
|
|
||||||
--accent-secondary-rgb: 135, 206, 235;
|
|
||||||
|
|
||||||
--page-gradient: linear-gradient(180deg, #87ceeb 0%, #c9e8f7 50%, #f0f8ff 100%);
|
|
||||||
--page-texture: none;
|
|
||||||
--title-gradient: linear-gradient(135deg, #5c9e40 0%, #8b6914 100%);
|
|
||||||
|
|
||||||
--control-bg: rgba(198, 198, 198, 0.92);
|
|
||||||
--control-border: #555555;
|
|
||||||
--control-inset: rgba(255, 255, 255, 0.5);
|
|
||||||
--control-fg: #2a2a2a;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="light"][data-design-theme="minecraft"] body {
|
|
||||||
color: #2a2a2a;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="light"][data-design-theme="minecraft"] body::before {
|
|
||||||
background-image:
|
|
||||||
linear-gradient(rgba(0, 0, 0, 0.04) 1px, transparent 1px),
|
|
||||||
linear-gradient(90deg, rgba(0, 0, 0, 0.04) 1px, transparent 1px);
|
|
||||||
background-size: 16px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="light"][data-design-theme="minecraft"] .design-theme-list {
|
|
||||||
background: rgba(210, 210, 210, 0.96);
|
|
||||||
border: 2px solid #555555;
|
|
||||||
box-shadow:
|
|
||||||
inset 2px 2px 0 rgba(255, 255, 255, 0.7),
|
|
||||||
inset -2px -2px 0 rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="light"][data-design-theme="minecraft"] .card {
|
|
||||||
background: rgba(198, 198, 198, 0.85);
|
|
||||||
border: 2px solid #555555;
|
|
||||||
border-radius: 0;
|
|
||||||
backdrop-filter: none;
|
|
||||||
-webkit-backdrop-filter: none;
|
|
||||||
box-shadow:
|
|
||||||
inset 2px 2px 0 rgba(255, 255, 255, 0.6),
|
|
||||||
inset -2px -2px 0 rgba(0, 0, 0, 0.25);
|
|
||||||
transition: all 0.1s steps(2, end);
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="light"][data-design-theme="minecraft"] .card::before {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="light"][data-design-theme="minecraft"] .card.pin {
|
|
||||||
background: rgba(210, 210, 210, 0.88);
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="light"][data-design-theme="minecraft"] .card h2 {
|
|
||||||
color: #2a2a2a;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="light"][data-design-theme="minecraft"] .card p {
|
|
||||||
color: #4a4a4a;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="light"][data-design-theme="minecraft"] .card:hover,
|
|
||||||
html[data-theme="light"][data-design-theme="minecraft"] .card:focus {
|
|
||||||
background: rgba(212, 224, 198, 0.92);
|
|
||||||
transform: none;
|
|
||||||
border-color: #5c9e40;
|
|
||||||
box-shadow:
|
|
||||||
inset 2px 2px 0 rgba(0, 0, 0, 0.15),
|
|
||||||
inset -2px -2px 0 rgba(255, 255, 255, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="light"][data-design-theme="minecraft"] .card:hover h2,
|
|
||||||
html[data-theme="light"][data-design-theme="minecraft"] .card:focus h2 {
|
|
||||||
color: #5c9e40;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="light"][data-design-theme="minecraft"] .card:hover p,
|
|
||||||
html[data-theme="light"][data-design-theme="minecraft"] .card:focus p {
|
|
||||||
color: #2a2a2a;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="light"][data-design-theme="minecraft"] .beian a {
|
|
||||||
color: #4a4a4a;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="light"][data-design-theme="minecraft"] .beian a:hover {
|
|
||||||
color: #5c9e40;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
|
||||||
html[data-design-theme="minecraft"] .card,
|
|
||||||
html[data-design-theme="minecraft"] .design-theme-button,
|
|
||||||
html[data-design-theme="minecraft"] .theme-toggle {
|
|
||||||
transition-duration: 0.01ms !important;
|
|
||||||
transform: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Verify light mode in browser**
|
|
||||||
|
|
||||||
Toggle to light mode with "我的世界" theme active. Verify:
|
|
||||||
- Background is sky blue gradient (top `#87ceeb` to near-white at bottom)
|
|
||||||
- Cards are stone gray `rgba(198,198,198)` with bevel (light top-left, darker bottom-right)
|
|
||||||
- Cards have NO rounded corners
|
|
||||||
- Card hover turns slightly green-tinted and border goes grass green
|
|
||||||
- 16px grid is faintly visible on the sky background
|
|
||||||
|
|
||||||
- [ ] **Step 3: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add style.css
|
|
||||||
git commit -m "feat: add minecraft light mode CSS"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 5: Final verification
|
|
||||||
|
|
||||||
- [ ] **Step 1: Check `minecraft` is not in FORCED_DARK_DESIGN_THEMES**
|
|
||||||
|
|
||||||
Read `theme.js` line 2. It must remain:
|
|
||||||
```js
|
|
||||||
const FORCED_DARK_DESIGN_THEMES = new Set(["terminal", "cyberpunk"])
|
|
||||||
```
|
|
||||||
"minecraft" must NOT appear here.
|
|
||||||
|
|
||||||
- [ ] **Step 2: Verify theme cycles cleanly**
|
|
||||||
|
|
||||||
Cycle through all 7 themes in the dropdown. No broken styles at any step.
|
|
||||||
|
|
||||||
- [ ] **Step 3: Verify forced-dark restore**
|
|
||||||
|
|
||||||
While on Minecraft (light mode), switch to Terminal (forces dark), then back to Minecraft — should restore light mode.
|
|
||||||
|
|
||||||
- [ ] **Step 4: Run formatter**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run fmt
|
|
||||||
```
|
|
||||||
|
|
||||||
If files changed, commit:
|
|
||||||
```bash
|
|
||||||
git add style.css index.html i18n.js theme.js
|
|
||||||
git commit -m "style: run prettier after minecraft theme"
|
|
||||||
```
|
|
||||||
@@ -1,527 +0,0 @@
|
|||||||
# Terminal Theme Redesign Implementation Plan
|
|
||||||
|
|
||||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
||||||
|
|
||||||
**Goal:** Replace the green-on-black terminal theme with a Dracula-palette soft-modern style (purple accent, rounded cards, dot-matrix background).
|
|
||||||
|
|
||||||
**Architecture:** All changes are in `style.css` — update CSS variable overrides in the `html[data-design-theme="terminal"]` block, then update each component override section below it. No JS changes needed; the forced-dark logic and font chain stay unchanged.
|
|
||||||
|
|
||||||
**Tech Stack:** Vanilla CSS, Vite. No test suite — verification is visual via `npm start`.
|
|
||||||
|
|
||||||
**Spec:** `docs/superpowers/specs/2026-05-18-terminal-theme-design.md`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## File Map
|
|
||||||
|
|
||||||
| File | Action | What changes |
|
|
||||||
|---|---|---|
|
|
||||||
| `style.css` lines 51–80 | Modify | CSS variables block for terminal theme |
|
|
||||||
| `style.css` lines 702–814 | Modify | All terminal component overrides |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 1: Update CSS variable tokens
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `style.css:51-80`
|
|
||||||
|
|
||||||
- [ ] **Step 1: Replace the terminal theme variable block**
|
|
||||||
|
|
||||||
Find this block (starts at line 51):
|
|
||||||
```css
|
|
||||||
html[data-design-theme="terminal"] {
|
|
||||||
color-scheme: dark;
|
|
||||||
|
|
||||||
--accent: #33ff00;
|
|
||||||
--accent-rgb: 51, 255, 0;
|
|
||||||
--accent-2: #33ff00;
|
|
||||||
--accent-3: #33ff00;
|
|
||||||
--accent-secondary-rgb: 255, 176, 0;
|
|
||||||
|
|
||||||
--page-gradient: #0a0a0a;
|
|
||||||
--page-texture:
|
|
||||||
repeating-linear-gradient(
|
|
||||||
to bottom,
|
|
||||||
rgba(var(--accent-rgb), 0.035) 0px,
|
|
||||||
rgba(var(--accent-rgb), 0.035) 1px,
|
|
||||||
transparent 2px,
|
|
||||||
transparent 4px
|
|
||||||
),
|
|
||||||
radial-gradient(
|
|
||||||
circle at 50% 50%,
|
|
||||||
rgba(var(--accent-rgb), 0.05) 0%,
|
|
||||||
transparent 60%
|
|
||||||
);
|
|
||||||
--title-gradient: none;
|
|
||||||
|
|
||||||
--control-bg: rgba(10, 10, 10, 0.85);
|
|
||||||
--control-border: rgba(var(--accent-rgb), 0.35);
|
|
||||||
--control-inset: transparent;
|
|
||||||
--control-fg: var(--accent);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Replace it with:
|
|
||||||
```css
|
|
||||||
html[data-design-theme="terminal"] {
|
|
||||||
color-scheme: dark;
|
|
||||||
|
|
||||||
--accent: #bd93f9;
|
|
||||||
--accent-rgb: 189, 147, 249;
|
|
||||||
--accent-2: #ff79c6;
|
|
||||||
--accent-3: #8be9fd;
|
|
||||||
--accent-secondary-rgb: 255, 121, 198;
|
|
||||||
|
|
||||||
--page-gradient: #282a36;
|
|
||||||
--page-texture: radial-gradient(
|
|
||||||
circle,
|
|
||||||
rgba(98, 114, 164, 0.55) 1px,
|
|
||||||
transparent 1px
|
|
||||||
);
|
|
||||||
--title-gradient: linear-gradient(90deg, #bd93f9 0%, #ff79c6 100%);
|
|
||||||
|
|
||||||
--control-bg: rgba(40, 42, 54, 0.92);
|
|
||||||
--control-border: #44475a;
|
|
||||||
--control-inset: transparent;
|
|
||||||
--control-fg: #f8f8f2;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Start dev server and verify background color changed**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm start
|
|
||||||
```
|
|
||||||
|
|
||||||
Open the app, switch design theme to **Terminal**. The page background should now be dark blue-purple (`#282a36`) with a faint dot grid, not pure black. The dot grid may be hard to see at first — look closely at a light source on a dark background.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 2: Fix dot-matrix background size + body text
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `style.css` — terminal `body` rule (~line 702) and add `body::before` size rule
|
|
||||||
|
|
||||||
- [ ] **Step 1: Update the terminal body rule**
|
|
||||||
|
|
||||||
Find:
|
|
||||||
```css
|
|
||||||
html[data-design-theme="terminal"] body {
|
|
||||||
font-family:
|
|
||||||
ui-monospace, "JetBrains Mono", "Fira Code", "VT323", Menlo, Consolas,
|
|
||||||
"Liberation Mono", monospace;
|
|
||||||
letter-spacing: 0.01em;
|
|
||||||
color: var(--accent);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Replace with:
|
|
||||||
```css
|
|
||||||
html[data-design-theme="terminal"] body {
|
|
||||||
font-family:
|
|
||||||
ui-monospace, "JetBrains Mono", "Fira Code", "VT323", Menlo, Consolas,
|
|
||||||
"Liberation Mono", monospace;
|
|
||||||
letter-spacing: 0.01em;
|
|
||||||
color: #f8f8f2;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Add body::before background-size rule immediately after**
|
|
||||||
|
|
||||||
After the `body` rule above, insert:
|
|
||||||
```css
|
|
||||||
html[data-design-theme="terminal"] body::before {
|
|
||||||
background-size: 20px 20px;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 3: Verify in browser**
|
|
||||||
|
|
||||||
The dot matrix texture should now tile visibly at 20px intervals across the `#282a36` background. Body text should be `#f8f8f2` (off-white) instead of green.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 3: Update title styling
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `style.css` — terminal `.title` and `.title::after` rules (~lines 761–775)
|
|
||||||
|
|
||||||
- [ ] **Step 1: Update the title rule**
|
|
||||||
|
|
||||||
Find:
|
|
||||||
```css
|
|
||||||
html[data-design-theme="terminal"] .title {
|
|
||||||
background: none;
|
|
||||||
-webkit-text-fill-color: currentColor;
|
|
||||||
color: var(--accent);
|
|
||||||
text-shadow: 0 0 5px rgba(var(--accent-rgb), 0.5);
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.04em;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Replace with:
|
|
||||||
```css
|
|
||||||
html[data-design-theme="terminal"] .title {
|
|
||||||
background: var(--title-gradient);
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
color: transparent;
|
|
||||||
text-shadow: none;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.04em;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Update the cursor rule to keep it visible**
|
|
||||||
|
|
||||||
The `::after` pseudo-element inherits `-webkit-text-fill-color: transparent` from the title, which would hide the cursor. Fix it explicitly.
|
|
||||||
|
|
||||||
Find:
|
|
||||||
```css
|
|
||||||
html[data-design-theme="terminal"] .title::after {
|
|
||||||
content: "\2588";
|
|
||||||
margin-left: 8px;
|
|
||||||
animation: terminal-cursor-blink 1s step-end infinite;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Replace with:
|
|
||||||
```css
|
|
||||||
html[data-design-theme="terminal"] .title::after {
|
|
||||||
content: "\2588";
|
|
||||||
margin-left: 8px;
|
|
||||||
animation: terminal-cursor-blink 1s step-end infinite;
|
|
||||||
-webkit-text-fill-color: #bd93f9;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 3: Verify in browser**
|
|
||||||
|
|
||||||
The page title should now show a purple-to-pink gradient text (uppercase). The blinking `█` cursor should be solid purple (`#bd93f9`), not transparent.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 4: Update card styling
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `style.css` — all terminal `.card` rules (~lines 777–814)
|
|
||||||
|
|
||||||
- [ ] **Step 1: Update the base card rule**
|
|
||||||
|
|
||||||
Find:
|
|
||||||
```css
|
|
||||||
html[data-design-theme="terminal"] .card {
|
|
||||||
border-radius: 0;
|
|
||||||
background: transparent;
|
|
||||||
border: 1px solid rgba(var(--accent-rgb), 0.35);
|
|
||||||
box-shadow: none;
|
|
||||||
backdrop-filter: none;
|
|
||||||
-webkit-backdrop-filter: none;
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Replace with:
|
|
||||||
```css
|
|
||||||
html[data-design-theme="terminal"] .card {
|
|
||||||
border-radius: 10px;
|
|
||||||
background: #313244;
|
|
||||||
border: 1px solid #44475a;
|
|
||||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.45);
|
|
||||||
backdrop-filter: none;
|
|
||||||
-webkit-backdrop-filter: none;
|
|
||||||
transform: translateY(0);
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Add pin card variant**
|
|
||||||
|
|
||||||
After the base card rule, add:
|
|
||||||
```css
|
|
||||||
html[data-design-theme="terminal"] .card.pin {
|
|
||||||
background: #383a4a;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 3: Update card h2, p, and beian (split the combined rule)**
|
|
||||||
|
|
||||||
Find:
|
|
||||||
```css
|
|
||||||
html[data-design-theme="terminal"] .card h2,
|
|
||||||
html[data-design-theme="terminal"] .card p,
|
|
||||||
html[data-design-theme="terminal"] .beian a {
|
|
||||||
color: var(--accent);
|
|
||||||
text-shadow: 0 0 5px rgba(var(--accent-rgb), 0.25);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Replace with:
|
|
||||||
```css
|
|
||||||
html[data-design-theme="terminal"] .card h2 {
|
|
||||||
color: #f8f8f2;
|
|
||||||
text-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-design-theme="terminal"] .card p {
|
|
||||||
color: #6272a4;
|
|
||||||
text-shadow: none;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 4: Update card hover/focus**
|
|
||||||
|
|
||||||
Find:
|
|
||||||
```css
|
|
||||||
html[data-design-theme="terminal"] .card:hover,
|
|
||||||
html[data-design-theme="terminal"] .card:focus {
|
|
||||||
background: rgba(var(--accent-rgb), 0.06);
|
|
||||||
border-color: var(--accent);
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Replace with:
|
|
||||||
```css
|
|
||||||
html[data-design-theme="terminal"] .card:hover,
|
|
||||||
html[data-design-theme="terminal"] .card:focus {
|
|
||||||
background: #313244;
|
|
||||||
border-color: #bd93f9;
|
|
||||||
box-shadow:
|
|
||||||
0 0 0 1px rgba(189, 147, 249, 0.25),
|
|
||||||
0 8px 28px rgba(189, 147, 249, 0.15),
|
|
||||||
0 4px 12px rgba(0, 0, 0, 0.5);
|
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 5: Update hover h2 color**
|
|
||||||
|
|
||||||
Find:
|
|
||||||
```css
|
|
||||||
html[data-design-theme="terminal"] .card:hover h2,
|
|
||||||
html[data-design-theme="terminal"] .card:focus h2 {
|
|
||||||
color: var(--accent);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Replace with:
|
|
||||||
```css
|
|
||||||
html[data-design-theme="terminal"] .card:hover h2,
|
|
||||||
html[data-design-theme="terminal"] .card:focus h2 {
|
|
||||||
color: #bd93f9;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 6: Add hover p color rule (currently missing)**
|
|
||||||
|
|
||||||
Immediately after the hover h2 rule, add:
|
|
||||||
```css
|
|
||||||
html[data-design-theme="terminal"] .card:hover p,
|
|
||||||
html[data-design-theme="terminal"] .card:focus p {
|
|
||||||
color: #cdd6f4;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 7: Verify in browser**
|
|
||||||
|
|
||||||
Cards should now have rounded corners, a dark-purple surface (`#313244`), and lift with a purple glow on hover. Card titles should be off-white at rest and turn purple on hover. Card descriptions should be muted (`#6272a4`) at rest and lighten on hover.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 5: Update controls and dropdown
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `style.css` — terminal `.design-theme-button`, `.theme-toggle`, `.design-theme-list` rules (~lines 710–759)
|
|
||||||
|
|
||||||
- [ ] **Step 1: Update the base button/toggle rule**
|
|
||||||
|
|
||||||
Find:
|
|
||||||
```css
|
|
||||||
html[data-design-theme="terminal"] .design-theme-button,
|
|
||||||
html[data-design-theme="terminal"] .theme-toggle {
|
|
||||||
border-radius: 0;
|
|
||||||
box-shadow: none;
|
|
||||||
backdrop-filter: none;
|
|
||||||
-webkit-backdrop-filter: none;
|
|
||||||
border: 1px solid rgba(var(--accent-rgb), 0.55);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Replace with:
|
|
||||||
```css
|
|
||||||
html[data-design-theme="terminal"] .design-theme-button,
|
|
||||||
html[data-design-theme="terminal"] .theme-toggle {
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: none;
|
|
||||||
backdrop-filter: none;
|
|
||||||
-webkit-backdrop-filter: none;
|
|
||||||
border: 1px solid #44475a;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Update button/toggle hover**
|
|
||||||
|
|
||||||
Find:
|
|
||||||
```css
|
|
||||||
html[data-design-theme="terminal"] .design-theme-button:hover,
|
|
||||||
html[data-design-theme="terminal"] .theme-toggle:hover {
|
|
||||||
background: var(--accent);
|
|
||||||
color: #0a0a0a;
|
|
||||||
border-color: var(--accent);
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Replace with:
|
|
||||||
```css
|
|
||||||
html[data-design-theme="terminal"] .design-theme-button:hover,
|
|
||||||
html[data-design-theme="terminal"] .theme-toggle:hover {
|
|
||||||
background: rgba(189, 147, 249, 0.08);
|
|
||||||
color: #bd93f9;
|
|
||||||
border-color: #bd93f9;
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 3: Update dropdown list**
|
|
||||||
|
|
||||||
Find:
|
|
||||||
```css
|
|
||||||
html[data-design-theme="terminal"] .design-theme-list {
|
|
||||||
border-radius: 0;
|
|
||||||
box-shadow: none;
|
|
||||||
backdrop-filter: none;
|
|
||||||
-webkit-backdrop-filter: none;
|
|
||||||
background: rgba(10, 10, 10, 0.92);
|
|
||||||
border: 1px solid rgba(var(--accent-rgb), 0.55);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Replace with:
|
|
||||||
```css
|
|
||||||
html[data-design-theme="terminal"] .design-theme-list {
|
|
||||||
border-radius: 10px;
|
|
||||||
box-shadow: none;
|
|
||||||
backdrop-filter: none;
|
|
||||||
-webkit-backdrop-filter: none;
|
|
||||||
background: rgba(40, 42, 54, 0.96);
|
|
||||||
border: 1px solid #44475a;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 4: Update dropdown option hover/selected**
|
|
||||||
|
|
||||||
Find:
|
|
||||||
```css
|
|
||||||
html[data-design-theme="terminal"] .design-theme-list [role="option"]:hover,
|
|
||||||
html[data-design-theme="terminal"]
|
|
||||||
.design-theme-list
|
|
||||||
[role="option"][aria-selected="true"] {
|
|
||||||
background: var(--accent);
|
|
||||||
color: #0a0a0a;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Replace with:
|
|
||||||
```css
|
|
||||||
html[data-design-theme="terminal"] .design-theme-list [role="option"]:hover,
|
|
||||||
html[data-design-theme="terminal"]
|
|
||||||
.design-theme-list
|
|
||||||
[role="option"][aria-selected="true"] {
|
|
||||||
background: rgba(189, 147, 249, 0.15);
|
|
||||||
color: #bd93f9;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 5: Verify in browser**
|
|
||||||
|
|
||||||
The theme selector button should have rounded corners and a dark surface. Hovering it should show a subtle purple tint (not green invert). Opening the dropdown should show a rounded dark panel; hovering or selecting an option highlights it in purple.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 6: Update beian footer
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `style.css` — terminal `.beian a` and `.beian a:hover` rules (~lines 791–814)
|
|
||||||
|
|
||||||
- [ ] **Step 1: Add beian rest-state rule**
|
|
||||||
|
|
||||||
The old combined rule (`.card h2, .card p, .beian a`) was split in Task 4, which removed the beian rest-state color. Insert a new rule immediately **before** the existing `.beian a:hover` rule:
|
|
||||||
|
|
||||||
```css
|
|
||||||
html[data-design-theme="terminal"] .beian a {
|
|
||||||
color: #6272a4;
|
|
||||||
text-shadow: none;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Update beian hover**
|
|
||||||
|
|
||||||
Find:
|
|
||||||
```css
|
|
||||||
html[data-design-theme="terminal"] .beian a:hover {
|
|
||||||
background: var(--accent);
|
|
||||||
color: #0a0a0a;
|
|
||||||
text-shadow: none;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Replace with:
|
|
||||||
```css
|
|
||||||
html[data-design-theme="terminal"] .beian a:hover {
|
|
||||||
color: #bd93f9;
|
|
||||||
background: none;
|
|
||||||
text-shadow: none;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 3: Verify in browser**
|
|
||||||
|
|
||||||
The footer ICP link should appear in muted purple-grey (`#6272a4`) at rest and turn accent purple (`#bd93f9`) on hover, with no background invert.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 7: Format, QA, and commit
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `style.css` (formatter pass)
|
|
||||||
|
|
||||||
- [ ] **Step 1: Run Prettier**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run fmt
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: `style.css` reformatted with no errors.
|
|
||||||
|
|
||||||
- [ ] **Step 2: Full visual QA checklist**
|
|
||||||
|
|
||||||
With `npm start` running, switch to the Terminal theme and check each item:
|
|
||||||
|
|
||||||
| Element | Expected |
|
|
||||||
|---|---|
|
|
||||||
| Page background | `#282a36` dark blue-purple with subtle 20px dot matrix |
|
|
||||||
| Title | Purple-to-pink gradient text, uppercase, blinking purple `█` cursor |
|
|
||||||
| Cards at rest | `#313244` rounded (10px), `#44475a` border, subtle shadow |
|
|
||||||
| Pin card | Slightly lighter surface `#383a4a` |
|
|
||||||
| Card h2 at rest | `#f8f8f2` off-white |
|
|
||||||
| Card p at rest | `#6272a4` muted purple-grey |
|
|
||||||
| Card on hover | Purple border glow, lifts 2px, h2 → `#bd93f9`, p → `#cdd6f4` |
|
|
||||||
| Theme selector button | Rounded 8px, dark surface, purple hover tint (no invert) |
|
|
||||||
| Dropdown panel | Rounded 10px, dark surface, purple option highlight |
|
|
||||||
| Dark toggle | Greyed out / cursor: not-allowed (unchanged) |
|
|
||||||
| Beian footer | `#6272a4` at rest, `#bd93f9` on hover, no bg invert |
|
|
||||||
| Other themes | Completely unaffected — spot-check Cyberpunk and Fluent |
|
|
||||||
|
|
||||||
- [ ] **Step 3: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add style.css
|
|
||||||
git commit -m "redesign: terminal theme — Dracula purple focus (soft modern cards, dot matrix bg)"
|
|
||||||
```
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
# Animal Crossing Theme — Sunny Meadow
|
|
||||||
|
|
||||||
**Date:** 2026-05-18
|
|
||||||
**Status:** Approved
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Add a fifth design theme ("animal-crossing") to the hyyzhome IoT learning portal. The theme captures the warm, cozy Animal Crossing aesthetic: leaf greens, cream backgrounds in light mode, and deep forest tones in dark mode.
|
|
||||||
|
|
||||||
Reference: https://guokaigdg.github.io/animal-island-ui/
|
|
||||||
|
|
||||||
## Behavior
|
|
||||||
|
|
||||||
- Supports both light and dark modes (no forced mode, unlike Terminal/Cyberpunk)
|
|
||||||
- Default: follows the user's current light/dark preference
|
|
||||||
- Slug: `"animal-crossing"` (kebab-case, consistent with `"material-you"`)
|
|
||||||
|
|
||||||
## Color Palette
|
|
||||||
|
|
||||||
### Light mode (Sunny Island Day)
|
|
||||||
|
|
||||||
| Token | Value | Role |
|
|
||||||
|---|---|---|
|
|
||||||
| `--accent` | `#6dbc7e` | Primary leaf green |
|
|
||||||
| `--accent-rgb` | `109, 188, 126` | |
|
|
||||||
| `--accent-2` | `#5aad6d` | Darker green |
|
|
||||||
| `--accent-3` | `#4a9e5d` | Deepest green |
|
|
||||||
| `--accent-secondary-rgb` | `244, 200, 66` | Warm yellow for texture |
|
|
||||||
| `--page-gradient` | Warm cream `#fdf6e3` base with soft radial blobs | Background |
|
|
||||||
| `--page-texture` | Soft radial green/yellow gradients | Overlay layer |
|
|
||||||
| `--title-gradient` | `#6dbc7e` → `#5aad6d` → `#f4c842` | Title text fill |
|
|
||||||
| `--control-bg` | `rgba(255, 252, 240, 0.82)` | Button/dropdown backgrounds |
|
|
||||||
| `--control-border` | `rgba(109, 188, 126, 0.35)` | Button borders |
|
|
||||||
| `--control-inset` | `rgba(255, 255, 255, 0.85)` | Inner highlight |
|
|
||||||
| `--control-fg` | `#5c4824` | Warm brown text |
|
|
||||||
| Card bg | `rgba(255, 252, 240, 0.85)` | Creamy white |
|
|
||||||
| Card border | `rgba(109, 188, 126, 0.25)` | Soft green border |
|
|
||||||
| Body text | `#5c4824` | Warm brown |
|
|
||||||
| Muted text (card p) | `#7a6040` | Lighter warm brown |
|
|
||||||
|
|
||||||
### Dark mode (Night on the Island)
|
|
||||||
|
|
||||||
| Token | Value | Role |
|
|
||||||
|---|---|---|
|
|
||||||
| `--accent` | `#9ed98a` | Soft lime |
|
|
||||||
| `--accent-rgb` | `158, 217, 138` | |
|
|
||||||
| `--accent-2` | `#86cb72` | |
|
|
||||||
| `--accent-3` | `#6dbc5e` | |
|
|
||||||
| `--accent-secondary-rgb` | `245, 210, 90` | Lantern yellow |
|
|
||||||
| `--page-gradient` | Deep forest `#1e2d1e` solid | Background |
|
|
||||||
| `--page-texture` | Subtle radial lime/yellow glows | Overlay layer |
|
|
||||||
| `--title-gradient` | `#9ed98a` → `#86cb72` → `#f5d25a` | Title text fill |
|
|
||||||
| `--control-bg` | `rgba(25, 40, 25, 0.82)` | Dark forest controls |
|
|
||||||
| `--control-border` | `rgba(158, 217, 138, 0.25)` | |
|
|
||||||
| `--control-inset` | `rgba(255, 255, 255, 0.04)` | |
|
|
||||||
| `--control-fg` | `#e8f4d8` | Off-white |
|
|
||||||
| Card bg | `rgba(30, 48, 30, 0.82)` | Dark forest green |
|
|
||||||
| Card border | `rgba(158, 217, 138, 0.18)` | |
|
|
||||||
| Body text | `#e8f4d8` | Off-white |
|
|
||||||
| Muted text | `#b0c8a0` | Muted sage |
|
|
||||||
|
|
||||||
## Visual Style
|
|
||||||
|
|
||||||
- **Border radius:** `16px` on cards (vs Fluent's `12px`), `14px` on controls — slightly softer
|
|
||||||
- **Hover:** gentle `translateY(-2px)` lift + green glow shadow matching accent
|
|
||||||
- **Backdrop filter:** same acrylic blur as Fluent (20px blur, 180% saturate)
|
|
||||||
- **No special font override** — system sans-serif for legibility
|
|
||||||
- **No animations** — keeps the theme calm and lightweight
|
|
||||||
|
|
||||||
## Files Changed
|
|
||||||
|
|
||||||
| File | Change |
|
|
||||||
|---|---|
|
|
||||||
| `theme.js` | Add `"animal-crossing"` to `DESIGN_THEMES` array |
|
|
||||||
| `style.css` | Add light + dark CSS variable blocks; card/hover overrides scoped to `[data-design-theme="animal-crossing"]` |
|
|
||||||
| `i18n.js` | Add label in all 11 language variants in `DESIGN_THEME_LABELS` |
|
|
||||||
| `index.html` | Add `<li role="option" data-value="animal-crossing" aria-selected="false">` |
|
|
||||||
|
|
||||||
## Labels by Language
|
|
||||||
|
|
||||||
| Language | Label |
|
|
||||||
|---|---|
|
|
||||||
| zh-Hans | 动森 |
|
|
||||||
| zh-Hant | 動森 |
|
|
||||||
| en | Animal Crossing |
|
|
||||||
| ja | どうぶつの森 |
|
|
||||||
| ko | 동물의 숲 |
|
|
||||||
| wenyan | 森友 |
|
|
||||||
| mars | 动↗森★ |
|
|
||||||
| garbled | ä◼▤è |
|
|
||||||
| bin | 01010101 |
|
|
||||||
| meow | 喵喵喵喵 |
|
|
||||||
| emoji | 🌿 |
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
# Terminal Theme Redesign — Design Spec
|
|
||||||
|
|
||||||
**Date:** 2026-05-18
|
|
||||||
**Status:** Approved
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Redesign the existing Terminal design theme from a sparse green-on-black aesthetic into a polished **Dracula-palette, soft-modern** style. The theme keeps its forced-dark, monospace character while gaining a richer color story, rounded cards, and a more refined hover system.
|
|
||||||
|
|
||||||
## Design Direction
|
|
||||||
|
|
||||||
**Palette:** Dracula
|
|
||||||
**Card style:** Soft modern — rounded corners, surface color fill, subtle shadow
|
|
||||||
**Background:** Dot matrix — `#282a36` base with 20px radial dot grid
|
|
||||||
**Accent strategy:** Purple Focus — single primary accent `#bd93f9`, pink `#ff79c6` used only in the title gradient
|
|
||||||
|
|
||||||
## Color Tokens
|
|
||||||
|
|
||||||
| Token | Value | Usage |
|
|
||||||
|---|---|---|
|
|
||||||
| Background | `#282a36` | Page base |
|
|
||||||
| Surface | `#313244` | Card background |
|
|
||||||
| Surface-alt | `#383a4a` | Pin card background |
|
|
||||||
| Border | `#44475a` | Card/control border |
|
|
||||||
| Accent (purple) | `#bd93f9` | Primary accent — borders on hover, h2 on hover, controls on hover, title start |
|
|
||||||
| Pink | `#ff79c6` | Title gradient end only |
|
|
||||||
| Foreground | `#f8f8f2` | Body text, card h2 |
|
|
||||||
| Muted | `#6272a4` | Card p, beian, description |
|
|
||||||
| Hover fg | `#cdd6f4` | Card p on hover |
|
|
||||||
|
|
||||||
CSS variables to set on `html[data-design-theme="terminal"]`:
|
|
||||||
|
|
||||||
```css
|
|
||||||
--accent: #bd93f9;
|
|
||||||
--accent-rgb: 189, 147, 249;
|
|
||||||
--accent-2: #ff79c6;
|
|
||||||
--accent-3: #8be9fd;
|
|
||||||
--accent-secondary-rgb: 255, 121, 198;
|
|
||||||
--page-gradient: #282a36;
|
|
||||||
--page-texture: radial-gradient(circle, rgba(98,114,164,0.55) 1px, transparent 1px);
|
|
||||||
--title-gradient: linear-gradient(90deg, #bd93f9 0%, #ff79c6 100%);
|
|
||||||
--control-bg: rgba(40, 42, 54, 0.92);
|
|
||||||
--control-border: #44475a;
|
|
||||||
--control-inset: transparent;
|
|
||||||
--control-fg: #f8f8f2;
|
|
||||||
```
|
|
||||||
|
|
||||||
The `--page-texture` background-size must be set to `20px 20px` on the `body::before` element for terminal theme.
|
|
||||||
|
|
||||||
## Components
|
|
||||||
|
|
||||||
### Background
|
|
||||||
|
|
||||||
`body` uses `--page-gradient` (`#282a36`) as a flat color.
|
|
||||||
`body::before` applies the dot matrix texture via `--page-texture` at `background-size: 20px 20px`.
|
|
||||||
No `backdrop-filter` anywhere in the terminal theme.
|
|
||||||
|
|
||||||
### Title
|
|
||||||
|
|
||||||
- Gradient text: `linear-gradient(90deg, #bd93f9 0%, #ff79c6 100%)` via `-webkit-background-clip: text`
|
|
||||||
- Uppercase, `letter-spacing: 0.04em`
|
|
||||||
- Blinking `█` cursor via `::after` (`\2588`, unchanged), color `#bd93f9`
|
|
||||||
- Remove the current `text-shadow` (conflicts with gradient text)
|
|
||||||
|
|
||||||
### Cards
|
|
||||||
|
|
||||||
| Property | Value |
|
|
||||||
|---|---|
|
|
||||||
| `background` | `#313244` |
|
|
||||||
| `border` | `1px solid #44475a` |
|
|
||||||
| `border-radius` | `10px` |
|
|
||||||
| `box-shadow` | `0 4px 16px rgba(0,0,0,0.45)` |
|
|
||||||
| `backdrop-filter` | `none` |
|
|
||||||
| `transform` | none (no translateY at rest) |
|
|
||||||
|
|
||||||
**Pin card:** `background: #383a4a`
|
|
||||||
|
|
||||||
**Hover / focus:**
|
|
||||||
- `border-color: #bd93f9`
|
|
||||||
- `box-shadow: 0 0 0 1px rgba(189,147,249,0.25), 0 8px 28px rgba(189,147,249,0.15), 0 4px 12px rgba(0,0,0,0.5)`
|
|
||||||
- `transform: translateY(-2px)`
|
|
||||||
- `h2 color: #bd93f9`
|
|
||||||
- `p color: #cdd6f4`
|
|
||||||
|
|
||||||
**Card `::before`:** `display: none` (no gradient border overlay)
|
|
||||||
|
|
||||||
**Card h2:** `#f8f8f2` at rest
|
|
||||||
**Card p:** `#6272a4` at rest
|
|
||||||
|
|
||||||
### Controls (theme button + toggle)
|
|
||||||
|
|
||||||
| Property | Value |
|
|
||||||
|---|---|
|
|
||||||
| `background` | `rgba(40, 42, 54, 0.92)` |
|
|
||||||
| `border` | `1px solid #44475a` |
|
|
||||||
| `border-radius` | `8px` |
|
|
||||||
| `color` | `#f8f8f2` |
|
|
||||||
| `box-shadow` | `none` |
|
|
||||||
| `backdrop-filter` | `none` |
|
|
||||||
|
|
||||||
**Hover:**
|
|
||||||
- `border-color: #bd93f9`
|
|
||||||
- `color: #bd93f9`
|
|
||||||
- `background: rgba(189, 147, 249, 0.08)`
|
|
||||||
- No `transform`
|
|
||||||
|
|
||||||
**Dark toggle:** remains `cursor: not-allowed` and visually disabled (terminal forces dark).
|
|
||||||
|
|
||||||
### Dropdown list
|
|
||||||
|
|
||||||
| Property | Value |
|
|
||||||
|---|---|
|
|
||||||
| `background` | `rgba(40, 42, 54, 0.96)` |
|
|
||||||
| `border` | `1px solid #44475a` |
|
|
||||||
| `border-radius` | `10px` |
|
|
||||||
| `box-shadow` | `none` |
|
|
||||||
|
|
||||||
**Options hover / selected:** `background: rgba(189, 147, 249, 0.15)`, `color: #bd93f9`
|
|
||||||
**Options:** keep `text-transform: uppercase` (terminal feel)
|
|
||||||
|
|
||||||
### Beian
|
|
||||||
|
|
||||||
- Rest: `#6272a4`
|
|
||||||
- Hover: `#bd93f9` (remove current invert-to-black-bg behavior)
|
|
||||||
|
|
||||||
### Body text
|
|
||||||
|
|
||||||
- `color: #f8f8f2`
|
|
||||||
- Font chain unchanged: `ui-monospace, "JetBrains Mono", "Fira Code", "VT323", Menlo, Consolas, "Liberation Mono", monospace`
|
|
||||||
|
|
||||||
## What Changes vs Current
|
|
||||||
|
|
||||||
| Element | Before | After |
|
|
||||||
|---|---|---|
|
|
||||||
| Accent color | `#33ff00` green | `#bd93f9` purple |
|
|
||||||
| Background | `#0a0a0a` | `#282a36` |
|
|
||||||
| Texture | Horizontal scanlines | Dot matrix 20px |
|
|
||||||
| Card bg | Transparent | `#313244` surface fill |
|
|
||||||
| Card corners | `border-radius: 0` | `border-radius: 10px` |
|
|
||||||
| Card hover | Green tint bg | Purple glow + lift |
|
|
||||||
| Title | Green text-shadow | Purple→pink gradient |
|
|
||||||
| Button hover | Full green bg invert | Subtle purple tint |
|
|
||||||
| Dropdown options | Uppercase, green invert | Uppercase, purple highlight |
|
|
||||||
|
|
||||||
## Out of Scope
|
|
||||||
|
|
||||||
- No changes to any other theme (Fluent, Material You, Nord, Cyberpunk, Animal Crossing, Minecraft)
|
|
||||||
- No new animations beyond the existing cursor blink
|
|
||||||
- No changes to `FORCED_DARK_DESIGN_THEMES` logic — terminal still forces dark
|
|
||||||
- No font changes
|
|
||||||
295
i18n.js
@@ -1,295 +0,0 @@
|
|||||||
export const I18N = {
|
|
||||||
"zh-Hans": {
|
|
||||||
appTitle: "物联网专业在线学习平台",
|
|
||||||
pinnedSubtitle: "置顶内容",
|
|
||||||
designThemeLabel: "设计主题",
|
|
||||||
themeToggleLabel: "切换主题",
|
|
||||||
themeToggleTitle: "切换深色/浅色模式",
|
|
||||||
moonAlt: "月亮",
|
|
||||||
sunAlt: "太阳",
|
|
||||||
languageLabel: "语言",
|
|
||||||
beianIcp: "浙ICP备2023044109号",
|
|
||||||
beianMps: "浙公网安备33100402331786号",
|
|
||||||
},
|
|
||||||
"zh-Hant": {
|
|
||||||
appTitle: "物聯網專業在線學習平台",
|
|
||||||
pinnedSubtitle: "置頂內容",
|
|
||||||
designThemeLabel: "設計主題",
|
|
||||||
themeToggleLabel: "切換主題",
|
|
||||||
themeToggleTitle: "切換深色/淺色模式",
|
|
||||||
moonAlt: "月亮",
|
|
||||||
sunAlt: "太陽",
|
|
||||||
languageLabel: "語言",
|
|
||||||
beianIcp: "浙ICP備2023044109號",
|
|
||||||
beianMps: "浙公網安備33100402331786號",
|
|
||||||
},
|
|
||||||
en: {
|
|
||||||
appTitle: "IoT Program Online Learning Hub",
|
|
||||||
pinnedSubtitle: "Pinned",
|
|
||||||
designThemeLabel: "Design theme",
|
|
||||||
themeToggleLabel: "Toggle theme",
|
|
||||||
themeToggleTitle: "Toggle dark/light mode",
|
|
||||||
moonAlt: "Moon",
|
|
||||||
sunAlt: "Sun",
|
|
||||||
languageLabel: "Language",
|
|
||||||
beianIcp: "Zhejiang ICP 2023044109",
|
|
||||||
beianMps: "Zhejiang Public Security 33100402331786",
|
|
||||||
},
|
|
||||||
ja: {
|
|
||||||
appTitle: "IoT専攻オンライン学習プラットフォーム",
|
|
||||||
pinnedSubtitle: "ピン留め",
|
|
||||||
designThemeLabel: "デザインテーマ",
|
|
||||||
themeToggleLabel: "テーマ切替",
|
|
||||||
themeToggleTitle: "ダーク/ライト切替",
|
|
||||||
moonAlt: "月",
|
|
||||||
sunAlt: "太陽",
|
|
||||||
languageLabel: "言語",
|
|
||||||
beianIcp: "浙江ICP 2023044109",
|
|
||||||
beianMps: "浙江公安 33100402331786",
|
|
||||||
},
|
|
||||||
ko: {
|
|
||||||
appTitle: "IoT 전공 온라인 학습 플랫폼",
|
|
||||||
pinnedSubtitle: "고정",
|
|
||||||
designThemeLabel: "디자인 테마",
|
|
||||||
themeToggleLabel: "테마 전환",
|
|
||||||
themeToggleTitle: "다크/라이트 전환",
|
|
||||||
moonAlt: "달",
|
|
||||||
sunAlt: "태양",
|
|
||||||
languageLabel: "언어",
|
|
||||||
beianIcp: "저장 ICP 2023044109",
|
|
||||||
beianMps: "저장 공안 33100402331786",
|
|
||||||
},
|
|
||||||
wenyan: {
|
|
||||||
appTitle: "物联网专业线上学塾",
|
|
||||||
pinnedSubtitle: "置顶",
|
|
||||||
designThemeLabel: "设计之式",
|
|
||||||
themeToggleLabel: "易其主题",
|
|
||||||
themeToggleTitle: "更晦明",
|
|
||||||
moonAlt: "月",
|
|
||||||
sunAlt: "日",
|
|
||||||
languageLabel: "语言",
|
|
||||||
beianIcp: "浙ICP备2023044109号",
|
|
||||||
beianMps: "浙公安备33100402331786号",
|
|
||||||
},
|
|
||||||
mars: {
|
|
||||||
appTitle: "物↗聯☆網↘專★業☆線★↗上★~學↘塾",
|
|
||||||
pinnedSubtitle: "置↗頂★內→↘容氵",
|
|
||||||
designThemeLabel: "設↗計☆主↘題",
|
|
||||||
themeToggleLabel: "切↗換★主↘…題孒",
|
|
||||||
themeToggleTitle: "切↗換★晦钅↘明~",
|
|
||||||
moonAlt: "月",
|
|
||||||
sunAlt: "日",
|
|
||||||
languageLabel: "語↗言",
|
|
||||||
beianIcp: "浙↗ICP☆★備吖202訁3044109★號",
|
|
||||||
beianMps: "浙↘公★勒安備33氵100勒40孒2★331786號",
|
|
||||||
},
|
|
||||||
garbled: {
|
|
||||||
appTitle: "糊斤▥烫斤拷▦噪¿ä¢çèä",
|
|
||||||
pinnedSubtitle: "¨¢糊◾¬¿",
|
|
||||||
designThemeLabel: "¬æ◽ä◾▩",
|
|
||||||
themeToggleLabel: "ä斤¿è▣拷",
|
|
||||||
themeToggleTitle: "▥¬糊¬烫è拷ç¿",
|
|
||||||
moonAlt: "¥æ½",
|
|
||||||
sunAlt: "¥▤锟",
|
|
||||||
languageLabel: "¿锟屯",
|
|
||||||
beianIcp: "å锟æ¨å屯¥◾▨",
|
|
||||||
beianMps: "¿噪斤ä屯斤½æ屯ç▩",
|
|
||||||
},
|
|
||||||
bin: {
|
|
||||||
appTitle: "0101010101010101",
|
|
||||||
pinnedSubtitle: "010101",
|
|
||||||
designThemeLabel: "0101010101",
|
|
||||||
themeToggleLabel: "01010101",
|
|
||||||
themeToggleTitle: "010101010101010101",
|
|
||||||
moonAlt: "0101",
|
|
||||||
sunAlt: "1010",
|
|
||||||
languageLabel: "010101",
|
|
||||||
beianIcp: "01010101010101010101",
|
|
||||||
beianMps: "0101010101010101010101",
|
|
||||||
},
|
|
||||||
meow: {
|
|
||||||
appTitle: "喵喵喵喵喵喵喵喵喵喵喵喵",
|
|
||||||
pinnedSubtitle: "喵喵喵喵",
|
|
||||||
designThemeLabel: "喵喵喵喵",
|
|
||||||
themeToggleLabel: "喵喵喵喵",
|
|
||||||
themeToggleTitle: "喵喵喵喵喵喵喵喵喵",
|
|
||||||
moonAlt: "喵喵",
|
|
||||||
sunAlt: "喵喵",
|
|
||||||
languageLabel: "喵喵",
|
|
||||||
beianIcp: "喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵",
|
|
||||||
beianMps: "喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵",
|
|
||||||
},
|
|
||||||
emoji: {
|
|
||||||
appTitle: "📡🎓📚🌐🧠",
|
|
||||||
pinnedSubtitle: "📌",
|
|
||||||
designThemeLabel: "🎨",
|
|
||||||
themeToggleLabel: "🌓",
|
|
||||||
themeToggleTitle: "🌗/🌕",
|
|
||||||
moonAlt: "🌙",
|
|
||||||
sunAlt: "☀️",
|
|
||||||
languageLabel: "🌐",
|
|
||||||
beianIcp: "🧾 ICP 2023044109",
|
|
||||||
beianMps: "🛡️ MPS 33100402331786",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DESIGN_THEME_LABELS = {
|
|
||||||
"zh-Hans": {
|
|
||||||
fluent: "Fluent",
|
|
||||||
"material-you": "Material You",
|
|
||||||
terminal: "终端",
|
|
||||||
cyberpunk: "赛博朋克",
|
|
||||||
nord: "Nord",
|
|
||||||
"animal-crossing": "动森",
|
|
||||||
minecraft: "我的世界",
|
|
||||||
"persona-5": "女神异闻录 5",
|
|
||||||
},
|
|
||||||
"zh-Hant": {
|
|
||||||
fluent: "Fluent",
|
|
||||||
"material-you": "Material You",
|
|
||||||
terminal: "終端",
|
|
||||||
cyberpunk: "賽博龐克",
|
|
||||||
nord: "Nord",
|
|
||||||
"animal-crossing": "動森",
|
|
||||||
minecraft: "我的世界",
|
|
||||||
"persona-5": "女神異聞錄 5",
|
|
||||||
},
|
|
||||||
en: {
|
|
||||||
fluent: "Fluent",
|
|
||||||
"material-you": "Material You",
|
|
||||||
terminal: "Terminal",
|
|
||||||
cyberpunk: "Cyberpunk",
|
|
||||||
nord: "Nord",
|
|
||||||
"animal-crossing": "Animal Crossing",
|
|
||||||
minecraft: "Minecraft",
|
|
||||||
"persona-5": "Persona 5",
|
|
||||||
},
|
|
||||||
ja: {
|
|
||||||
fluent: "Fluent",
|
|
||||||
"material-you": "Material You",
|
|
||||||
terminal: "ターミナル",
|
|
||||||
cyberpunk: "サイバーパンク",
|
|
||||||
nord: "Nord",
|
|
||||||
"animal-crossing": "どうぶつの森",
|
|
||||||
minecraft: "マインクラフト",
|
|
||||||
"persona-5": "ペルソナ5",
|
|
||||||
},
|
|
||||||
ko: {
|
|
||||||
fluent: "Fluent",
|
|
||||||
"material-you": "Material You",
|
|
||||||
terminal: "터미널",
|
|
||||||
cyberpunk: "사이버펑크",
|
|
||||||
nord: "Nord",
|
|
||||||
"animal-crossing": "동물의 숲",
|
|
||||||
minecraft: "마인크래프트",
|
|
||||||
"persona-5": "페르소나 5",
|
|
||||||
},
|
|
||||||
wenyan: {
|
|
||||||
fluent: "流光",
|
|
||||||
"material-you": "物材",
|
|
||||||
terminal: "终端",
|
|
||||||
cyberpunk: "赛博",
|
|
||||||
nord: "清寒",
|
|
||||||
"animal-crossing": "森友",
|
|
||||||
minecraft: "方块世界",
|
|
||||||
"persona-5": "异闻录五",
|
|
||||||
},
|
|
||||||
mars: {
|
|
||||||
fluent: "流↗光",
|
|
||||||
"material-you": "材↘質",
|
|
||||||
terminal: "終↗★端",
|
|
||||||
cyberpunk: "賽↘!博",
|
|
||||||
nord: "清↗寒★",
|
|
||||||
"animal-crossing": "动↗森★",
|
|
||||||
minecraft: "我↗的★世界",
|
|
||||||
"persona-5": "女↘神★异闻5",
|
|
||||||
},
|
|
||||||
garbled: {
|
|
||||||
fluent: "◼è▦",
|
|
||||||
"material-you": "拷▤屯ä锟◽",
|
|
||||||
terminal: "¥¬▤▨¿¿",
|
|
||||||
cyberpunk: "◼çæ¥烫¥",
|
|
||||||
nord: "æ◽屯¿",
|
|
||||||
"animal-crossing": "ä◼▤è",
|
|
||||||
minecraft: "▤◼▦è屯",
|
|
||||||
"persona-5": "▦¥◼ç5",
|
|
||||||
},
|
|
||||||
bin: {
|
|
||||||
fluent: "0101",
|
|
||||||
"material-you": "010101",
|
|
||||||
terminal: "01010101",
|
|
||||||
cyberpunk: "0101010101",
|
|
||||||
nord: "0101010",
|
|
||||||
"animal-crossing": "01010101",
|
|
||||||
minecraft: "0101010",
|
|
||||||
"persona-5": "01010101",
|
|
||||||
},
|
|
||||||
meow: {
|
|
||||||
fluent: "喵喵",
|
|
||||||
"material-you": "喵喵喵",
|
|
||||||
terminal: "喵喵",
|
|
||||||
cyberpunk: "喵喵喵喵",
|
|
||||||
nord: "喵喵喵",
|
|
||||||
"animal-crossing": "喵喵喵喵",
|
|
||||||
minecraft: "喵喵喵",
|
|
||||||
"persona-5": "喵喵喵喵喵",
|
|
||||||
},
|
|
||||||
emoji: {
|
|
||||||
fluent: "💧",
|
|
||||||
"material-you": "🧱",
|
|
||||||
terminal: "⌨️",
|
|
||||||
cyberpunk: "⚡",
|
|
||||||
nord: "❄️",
|
|
||||||
"animal-crossing": "🌿",
|
|
||||||
minecraft: "⛏️",
|
|
||||||
"persona-5": "🎭",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export const LANGUAGE_NAMES = {
|
|
||||||
"zh-Hans": "简体中文",
|
|
||||||
"zh-Hant": "繁體中文",
|
|
||||||
en: "English",
|
|
||||||
ja: "日本語",
|
|
||||||
ko: "한국어",
|
|
||||||
wenyan: "文言文",
|
|
||||||
mars: "非★主☆流",
|
|
||||||
garbled: "GBK乱码",
|
|
||||||
bin: "计算机语",
|
|
||||||
meow: "喵喵喵",
|
|
||||||
emoji: "😅😅😅",
|
|
||||||
}
|
|
||||||
|
|
||||||
export const LANGUAGE_KEY = "language"
|
|
||||||
export const SUPPORTED_LANGUAGES = Object.keys(LANGUAGE_NAMES)
|
|
||||||
|
|
||||||
export function getLocalizedText(value, language) {
|
|
||||||
if (!value) return ""
|
|
||||||
if (typeof value === "object") {
|
|
||||||
return value[language] || value["zh-Hans"]
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getInitialLanguage() {
|
|
||||||
const saved = localStorage.getItem(LANGUAGE_KEY)
|
|
||||||
if (saved && SUPPORTED_LANGUAGES.includes(saved)) return saved
|
|
||||||
const normalized = (navigator.language || "").toLowerCase()
|
|
||||||
if (normalized.startsWith("zh")) {
|
|
||||||
return normalized.includes("hant") || normalized.includes("tw")
|
|
||||||
? "zh-Hant"
|
|
||||||
: "zh-Hans"
|
|
||||||
}
|
|
||||||
if (normalized.startsWith("ja")) return "ja"
|
|
||||||
if (normalized.startsWith("ko")) return "ko"
|
|
||||||
return "zh-Hans"
|
|
||||||
}
|
|
||||||
|
|
||||||
export function t(key, language) {
|
|
||||||
return I18N[language]?.[key] || I18N["zh-Hans"][key] || ""
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getDesignThemeLabel(designTheme, language) {
|
|
||||||
const labels = DESIGN_THEME_LABELS[language] || DESIGN_THEME_LABELS["zh-Hans"]
|
|
||||||
return labels[designTheme] || labels.fluent
|
|
||||||
}
|
|
||||||
136
index.html
@@ -1,140 +1,34 @@
|
|||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html lang="zh-Hans">
|
<html lang="zh-Hans">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>物联网专业の在线学习平台</title>
|
<title>徐越的在线学习平台</title>
|
||||||
<link rel="stylesheet" href="./style.css" />
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="theme-controls">
|
|
||||||
<label class="design-theme">
|
|
||||||
<span class="visually-hidden" data-i18n="designThemeLabel">
|
|
||||||
设计主题
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
class="design-theme-button"
|
|
||||||
id="designThemeButton"
|
|
||||||
type="button"
|
|
||||||
aria-label="设计主题"
|
|
||||||
aria-haspopup="listbox"
|
|
||||||
aria-expanded="false"
|
|
||||||
aria-controls="designThemeList"
|
|
||||||
>
|
|
||||||
流光
|
|
||||||
</button>
|
|
||||||
<ul
|
|
||||||
class="design-theme-list"
|
|
||||||
id="designThemeList"
|
|
||||||
role="listbox"
|
|
||||||
tabindex="-1"
|
|
||||||
hidden
|
|
||||||
>
|
|
||||||
<li role="option" data-value="fluent" aria-selected="true">Fluent</li>
|
|
||||||
<li role="option" data-value="material-you" aria-selected="false">
|
|
||||||
Material You
|
|
||||||
</li>
|
|
||||||
<li role="option" data-value="terminal" aria-selected="false">
|
|
||||||
Terminal
|
|
||||||
</li>
|
|
||||||
<li role="option" data-value="cyberpunk" aria-selected="false">
|
|
||||||
Cyberpunk
|
|
||||||
</li>
|
|
||||||
<li role="option" data-value="nord" aria-selected="false">Nord</li>
|
|
||||||
<li role="option" data-value="animal-crossing" aria-selected="false">
|
|
||||||
Animal Crossing
|
|
||||||
</li>
|
|
||||||
<li role="option" data-value="minecraft" aria-selected="false">
|
|
||||||
Minecraft
|
|
||||||
</li>
|
|
||||||
<li role="option" data-value="persona-5" aria-selected="false">
|
|
||||||
Persona 5
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</label>
|
|
||||||
<label class="design-theme language-switch">
|
|
||||||
<span class="visually-hidden" data-i18n="languageLabel">语言</span>
|
|
||||||
<button
|
|
||||||
class="design-theme-button"
|
|
||||||
id="languageButton"
|
|
||||||
type="button"
|
|
||||||
aria-label="语言"
|
|
||||||
aria-haspopup="listbox"
|
|
||||||
aria-expanded="false"
|
|
||||||
aria-controls="languageList"
|
|
||||||
>
|
|
||||||
中文
|
|
||||||
</button>
|
|
||||||
<ul
|
|
||||||
class="design-theme-list"
|
|
||||||
id="languageList"
|
|
||||||
role="listbox"
|
|
||||||
tabindex="-1"
|
|
||||||
hidden
|
|
||||||
>
|
|
||||||
<li role="option" data-value="zh-Hans" aria-selected="true">
|
|
||||||
简体中文
|
|
||||||
</li>
|
|
||||||
<li role="option" data-value="zh-Hant" aria-selected="false">
|
|
||||||
繁體中文
|
|
||||||
</li>
|
|
||||||
<li role="option" data-value="ja" aria-selected="false">日本語</li>
|
|
||||||
<li role="option" data-value="ko" aria-selected="false">한국어</li>
|
|
||||||
<li role="option" data-value="en" aria-selected="false">English</li>
|
|
||||||
<li role="option" data-value="wenyan" aria-selected="false">
|
|
||||||
文言文
|
|
||||||
</li>
|
|
||||||
<li role="option" data-value="mars" aria-selected="false">
|
|
||||||
非→主←流
|
|
||||||
</li>
|
|
||||||
<li role="option" data-value="garbled" aria-selected="false">GBK</li>
|
|
||||||
<li role="option" data-value="bin" aria-selected="false">计算机语</li>
|
|
||||||
<li role="option" data-value="meow" aria-selected="false">喵喵喵</li>
|
|
||||||
<li role="option" data-value="emoji" aria-selected="false">😅😅😅</li>
|
|
||||||
</ul>
|
|
||||||
</label>
|
|
||||||
<button
|
|
||||||
class="theme-toggle"
|
|
||||||
id="themeToggle"
|
|
||||||
aria-label="切换主题"
|
|
||||||
title="切换深色/浅色模式"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src="/icons/moon.svg"
|
|
||||||
alt="月亮"
|
|
||||||
class="theme-icon theme-icon-moon"
|
|
||||||
/>
|
|
||||||
<img
|
|
||||||
src="/icons/sun.svg"
|
|
||||||
alt="太阳"
|
|
||||||
class="theme-icon theme-icon-sun"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<main class="main">
|
<main class="main">
|
||||||
<h1 class="title" data-i18n="appTitle">
|
<h1 class="title gradient">♥️ 徐越的在线学习平台 ♥️</h1>
|
||||||
物联网专业の在线学习平台<span
|
<h2 class="subtitle"></h2>
|
||||||
class="title-cursor"
|
|
||||||
aria-hidden="true"
|
|
||||||
></span>
|
|
||||||
</h1>
|
|
||||||
<div class="grid" id="sites"></div>
|
<div class="grid" id="sites"></div>
|
||||||
</main>
|
</main>
|
||||||
<div class="beian">
|
<div class="beian">
|
||||||
<a href="https://beian.miit.gov.cn" target="_blank" rel="noreferrer">
|
<a href="https://beian.miit.gov.cn" target="_blank" rel="noreferrer">
|
||||||
<span data-i18n="beianIcp">浙ICP备2023044109号</span>
|
浙ICP备2023044109号
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="https://beian.mps.gov.cn/#/query/webSearch?code=33100402331786"
|
|
||||||
rel="noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<span data-i18n="beianMps">浙公网安备33100402331786号</span>
|
|
||||||
</a>
|
</a>
|
||||||
|
<div>
|
||||||
|
<img src="/备案图标.png" alt="备案图标" />
|
||||||
|
<a
|
||||||
|
href="https://beian.mps.gov.cn/#/query/webSearch?code=33100402331786"
|
||||||
|
rel="noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
浙公网安备33100402331786号
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
102
main.js
@@ -1,3 +1,101 @@
|
|||||||
import { initApp } from "./app.js"
|
import "./style.css"
|
||||||
|
|
||||||
initApp()
|
const pins = [
|
||||||
|
// {
|
||||||
|
// url: "https://code.xuyue.cc?query=30",
|
||||||
|
// description: "示例代码",
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// url: "https://lc.xuyue.cc/public-forms/do0zqi0xlpe",
|
||||||
|
// description: "数据记录单",
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// url: "https://lc.xuyue.cc/",
|
||||||
|
// description: "账号 stu 密码 123456",
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// url: "https://play.xuyue.cc/",
|
||||||
|
// description: "选择自己的名字做小测试",
|
||||||
|
// },
|
||||||
|
]
|
||||||
|
|
||||||
|
const sites = [
|
||||||
|
{
|
||||||
|
url: import.meta.env.VITE_OJ,
|
||||||
|
title: "判题狗",
|
||||||
|
description: "在线判题网站",
|
||||||
|
icon: "noto--dog-face.svg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: import.meta.env.VITE_CODE,
|
||||||
|
title: "自测猫",
|
||||||
|
description: "代码运行网站",
|
||||||
|
icon: "noto--cat-face.svg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: import.meta.env.VITE_WEB,
|
||||||
|
title: "哈基米",
|
||||||
|
description: "Web 前端开发",
|
||||||
|
icon: "noto--honeybee.svg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: import.meta.env.VITE_SHUATI,
|
||||||
|
title: "刷题鸭",
|
||||||
|
description: "梁老师的刷题网站",
|
||||||
|
icon: "noto--paintbrush.svg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: import.meta.env.VITE_PLAY,
|
||||||
|
title: "限时鸭",
|
||||||
|
description: "用来练习基本的代码格式",
|
||||||
|
icon: "noto--duck.svg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: import.meta.env.VITE_BOOK,
|
||||||
|
title: "编程书",
|
||||||
|
description: "编程和计算机相关知识汇总",
|
||||||
|
icon: "noto--bookmark-tabs.svg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: import.meta.env.VITE_HUABU,
|
||||||
|
title: "白板",
|
||||||
|
description: "在线板书",
|
||||||
|
icon: "noto--artist-palette.svg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: import.meta.env.VITE_PPT,
|
||||||
|
title: "Python PPT",
|
||||||
|
description: "Python 第一学期上课用",
|
||||||
|
icon: "material-icon-theme--python.svg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: import.meta.env.VITE_PY,
|
||||||
|
title: "Python 项目",
|
||||||
|
description: "Python 第二学期上课用",
|
||||||
|
icon: "material-icon-theme--folder-python-open.svg",
|
||||||
|
},
|
||||||
|
].filter((i) => !!i.url)
|
||||||
|
|
||||||
|
const item = (site) => `
|
||||||
|
<a href="${site.url}" target="_blank" class="card">
|
||||||
|
<div class="title-icon">
|
||||||
|
${site.icon ? `<img src="/icons/${site.icon}" alt="${site.title}" class="icon" />` : ""}
|
||||||
|
<h2>${site.title} →</h2>
|
||||||
|
</div>
|
||||||
|
<p>${site.description}</p>
|
||||||
|
<p class="single">${site.url}</p>
|
||||||
|
</a>
|
||||||
|
`
|
||||||
|
|
||||||
|
const pin = (site) => `
|
||||||
|
<a href="${site.url}" target="_blank" class="card pin">
|
||||||
|
<p>${site.description}</p>
|
||||||
|
</a>
|
||||||
|
`
|
||||||
|
|
||||||
|
if (pins.length) {
|
||||||
|
document.querySelector(".subtitle").innerHTML = "置顶内容"
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelector("#sites").innerHTML =
|
||||||
|
pins.map(pin).join("") + sites.map(item).join("")
|
||||||
|
|||||||
1615
package-lock.json
generated
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "hyyz-home",
|
"name": "hyyz-home",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2.0.0",
|
"version": "1.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "vite --mode=production",
|
"start": "vite --mode=production",
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
"fmt": "prettier --write *.js style.css index.html"
|
"fmt": "prettier --write *.js style.css index.html"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"prettier": "^3.8.2",
|
"prettier": "^3.6.2",
|
||||||
"vite": "^8.0.8"
|
"vite": "^7.1.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><g fill="none"><path fill="#fcd53f" d="M23.41 5.632c.5 2.04.56 4.26.02 6.56c-1.26 5.33-5.64 9.51-11.02 10.48c-2.91.53-5.68.13-8.09-.92c-.56-.25-1.09.39-.8.93c2.65 4.88 8.11 8 14.22 7.19c6.23-.83 11.22-5.91 11.97-12.15c.6-5.18-1.6-9.86-5.28-12.75c-.47-.36-1.16.08-1.02.66"/><path fill="#f9c23c" d="M27.87 12.562a1.57 1.57 0 1 1-3.14 0a1.57 1.57 0 0 1 3.14 0m-12.92 12.88a1.57 1.57 0 1 1-3.14 0a1.57 1.57 0 0 1 3.14 0m11.85-6.47a.99.99 0 1 0 0-1.98a.99.99 0 0 0 0 1.98m-2 3.01a3 3 0 1 1-6 0a3 3 0 0 1 6 0"/></g></svg>
|
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 602 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128"><radialGradient id="SVG6bAUKdJt" cx="68.884" cy="124.296" r="70.587" gradientTransform="matrix(-1 -.00434 -.00713 1.6408 131.986 -79.345)" gradientUnits="userSpaceOnUse"><stop offset=".314" stop-color="#ff9800"/><stop offset=".662" stop-color="#ff6d00"/><stop offset=".972" stop-color="#f44336"/></radialGradient><path fill="url(#SVG6bAUKdJt)" d="M35.56 40.73c-.57 6.08-.97 16.84 2.62 21.42c0 0-1.69-11.82 13.46-26.65c6.1-5.97 7.51-14.09 5.38-20.18c-1.21-3.45-3.42-6.3-5.34-8.29c-1.12-1.17-.26-3.1 1.37-3.03c9.86.44 25.84 3.18 32.63 20.22c2.98 7.48 3.2 15.21 1.78 23.07c-.9 5.02-4.1 16.18 3.2 17.55c5.21.98 7.73-3.16 8.86-6.14c.47-1.24 2.1-1.55 2.98-.56c8.8 10.01 9.55 21.8 7.73 31.95c-3.52 19.62-23.39 33.9-43.13 33.9c-24.66 0-44.29-14.11-49.38-39.65c-2.05-10.31-1.01-30.71 14.89-45.11c1.18-1.08 3.11-.12 2.95 1.5"/><radialGradient id="SVG5R9TgbPb" cx="64.921" cy="54.062" r="73.86" gradientTransform="matrix(-.0101 .9999 .7525 .0076 26.154 -11.267)" gradientUnits="userSpaceOnUse"><stop offset=".214" stop-color="#fff176"/><stop offset=".328" stop-color="#fff27d"/><stop offset=".487" stop-color="#fff48f"/><stop offset=".672" stop-color="#fff7ad"/><stop offset=".793" stop-color="#fff9c4"/><stop offset=".822" stop-color="#fff8bd" stop-opacity="0.804"/><stop offset=".863" stop-color="#fff6ab" stop-opacity="0.529"/><stop offset=".91" stop-color="#fff38d" stop-opacity="0.209"/><stop offset=".941" stop-color="#fff176" stop-opacity="0"/></radialGradient><path fill="url(#SVG5R9TgbPb)" d="M76.11 77.42c-9.09-11.7-5.02-25.05-2.79-30.37c.3-.7-.5-1.36-1.13-.93c-3.91 2.66-11.92 8.92-15.65 17.73c-5.05 11.91-4.69 17.74-1.7 24.86c1.8 4.29-.29 5.2-1.34 5.36c-1.02.16-1.96-.52-2.71-1.23a16.1 16.1 0 0 1-4.44-7.6c-.16-.62-.97-.79-1.34-.28c-2.8 3.87-4.25 10.08-4.32 14.47C40.47 113 51.68 124 65.24 124c17.09 0 29.54-18.9 19.72-34.7c-2.85-4.6-5.53-7.61-8.85-11.88"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.9 KiB |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128"><path fill="#c33" d="M64 4C30.86 4 4 30.86 4 64s26.86 60 60 60s60-26.86 60-60S97.14 4 64 4m50 60a49.37 49.37 0 0 1-11.3 31.6L32.4 25.3A49.37 49.37 0 0 1 64 14c27.61 0 50 22.39 50 50M14 64a49.37 49.37 0 0 1 11.3-31.6l70.3 70.3A49.37 49.37 0 0 1 64 114c-27.61 0-50-22.39-50-50"/><circle cx="60.1" cy="63.1" r="56.1" fill="#f44336"/><path fill="#fff" d="M95.6 102.7A49.37 49.37 0 0 1 64 114c-27.61 0-50-22.39-50-50a49.37 49.37 0 0 1 11.3-31.6l7.1-7.1A49.37 49.37 0 0 1 64 14c27.61 0 50 22.39 50 50a49.37 49.37 0 0 1-11.3 31.6"/><path fill="#231f20" d="M95.6 102.7A49.37 49.37 0 0 1 64 114c-27.61 0-50-22.39-50-50a49.37 49.37 0 0 1 11.3-31.6l7.1-7.1A49.37 49.37 0 0 1 64 14c27.61 0 50 22.39 50 50a49.37 49.37 0 0 1-11.3 31.6"/><path fill="#414042" d="M96.4 103.2c-20.49 16.74-50.66 13.7-67.4-6.79c-14.41-17.63-14.41-42.97 0-60.61l6.8-6.8a47.3 47.3 0 0 1 30.3-10.8c26.45 0 47.9 21.44 47.9 47.9a47.93 47.93 0 0 1-10.8 30.3"/><path fill="#fafafa" d="M93 58.3c.04 2.74-.77 5.43-2.3 7.7a15.3 15.3 0 0 1-6.2 5.3c2.88 1.2 5.37 3.18 7.2 5.7c1.79 2.47 2.74 5.45 2.7 8.5c.19 4.56-1.68 8.97-5.1 12c-3.4 3-7.9 4.5-13.4 4.5s-10.1-1.5-13.4-4.5a15.19 15.19 0 0 1-5.1-12c-.01-3.03.89-5.99 2.6-8.5c1.79-2.55 4.25-4.55 7.1-5.8a15.04 15.04 0 0 1-6.1-5.3a13.57 13.57 0 0 1-2.2-7.7c0-4.9 1.6-8.8 4.7-11.7s7.3-4.3 12.4-4.3s9.2 1.4 12.4 4.3A15.67 15.67 0 0 1 93 58.3m-6.1 27.1c.13-2.98-1-5.88-3.1-8c-2.18-2.07-5.1-3.18-8.1-3.1c-5.69-.39-10.61 3.91-11 9.59q-.045.705 0 1.41c-.14 2.89.91 5.71 2.9 7.8c1.9 1.9 4.7 2.8 8.2 2.8S82 95 83.9 93c2-1.7 3-4.3 3-7.6m-11.1-37c-2.61-.13-5.15.85-7 2.7a9.8 9.8 0 0 0-2.7 7.3c-.11 2.67.87 5.26 2.7 7.2a9.48 9.48 0 0 0 7.1 2.7c2.64.13 5.21-.85 7.1-2.7a9.72 9.72 0 0 0 2.7-7.2c.14-2.69-.88-5.31-2.8-7.2a10.05 10.05 0 0 0-7.1-2.8M49.1 43l-14.3 5.4c-.68.23-1.13.88-1.1 1.6v3.2c0 .94.77 1.71 1.71 1.71c.2 0 .4-.04.59-.11l4.9-1.8a1.7 1.7 0 0 1 2.19 1.01c.07.19.1.39.11.59v45.1c.03.93.77 1.67 1.7 1.7H49c.93-.03 1.67-.77 1.7-1.7V44.1c0-.67-.55-1.22-1.23-1.21c-.06 0-.12 0-.17.01zm35.5-8.3h-1.1a.675.675 0 0 0-.7.66v3.04c.01.38-.28.69-.66.7h-.54a.675.675 0 0 1-.7-.66V28.3c-.01-.38.28-.69.66-.7h3.34c1.11-.07 2.2.25 3.1.9c.74.66 1.14 1.61 1.1 2.6c.02.71-.19 1.41-.6 2c-.26.38-.6.68-1 .9c-.3.24-.42.64-.3 1l2.3 4.2v.1h-1.7a1 1 0 0 1-.7-.4l-1.9-3.7c-.1-.4-.3-.5-.6-.5m-1.8-2.4c-.01.38.28.69.66.7h1.34c.57.02 1.14-.16 1.6-.5c.41-.34.64-.86.6-1.4c.02-.51-.16-1.01-.5-1.4c-.46-.34-1.03-.52-1.6-.5h-1.4a.675.675 0 0 0-.7.66zM77.3 34h-3.9a.43.43 0 0 0-.4.4v2.7c.01.21.19.39.4.4H78c.21.01.39.19.4.4v.8a.43.43 0 0 1-.4.4h-6.6a.43.43 0 0 1-.4-.4V28.1a.43.43 0 0 1 .4-.4H78c.21.01.39.19.4.4v.8a.43.43 0 0 1-.4.4h-4.6a.43.43 0 0 0-.4.4V32c.01.21.19.39.4.4h3.9c.21.01.39.19.4.4v.8c.02.2-.13.38-.34.4zm-16.8 4.7V28.1a.43.43 0 0 1 .4-.4h2.9c.95-.03 1.88.22 2.7.7c.79.42 1.42 1.09 1.8 1.9c.41.88.61 1.83.6 2.8v.6c.03.97-.18 1.93-.6 2.8c-.41.79-1.03 1.45-1.8 1.9c-.83.44-1.76.68-2.7.7h-2.9a.43.43 0 0 1-.4-.4m2-9v7.4c.01.21.19.39.4.4h.9c.91.05 1.79-.32 2.4-1c.62-.8.9-1.8.8-2.8v-.6a4.5 4.5 0 0 0-.8-2.8a2.93 2.93 0 0 0-2.3-1h-1a.43.43 0 0 0-.4.4m-16-2c.21.01.39.19.4.4v7.2c.05 1.1-.39 2.16-1.2 2.9c-.86.74-1.96 1.14-3.1 1.1c-1.12.07-2.23-.29-3.1-1a3.62 3.62 0 0 1-1.1-2.9v-7.2a.43.43 0 0 1 .4-.4H40c.21.01.39.19.4.4v7.2c-.05.63.17 1.25.6 1.7c.46.42 1.08.63 1.7.6c1.15.13 2.18-.69 2.31-1.84c.02-.19.02-.37-.01-.56v-7.1a.43.43 0 0 1 .4-.4c0-.1 1.1-.1 1.1-.1m11 11.4h-1.3c-.1 0-.3-.1-.3-.2l-4.2-6.7c-.2-.3-.8-.2-.8.2v6.2a.43.43 0 0 1-.4.4h-1.2a.43.43 0 0 1-.4-.4V28.1a.43.43 0 0 1 .4-.4h1.3c.1 0 .3.1.3.2l4.2 6.7c.2.3.8.2.8-.2v-6.3a.43.43 0 0 1 .4-.4h1.1c.21.01.39.19.4.4v10.5c.08.17.01.38-.17.47c-.04.02-.08.03-.13.03"/><path fill="#231f20" d="M23.4 35.6L95 102.1l2-1.9l-66.4-66.8" opacity="0.8"/><path fill="#f44336" d="M103.9 96.8L25.3 18.9L18.2 26l78.6 77.9"/><path fill="#ff8a80" d="M45 10.9c1.7-.4 4.2-1.6 5.9-1.1c1.09.3 1.75 1.4 1.5 2.5a2.61 2.61 0 0 1-2 1.5c-3.9.97-7.68 2.35-11.3 4.1A52 52 0 0 0 21 33.7c-1.9 2.7-3.4 5.5-5.8 7.8c-.21.23-.49.37-.8.4a.74.74 0 0 1-.5-.1c-1.2-.5-1.4-1.1-1.2-2.3c.18-1.01.52-1.99 1-2.9q1.515-2.64 3.3-5.1c1.89-2.91 4.11-5.59 6.6-8c6.8-6.3 15.6-11.1 21.4-12.6"/><path fill="#c33" d="m32.4 25.3l-2 2l71.1 71.1l1.8-2.2z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 4.1 KiB |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128"><path fill="#464c4f" d="M19.64 35.33c.09-.26-.09-4.82 2.45-8.41s5.87-4.12 8.33-4.56c2.98-.53 10.17-1.4 11.31 1.05s.26 3.77 2.1 4.47s-1.49 4.82-1.49 4.82zm88.25-.09s.72-4.43-1.81-7.42c-3.8-4.51-9.75-5.97-15.38-5.97c-1.81 0-3.98.35-4.68 2.51c-.4 1.25-.68 2.77-1.56 2.94c-.87.18 17.73 5.93 23.43 7.94"/><path fill="#5e6268" d="M39.04 81.29c-2.99 2.32-6.96 18.32-13.17 22.55s-20.28 1.97-21.34-6.66c-.93-7.61.76-23.61 5-39.96s7.5-24.45 17.41-27.1c7.95-2.13 23.53-3.63 38.66-3.48c15.14.15 28.39.15 36.72 3.33c7.47 2.85 12.56 10.6 16.05 25.73c3.48 15.14 6.17 33.34 5.75 39.36c-.61 8.78-13.02 14.38-22.25 7.57c-7.35-5.42-8.78-19.22-12.56-21.19s-47.55-2.27-50.27-.15"/><path fill="#9e9e9e" d="M93.25 77.17c-.72.9.94 2.24 2.12 5.17s4.22 12.63 7.17 15.34c3.68 3.37 6.55 2.74 7.11 1.68s-2.62-3.8-6.36-9.91s-8.54-14.15-10.04-12.28m-82.73-2.49c-1.11.2-4.05 14.96-1.87 21.2c1.82 5.2 8.79 5.49 11.41 4.74c5.22-1.49 6.86-6.55 5.67-7.11c-1.18-.56-5.32 3.4-9.23 1.56c-4.36-2.06-4.3-7.86-4.86-13.72c-.5-5.28-.06-6.86-1.12-6.67m27.49-32.73c-.41 0-4.01-.02-4.01-.02l.02-4.35s.08-3.51-3.68-3.43c-3.37.07-3.3 2.88-3.3 3.43s-.02 4.32-.02 4.32s-3.82-.04-4.53-.02s-3.37.06-3.37 3.49c0 3.24 2.75 3.47 3.37 3.49s4.51.02 4.51.02s-.03 3.63-.02 4.22s.12 3.37 3.49 3.37c3.68 0 3.49-3.37 3.49-3.37l.02-4.19s3.44.03 4.04.02c.86-.02 3.39-.25 3.43-3.68c.03-3.39-3.02-3.3-3.44-3.3"/><circle cx="48.4" cy="62.42" r="8.54" fill="#afafaf"/><circle cx="77.75" cy="62.55" r="8.54" fill="#afafaf"/><circle cx="48.39" cy="62.21" r="5.71" fill="#c8c8c8"/><circle cx="77.75" cy="62.4" r="5.71" fill="#c8c8c8"/><circle cx="85.82" cy="45.67" r="4.6" fill="#2086fa"/><circle cx="94.94" cy="54.48" r="4.6" fill="#06ac48"/><circle cx="104.12" cy="46.4" r="4.6" fill="#f72e26"/><circle cx="95.02" cy="37.01" r="4.6" fill="#fdb700"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.8 KiB |
@@ -1,2 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><g fill="none"><path fill="#ff822d" d="M13.638 3.202a2.936 2.936 0 0 1 4.724 0a2.94 2.94 0 0 0 3.25 1.055a2.936 2.936 0 0 1 3.822 2.778a2.94 2.94 0 0 0 2.008 2.763a2.936 2.936 0 0 1 1.46 4.494a2.94 2.94 0 0 0 0 3.416a2.936 2.936 0 0 1-1.46 4.494a2.94 2.94 0 0 0-2.008 2.763a2.936 2.936 0 0 1-3.823 2.778a2.94 2.94 0 0 0-3.249 1.055a2.936 2.936 0 0 1-4.724 0a2.94 2.94 0 0 0-3.25-1.055a2.936 2.936 0 0 1-3.822-2.778a2.94 2.94 0 0 0-2.008-2.763a2.936 2.936 0 0 1-1.46-4.494a2.94 2.94 0 0 0 0-3.416a2.936 2.936 0 0 1 1.46-4.494a2.94 2.94 0 0 0 2.008-2.763a2.936 2.936 0 0 1 3.823-2.778a2.94 2.94 0 0 0 3.249-1.055"/><path fill="#fcd53f" d="M25.062 21.232c-2.89 5.005-9.29 6.72-14.294 3.83s-6.72-9.29-3.83-14.294s9.29-6.72 14.294-3.83s6.72 9.29 3.83 14.294"/></g></svg>
|
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 852 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 36 36"><path fill="#c1694f" d="M35.627 10.826L.373 16.56v9.722h.004c-.012.149.129.295.443.473c.727.412 9.835 5.286 10.553 5.639c.895.439 1.441.493 2.206.112c.626-.312 19.236-10.173 20.932-11.074c.863-.459 1.146-.711 1.099-.919h.018z"/><path fill="#a0041e" d="M.373 16.56v9.722h.004c-.012.149.129.295.443.473c.727.412 9.835 5.286 10.553 5.639c.432.212.781.329 1.114.356V16.56z"/><path fill="#d99e82" d="M22.224 4.682c1.076-.558 1.113-.628 2.249-.024s9.846 5.248 10.487 5.589c.814.434 1.112.591-.449 1.421c-1.696.902-20.306 10.763-20.932 11.074c-.765.381-1.311.327-2.206-.112c-.718-.352-9.826-5.226-10.553-5.639c-.82-.465-.482-.706.769-1.37z"/><ellipse cx="11.189" cy="17.191" fill="#c1694f" rx="3.679" ry="1.84"/><path fill="#a0041e" d="M11.642 16.734c-1.569-.516-3.352-.369-4.108.339c-.005.04-.024.078-.024.118c0 1.016 1.647 1.84 3.679 1.84c1.063 0 2.013-.229 2.684-.589c-.333-.692-1.086-1.332-2.231-1.708"/><ellipse cx="18" cy="13.639" fill="#c1694f" rx="3.679" ry="1.84"/><path fill="#a0041e" d="M18.453 13.182c-1.569-.516-3.352-.369-4.108.339c-.005.04-.024.078-.024.118c0 1.016 1.647 1.84 3.679 1.84c1.063 0 2.013-.229 2.684-.589c-.333-.692-1.086-1.332-2.231-1.708"/><ellipse cx="24.811" cy="10.087" fill="#c1694f" rx="3.679" ry="1.84"/><path fill="#a0041e" d="M25.264 9.63c-1.569-.516-3.352-.369-4.108.339c-.005.04-.024.078-.024.118c0 1.016 1.647 1.84 3.679 1.84c1.063 0 2.012-.229 2.684-.589c-.333-.692-1.086-1.332-2.231-1.708"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 158 KiB |
BIN
public/备案图标.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
33
render.js
@@ -1,33 +0,0 @@
|
|||||||
import { getLocalizedText } from "./i18n.js"
|
|
||||||
|
|
||||||
const renderItem = (site, language) => {
|
|
||||||
const title = getLocalizedText(site.title, language)
|
|
||||||
const description = getLocalizedText(site.description, language)
|
|
||||||
const iconName = language === "meow" ? "noto--cat-face.svg" : site.icon
|
|
||||||
return `
|
|
||||||
<a href="${site.url}" target="_blank" class="card">
|
|
||||||
<div class="title-icon">
|
|
||||||
${iconName ? `<img src="/icons/${iconName}" alt="${title}" class="icon" />` : ""}
|
|
||||||
<h2>${title} →</h2>
|
|
||||||
</div>
|
|
||||||
<p>${description}</p>
|
|
||||||
<p class="single">${site.url}</p>
|
|
||||||
</a>
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
const renderPin = (site, language) => {
|
|
||||||
const description = getLocalizedText(site.description, language)
|
|
||||||
return `
|
|
||||||
<a href="${site.url}" target="_blank" class="card pin">
|
|
||||||
<p>${description}</p>
|
|
||||||
</a>
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
export function renderSites({ container, sites, pins, language }) {
|
|
||||||
if (!container) return
|
|
||||||
container.innerHTML =
|
|
||||||
pins.map((site) => renderPin(site, language)).join("") +
|
|
||||||
sites.map((site) => renderItem(site, language)).join("")
|
|
||||||
}
|
|
||||||
131
theme.js
@@ -1,131 +0,0 @@
|
|||||||
const DESIGN_THEMES = [
|
|
||||||
"fluent",
|
|
||||||
"material-you",
|
|
||||||
"terminal",
|
|
||||||
"cyberpunk",
|
|
||||||
"nord",
|
|
||||||
"animal-crossing",
|
|
||||||
"minecraft",
|
|
||||||
"persona-5",
|
|
||||||
]
|
|
||||||
const FORCED_DARK_DESIGN_THEMES = new Set(["terminal", "cyberpunk"])
|
|
||||||
const THEME_BEFORE_FORCED_KEY = "themeBeforeForcedDark"
|
|
||||||
|
|
||||||
export function getInitialTheme() {
|
|
||||||
const savedTheme = localStorage.getItem("theme")
|
|
||||||
if (savedTheme) {
|
|
||||||
return savedTheme
|
|
||||||
}
|
|
||||||
return window.matchMedia("(prefers-color-scheme: dark)").matches
|
|
||||||
? "dark"
|
|
||||||
: "light"
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setTheme(theme) {
|
|
||||||
document.documentElement.setAttribute("data-theme", theme)
|
|
||||||
localStorage.setItem("theme", theme)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toggleTheme() {
|
|
||||||
const designTheme =
|
|
||||||
document.documentElement.getAttribute("data-design-theme") || "fluent"
|
|
||||||
if (FORCED_DARK_DESIGN_THEMES.has(designTheme)) return
|
|
||||||
const currentTheme =
|
|
||||||
document.documentElement.getAttribute("data-theme") || "light"
|
|
||||||
const newTheme = currentTheme === "dark" ? "light" : "dark"
|
|
||||||
setTheme(newTheme)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getInitialDesignTheme() {
|
|
||||||
const savedDesignTheme = localStorage.getItem("designTheme")
|
|
||||||
if (savedDesignTheme && DESIGN_THEMES.includes(savedDesignTheme)) {
|
|
||||||
return savedDesignTheme
|
|
||||||
}
|
|
||||||
return "fluent"
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setDesignTheme(designTheme, themeToggle) {
|
|
||||||
const safeDesignTheme = DESIGN_THEMES.includes(designTheme)
|
|
||||||
? designTheme
|
|
||||||
: "fluent"
|
|
||||||
const previousDesignTheme =
|
|
||||||
document.documentElement.getAttribute("data-design-theme") || "fluent"
|
|
||||||
document.documentElement.setAttribute("data-design-theme", safeDesignTheme)
|
|
||||||
localStorage.setItem("designTheme", safeDesignTheme)
|
|
||||||
|
|
||||||
const willForceDark = FORCED_DARK_DESIGN_THEMES.has(safeDesignTheme)
|
|
||||||
const didForceDark = FORCED_DARK_DESIGN_THEMES.has(previousDesignTheme)
|
|
||||||
|
|
||||||
if (willForceDark) {
|
|
||||||
if (!didForceDark) {
|
|
||||||
const currentTheme =
|
|
||||||
document.documentElement.getAttribute("data-theme") || "light"
|
|
||||||
localStorage.setItem(THEME_BEFORE_FORCED_KEY, currentTheme)
|
|
||||||
}
|
|
||||||
setTheme("dark")
|
|
||||||
} else if (didForceDark) {
|
|
||||||
const restoreTheme =
|
|
||||||
localStorage.getItem(THEME_BEFORE_FORCED_KEY) ||
|
|
||||||
localStorage.getItem("themeBeforeTerminal")
|
|
||||||
if (restoreTheme === "dark" || restoreTheme === "light") {
|
|
||||||
setTheme(restoreTheme)
|
|
||||||
}
|
|
||||||
localStorage.removeItem(THEME_BEFORE_FORCED_KEY)
|
|
||||||
localStorage.removeItem("themeBeforeTerminal")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (themeToggle) {
|
|
||||||
themeToggle.disabled = willForceDark
|
|
||||||
themeToggle.setAttribute("aria-disabled", willForceDark ? "true" : "false")
|
|
||||||
themeToggle.tabIndex = willForceDark ? -1 : 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setSelectedDesignThemeUI({
|
|
||||||
designThemeList,
|
|
||||||
designThemeButton,
|
|
||||||
designTheme,
|
|
||||||
getLabel,
|
|
||||||
}) {
|
|
||||||
if (!designThemeList) return
|
|
||||||
const options = [...designThemeList.querySelectorAll('[role="option"]')]
|
|
||||||
options.forEach((el) => {
|
|
||||||
el.setAttribute(
|
|
||||||
"aria-selected",
|
|
||||||
el.getAttribute("data-value") === designTheme ? "true" : "false",
|
|
||||||
)
|
|
||||||
})
|
|
||||||
if (designThemeButton) {
|
|
||||||
designThemeButton.textContent = getLabel(designTheme)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setDesignThemeMenuOpen({
|
|
||||||
designThemeButton,
|
|
||||||
designThemeList,
|
|
||||||
open,
|
|
||||||
}) {
|
|
||||||
if (!designThemeButton || !designThemeList) return
|
|
||||||
designThemeButton.setAttribute("aria-expanded", open ? "true" : "false")
|
|
||||||
designThemeList.hidden = !open
|
|
||||||
if (open) {
|
|
||||||
designThemeList.focus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCurrentDesignTheme() {
|
|
||||||
return document.documentElement.getAttribute("data-design-theme") || "fluent"
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateDesignThemeOptions({
|
|
||||||
designThemeList,
|
|
||||||
getLabel,
|
|
||||||
language,
|
|
||||||
}) {
|
|
||||||
if (!designThemeList) return
|
|
||||||
const options = [...designThemeList.querySelectorAll('[role="option"]')]
|
|
||||||
options.forEach((el) => {
|
|
||||||
const value = el.getAttribute("data-value")
|
|
||||||
el.textContent = getLabel(value, language)
|
|
||||||
})
|
|
||||||
}
|
|
||||||