Skip to content

Commit 74c55e2

Browse files
committed
add light/dark theme toggle with persistence for UUID Generator
1 parent 5d7baab commit 74c55e2

File tree

3 files changed

+247
-0
lines changed

3 files changed

+247
-0
lines changed

projects/UUID/index.html

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<!doctype html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="utf-8" />
6+
<meta name="viewport" content="width=device-width,initial-scale=1" />
7+
<title>Quick UUID Generator</title>
8+
<link rel="stylesheet" href="styles.css">
9+
</head>
10+
11+
<body>
12+
<button id="themeToggle" class="theme-toggle" title="Toggle Theme">🌙</button>
13+
14+
<main class="card" role="main">
15+
<h1>Quick UUID Generator</h1><br>
16+
<p>Click the button to generate a Version 4 UUID.</p>
17+
18+
<div class="controls">
19+
<button id="generate">Generate UUID</button>
20+
<div id="uuid" aria-live="polite"></div>
21+
<button id="copy" class="small-btn" title="Copy UUID">Copy</button>
22+
</div>
23+
24+
<div class="hint">Tip: Click <em>Copy</em> or click the UUID itself to copy it to your clipboard.</div>
25+
</main>
26+
27+
<script src="main.js"></script>
28+
</body>
29+
30+
</html>

projects/UUID/main.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
const btn = document.getElementById('generate');
2+
const out = document.getElementById('uuid');
3+
const copyBtn = document.getElementById('copy');
4+
const themeToggle = document.getElementById('themeToggle');
5+
6+
// Theme toggle with persistence
7+
function setTheme(theme) {
8+
document.documentElement.setAttribute('data-theme', theme);
9+
localStorage.setItem('theme', theme);
10+
themeToggle.textContent = theme === 'dark' ? '🌙' : '☀️';
11+
}
12+
13+
const savedTheme = localStorage.getItem('theme') || 'dark';
14+
setTheme(savedTheme);
15+
16+
themeToggle.addEventListener('click', () => {
17+
const current = document.documentElement.getAttribute('data-theme');
18+
setTheme(current === 'dark' ? 'light' : 'dark');
19+
});
20+
21+
// UUID generation (v4)
22+
function uuidv4_fallback() {
23+
const bytes = crypto.getRandomValues(new Uint8Array(16));
24+
bytes[6] = (bytes[6] & 0x0f) | 0x40;
25+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
26+
const hex = Array.from(bytes, b => b.toString(16).padStart(2, '0'));
27+
return `${hex.slice(0, 4).join('')}-${hex.slice(4, 6).join('')}-${hex.slice(6, 8).join('')}-${hex.slice(8, 10).join('')}-${hex.slice(10, 16).join('')}`;
28+
}
29+
30+
function generateUUID() {
31+
try {
32+
if (typeof crypto.randomUUID === 'function') return crypto.randomUUID();
33+
} catch (e) { }
34+
return uuidv4_fallback();
35+
}
36+
37+
function showCopiedToast() {
38+
const t = document.createElement('div');
39+
t.className = 'copied';
40+
t.textContent = 'Copied to clipboard';
41+
document.body.appendChild(t);
42+
setTimeout(() => t.remove(), 1400);
43+
}
44+
45+
btn.addEventListener('click', () => {
46+
const id = generateUUID();
47+
out.textContent = id;
48+
});
49+
50+
copyBtn.addEventListener('click', async () => {
51+
const text = out.textContent;
52+
if (!text || text === '—') return;
53+
try {
54+
await navigator.clipboard.writeText(text);
55+
showCopiedToast();
56+
} catch (e) {
57+
const ta = document.createElement('textarea');
58+
ta.value = text;
59+
document.body.appendChild(ta);
60+
ta.select();
61+
try { document.execCommand('copy'); showCopiedToast(); }
62+
catch (err) { alert('Copy failed — select the UUID and press Ctrl/Cmd+C'); }
63+
ta.remove();
64+
}
65+
});
66+
67+
out.addEventListener('click', async () => {
68+
const text = out.textContent;
69+
if (!text || text === '—') return;
70+
try { await navigator.clipboard.writeText(text); showCopiedToast(); } catch (e) { }
71+
});

projects/UUID/styles.css

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
:root {
2+
--bg: linear-gradient(180deg, #071025 0%, #0b1220 100%);
3+
--card-bg: linear-gradient(180deg, rgba(255, 255, 255, 0.02), rgba(255, 255, 255, 0.01));
4+
--accent: #6ee7b7;
5+
--accent-dark: #18b38a;
6+
--muted: #94a3b8;
7+
--text: #e6eef8;
8+
--uuid-bg: rgba(255, 255, 255, 0.05);
9+
--border: rgba(255, 255, 255, 0.08);
10+
}
11+
12+
[data-theme="light"] {
13+
--bg: linear-gradient(180deg, #fdfdfd 0%, #f4f7fb 100%);
14+
--card-bg: linear-gradient(180deg, rgba(255, 255, 255, 0.95), rgba(255, 255, 255, 0.9));
15+
--accent: #34d399;
16+
--accent-dark: #059669;
17+
--muted: #64748b;
18+
--text: #0f172a;
19+
--uuid-bg: rgba(240, 245, 250, 0.9);
20+
--border: rgba(0, 0, 0, 0.08);
21+
}
22+
23+
html,
24+
body {
25+
height: 100%;
26+
margin: 0;
27+
font-family: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;
28+
transition: background 0.3s ease, color 0.3s ease;
29+
}
30+
31+
body {
32+
display: grid;
33+
place-items: center;
34+
background: var(--bg);
35+
color: var(--text);
36+
}
37+
38+
.card {
39+
width: min(720px, 94%);
40+
background: var(--card-bg);
41+
border-radius: 16px;
42+
padding: 32px;
43+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
44+
border: 1px solid var(--border);
45+
backdrop-filter: blur(8px);
46+
transition: background 0.3s ease, box-shadow 0.3s ease;
47+
}
48+
49+
h1 {
50+
margin: 0 0 8px;
51+
font-size: 22px;
52+
text-align: center;
53+
}
54+
55+
p {
56+
margin: 0 0 18px;
57+
color: var(--muted);
58+
}
59+
60+
.controls {
61+
display: flex;
62+
gap: 12px;
63+
align-items: center;
64+
flex-wrap: wrap;
65+
}
66+
67+
button {
68+
appearance: none;
69+
border: 0;
70+
padding: 10px 16px;
71+
border-radius: 10px;
72+
background: linear-gradient(180deg, var(--accent), var(--accent-dark));
73+
color: #032018;
74+
font-weight: 600;
75+
cursor: pointer;
76+
box-shadow: 0 6px 18px rgba(14, 165, 233, 0.08);
77+
font-size: 15px;
78+
transition: transform 0.1s;
79+
}
80+
81+
button:active {
82+
transform: translateY(1px);
83+
}
84+
85+
#uuid {
86+
flex: 1;
87+
min-height: 48px;
88+
padding: 10px 12px;
89+
border-radius: 10px;
90+
background: var(--uuid-bg);
91+
display: flex;
92+
align-items: center;
93+
justify-content: flex-start;
94+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, "Roboto Mono", monospace;
95+
font-size: 14px;
96+
color: var(--text);
97+
overflow: auto;
98+
transition: background 0.3s;
99+
}
100+
101+
.hint {
102+
margin-top: 12px;
103+
color: var(--muted);
104+
font-size: 13px;
105+
}
106+
107+
.small-btn {
108+
background: transparent;
109+
border: 1px solid var(--border);
110+
color: var(--muted);
111+
padding: 8px 10px;
112+
border-radius: 10px;
113+
font-size: 13px;
114+
cursor: pointer;
115+
}
116+
117+
.theme-toggle {
118+
position: fixed;
119+
top: 20px;
120+
right: 20px;
121+
background: var(--card-bg);
122+
border: 1px solid var(--border);
123+
border-radius: 50%;
124+
width: 42px;
125+
height: 42px;
126+
display: flex;
127+
align-items: center;
128+
justify-content: center;
129+
cursor: pointer;
130+
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
131+
font-size: 18px;
132+
transition: background 0.3s, box-shadow 0.3s;
133+
}
134+
135+
.copied {
136+
position: fixed;
137+
right: 18px;
138+
bottom: 18px;
139+
background: var(--card-bg);
140+
color: var(--text);
141+
padding: 10px 14px;
142+
border-radius: 10px;
143+
border: 1px solid var(--border);
144+
box-shadow: 0 6px 22px rgba(2, 6, 23, 0.2);
145+
backdrop-filter: blur(6px);
146+
}

0 commit comments

Comments
 (0)