├── src
├── vite-env.d.ts
├── sounds
│ └── startup.mp3
├── main.tsx
├── index.css
├── app.tsx
└── root.css
├── public
├── favicon.ico
└── vite.svg
├── vite.config.ts
├── tsconfig.node.json
├── .gitignore
├── index.html
├── package.json
└── tsconfig.json
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andybrooker/labs/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/sounds/startup.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andybrooker/labs/HEAD/src/sounds/startup.mp3
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "allowSyntheticDefaultImports": true
7 | },
8 | "include": ["vite.config.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import App from "./app";
4 | import "./index.css";
5 |
6 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
7 |
8 |
9 |
10 | );
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Labs
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "labs",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc && vite build",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "@radix-ui/react-toggle-group": "^1.0.3",
13 | "react": "^18.2.0",
14 | "react-dom": "^18.2.0",
15 | "use-sound": "^4.0.1"
16 | },
17 | "devDependencies": {
18 | "@types/react": "^18.0.28",
19 | "@types/react-dom": "^18.0.11",
20 | "@vitejs/plugin-react": "^3.1.0",
21 | "typescript": "^4.9.3",
22 | "vite": "^4.2.0"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "allowJs": false,
7 | "skipLibCheck": true,
8 | "esModuleInterop": false,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "module": "ESNext",
13 | "moduleResolution": "Node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx"
18 | },
19 | "include": ["src"],
20 | "references": [{ "path": "./tsconfig.node.json" }]
21 | }
22 |
--------------------------------------------------------------------------------
/public/vite.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3 | font-size: 16px;
4 | line-height: 1.5;
5 | font-weight: 400;
6 | color-scheme: light dark;
7 | color: var(--foreground);
8 | font-synthesis: none;
9 | text-rendering: optimizeLegibility;
10 | -webkit-font-smoothing: antialiased;
11 | -moz-osx-font-smoothing: grayscale;
12 | -webkit-text-size-adjust: 100%;
13 | /* Token */
14 | --foreground: var(--darkgray12);
15 | --background: var(--darkgray1);
16 | /* Colours */
17 | --gray1: hsl(0, 0%, 99%);
18 | --gray2: hsl(0, 0%, 97.3%);
19 | --gray3: hsl(0, 0%, 95.1%);
20 | --gray4: hsl(0, 0%, 93%);
21 | --gray5: hsl(0, 0%, 90.9%);
22 | --gray6: hsl(0, 0%, 88.7%);
23 | --gray7: hsl(0, 0%, 85.8%);
24 | --gray8: hsl(0, 0%, 78%);
25 | --gray9: hsl(0, 0%, 56.1%);
26 | --gray10: hsl(0, 0%, 52.3%);
27 | --gray11: hsl(0, 0%, 43.5%);
28 | --gray12: hsl(0, 0%, 9%);
29 | --darkgray1: hsl(0, 0%, 8.5%);
30 | --darkgray2: hsl(0, 0%, 11%);
31 | --darkgray3: hsl(0, 0%, 13.6%);
32 | --darkgray4: hsl(0, 0%, 15.8%);
33 | --darkgray5: hsl(0, 0%, 17.9%);
34 | --darkgray6: hsl(0, 0%, 20.5%);
35 | --darkgray7: hsl(0, 0%, 24.3%);
36 | --darkgray8: hsl(0, 0%, 31.2%);
37 | --darkgray9: hsl(0, 0%, 43.9%);
38 | --darkgray10: hsl(0, 0%, 49.4%);
39 | --darkgray11: hsl(0, 0%, 62.8%);
40 | --darkgray12: hsl(0, 0%, 93%);
41 | /* Shadows */
42 | --shadow-color: 0deg 0% 63%;
43 | --shadow: 1px 0px 1.1px hsl(var(--shadow-color) / 0.36),
44 | 3.3px 0px 3.7px -0.8px hsl(var(--shadow-color) / 0.36),
45 | 8.2px 0px 9.2px -1.7px hsl(var(--shadow-color) / 0.36),
46 | 20px 0px 22.5px -2.5px hsl(var(--shadow-color) / 0.36);
47 | }
48 |
49 | body {
50 | margin: 0;
51 | }
52 |
--------------------------------------------------------------------------------
/src/app.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, useState } from "react";
2 | import windowsSFX from "./sounds/startup.mp3";
3 |
4 | import "./root.css";
5 | import useSound from "use-sound";
6 |
7 | function degreeToRadian(degrees: number) {
8 | return degrees * (Math.PI / 180);
9 | }
10 |
11 | function easeInQuad(x: number): number {
12 | return x ** 2;
13 | }
14 |
15 | function normalise(value: number, min: number, max: number): number {
16 | return (value - min) / (max - min);
17 | }
18 |
19 | function getShadows({ angle }: { angle: number }) {
20 | const numShadows = 4;
21 | const longestSide = Math.max(200, 100);
22 | const factor = longestSide / 100;
23 | const scale = factor;
24 |
25 | return Array.from({ length: numShadows }, (_, i) => {
26 | const normalisedIndex = normalise(i, 0, numShadows);
27 |
28 | // Calculate Offset
29 | const offsetEase = easeInQuad(normalisedIndex) * 8.33;
30 | const offsetX = Math.cos(angle) * scale * factor * offsetEase;
31 | const offsetY = Math.sin(angle) * scale * factor * offsetEase;
32 |
33 | // Calculate radius-blur
34 | const offsetRadius = easeInQuad(normalisedIndex) * 9;
35 | const radiusBlur = offsetRadius * scale * factor;
36 |
37 | // Calculate spread-radius
38 | const offsetSpread = normalisedIndex * 2.5;
39 |
40 | const hslLightness = 0.36 * Math.sin(angle);
41 |
42 | return `${offsetX}px ${offsetY}px ${radiusBlur}px -${offsetSpread}px hsl(var(--shadow-color) / ${hslLightness})`;
43 | });
44 | }
45 |
46 | function useTime() {
47 | const now = () => {
48 | const d = new Date();
49 | return { hours: d.getUTCHours(), minutes: d.getUTCMinutes() };
50 | };
51 |
52 | const [time, setTime] = useState(now());
53 |
54 | useEffect(() => {
55 | const timerID = setInterval(() => setTime(now()), 1000);
56 | return () => clearInterval(timerID);
57 | }, []);
58 |
59 | return { time };
60 | }
61 |
62 | function useCounter() {
63 | const [count, setCount] = useState(360);
64 | const [isRunning, setIsRunning] = useState(false);
65 | const intervalRef = useRef();
66 |
67 | useEffect(() => {
68 | if (isRunning) {
69 | intervalRef.current = setInterval(() => {
70 | setCount((prevCount) => {
71 | const newCount = prevCount + 1;
72 | if (newCount > 1440) {
73 | // Stop at 24 hours (1440 minutes)
74 | // setIsRunning(false);
75 | return 0;
76 | }
77 | return newCount;
78 | });
79 | }, 1000 / 60);
80 | }
81 | return () => clearInterval(intervalRef.current);
82 | }, [isRunning]);
83 |
84 | function start() {
85 | setIsRunning(true);
86 | }
87 |
88 | function stop() {
89 | setIsRunning(false);
90 | }
91 |
92 | function reset() {
93 | setCount(0);
94 | }
95 |
96 | return { count, start, stop, reset, isRunning };
97 | }
98 |
99 | function getGreeting(time: number) {
100 | if (6 <= time && time < 12) return "Good Morning";
101 | else if (12 <= time && time <= 18) return "Good Afternoon";
102 | else return "Good Evening";
103 | }
104 |
105 | const getProgress = (totalMinutes: number) =>
106 | ((100 * Math.floor(totalMinutes / 30)) / 48).toString().padStart(2, "0");
107 |
108 | const getHours = (totalMinutes: number) =>
109 | Math.floor(totalMinutes / 60)
110 | .toString()
111 | .padStart(2, "0");
112 |
113 | function formatTime(totalMinutes: number) {
114 | const hours = getHours(totalMinutes);
115 | const minutes = (totalMinutes % 60).toString().padStart(2, "0");
116 | return `${hours}:${minutes}`;
117 | }
118 |
119 | const dayOrNight = (value: number) =>
120 | 0 <= value && value < 180 ? "day" : "night";
121 |
122 | function App() {
123 | const [theme, setTheme] = useState<"flat" | "xp">("flat");
124 | const { count, start, stop, isRunning } = useCounter();
125 | const [playStartup] = useSound(windowsSFX, { volume: 0.25 });
126 | // const { time } = useTime();
127 | // const [value, setValue] = useState(0);
128 | /* Derived Values */
129 |
130 | const value = (count - 360) / 4;
131 | // const maxDegrees = 360;
132 |
133 | function getCoord(
134 | offset: number,
135 | radius: number,
136 | angle: number,
137 | trigFn: Math["cos"] | Math["sin"]
138 | ) {
139 | return offset - radius * trigFn(angle);
140 | }
141 |
142 | // const timeProportion = (time.hours + time.minutes / 60 - 6) / 24;
143 | // const value = 360 * timeProportion;
144 | const radians = degreeToRadian(value - 180);
145 |
146 | const glowX = getCoord(50, 60, radians, Math.cos);
147 | const glowY = getCoord(0, 75, radians, Math.sin);
148 | const borderX = getCoord(50, 50, radians, Math.cos);
149 | const borderY = getCoord(50, 50, radians, Math.sin);
150 | const opacity = Math.sin(radians);
151 |
152 | const shadows = getShadows({ angle: degreeToRadian(value) }).join(", ");
153 | const timeOfDay = dayOrNight(value);
154 |
155 | return (
156 |
162 |
163 |
164 |
172 |
173 |
187 |
196 | Greetings
197 |
198 |
199 |
200 |
{getGreeting(value / 15 + 6)}, Andy
201 |
202 | {formatTime(count)}
203 | {/* {`${time.hours}:${time.minutes
204 | .toString()
205 | .padStart(2)}`} */}
206 |
207 |
208 |
209 |
217 | {Array(48)
218 | .fill(0)
219 | .map(({ value, index }) => (
220 |
221 | ))}
222 |
223 | Day Progressing... {getHours(count)}/24
224 |
225 |
226 |
London, United Kingdom
227 |
228 |
229 |
239 |
240 |
241 |
258 |
259 | );
260 | }
261 |
262 | export default App;
263 |
--------------------------------------------------------------------------------
/src/root.css:
--------------------------------------------------------------------------------
1 | .app {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | justify-content: center;
6 | height: 100vh;
7 | width: 100vw;
8 | /* background-color: hsl(0deg, 0%, clamp(8.5%, var(--light), 93%)); */
9 | --x: 0%;
10 | --y: 50%;
11 | --opacity: 1;
12 | --light: 0%;
13 | --angle: 0deg;
14 | }
15 |
16 | .app[data-theme="flat"] {
17 | font-family: inherit;
18 | }
19 |
20 | .app[data-theme="xp"] {
21 | font-family: Tahoma, "Trebuchet MS";
22 | font-size: 12px;
23 | }
24 |
25 | .app[data-theme="xp"][data-state="day"] {
26 | background: url("https://binsta.dev/api/v1/files/YE2ru3_wpJ/transform?format=webp&size=xl&quality=hi");
27 | }
28 |
29 | .app[data-theme="xp"][data-state="night"] {
30 | background: url("https://binsta.dev/api/v1/files/4AA7dO33BR/transform?format=webp&size=xl&quality=hi");
31 | }
32 |
33 | .app[data-state="day"] {
34 | background-color: var(--gray3);
35 | transition: all 1s ease;
36 | }
37 |
38 | .app[data-state="night"] {
39 | background-color: #101010;
40 | transition: all 1s ease;
41 | }
42 |
43 | .card {
44 | position: relative;
45 | width: 400px;
46 | height: 200px;
47 | overflow: hidden;
48 | box-sizing: border-box;
49 | font-weight: 500;
50 | }
51 |
52 | .app[data-theme="flat"] .card {
53 | border-radius: 24px;
54 | letter-spacing: -0.5px;
55 | padding: 32px;
56 | }
57 |
58 | .app[data-theme="flat"] .title-bar {
59 | display: none;
60 | }
61 |
62 | .app[data-theme="xp"] .card {
63 | font-family: Tahoma;
64 | overflow: hidden;
65 | letter-spacing: 0px;
66 | border-top-left-radius: 8px;
67 | border-top-right-radius: 8px;
68 | display: flex;
69 | flex-direction: column;
70 | }
71 |
72 | .app[data-theme="xp"][data-state="day"] .card {
73 | color: var(--gray12);
74 | --shadow-color: 0 0% 8.5%;
75 | box-shadow: inset -1px -1px #00138c, inset 1px 1px #0831d9,
76 | inset -2px -2px #001ea0, inset 2px 2px #166aee, inset -3px -3px #003bda,
77 | inset 3px 3px #0855dd, var(--boxShadow);
78 | background: #ece9d8;
79 | }
80 |
81 | .app[data-theme="xp"][data-state="night"] .card {
82 | color: var(--darkgray12);
83 | --shadow-color: 0 0% 8.5%;
84 | box-shadow: inset -1px -1px var(--darkgray2), inset 1px 1px var(--darkgray3),
85 | inset -2px -2px var(--darkgray2), inset 2px 2px var(--darkgray5),
86 | inset -3px -3px var(--darkgray4), inset 3px 3px var(--darkgray6),
87 | var(--boxShadow);
88 | background: linear-gradient(
89 | calc(var(--angle) - 90deg),
90 | var(--darkgray5) 0%,
91 | var(--darkgray6) 100%
92 | );
93 | }
94 |
95 | .app[data-theme="xp"] .title-bar {
96 | font-family: Trebuchet MS;
97 | padding: 3px 5px 3px 14px;
98 | border-top-left-radius: 8px;
99 | border-top-right-radius: 7px;
100 | font-size: 13px;
101 | letter-spacing: -0.25px;
102 | }
103 |
104 | .app[data-theme="xp"][data-state="day"] .title-bar {
105 | color: var(--gray1);
106 | text-shadow: 1px 1px #0f1089;
107 | background: linear-gradient(
108 | 180deg,
109 | #0997ff,
110 | #0053ee 8%,
111 | #0050ee 40%,
112 | #06f 88%,
113 | #06f 93%,
114 | #005bff 95%,
115 | #003dd7 96%,
116 | #003dd7
117 | )
118 | padding-box,
119 | radial-gradient(
120 | 50px 20px at calc(100% - var(--x)) calc(100% - var(--y)),
121 | #41b0ff 0%,
122 | #003dd7 100%
123 | )
124 | border-box;
125 | border: 1px solid transparent;
126 | }
127 |
128 | .app[data-theme="xp"][data-state="night"] .title-bar {
129 | text-shadow: 0px 0px 1px var(--darkgray11);
130 | background: linear-gradient(
131 | 180deg,
132 | var(--darkgray8),
133 | var(--darkgray4) 8%,
134 | var(--darkgray3) 40%,
135 | var(--darkgray2) 88%,
136 | var(--darkgray2) 95%,
137 | var(--darkgray1) 96%,
138 | var(--darkgray1)
139 | )
140 | padding-box,
141 | radial-gradient(
142 | 50px 20px at calc(var(--x)) calc(var(--y)),
143 | hsl(0, 0%, clamp(11%, calc(-1 * var(--light)), 62.8%)) 0%,
144 | var(--darkgray2) 100%
145 | )
146 | border-box;
147 | border: 1px solid transparent;
148 | }
149 |
150 | @media only screen and (max-width: 600px) {
151 | .card {
152 | width: 350px;
153 | height: 175px;
154 | }
155 | }
156 |
157 | .app[data-theme="flat"][data-state="day"] .card {
158 | color: var(--gray12);
159 | --secondary-color: var(--gray11);
160 | --tertiary-color: var(--gray10);
161 | background: linear-gradient(calc(var(--angle) - 90deg), white 0%, white 100%)
162 | padding-box,
163 | linear-gradient(
164 | calc(var(--angle) - 90deg),
165 | hsl(0 0% 88.7% / 0.8) 0%,
166 | var(--gray2) 100%
167 | )
168 | border-box;
169 | border: 1px solid transparent;
170 | box-shadow: var(--boxShadow);
171 | }
172 |
173 | .app[data-theme="flat"][data-state="night"] .card {
174 | color: var(--darkgray12);
175 | --secondary-color: var(--darkgray11);
176 | --tertiary-color: var(--darkgray10);
177 | background: linear-gradient(
178 | calc(var(--angle) - 90deg),
179 | var(--darkgray3) 0%,
180 | var(--darkgray2) 100%
181 | )
182 | padding-box,
183 | radial-gradient(
184 | 100px 100px at var(--x) calc(var(--y)),
185 | hsl(0, 0%, clamp(11%, calc(-1 * var(--light)), 62.8%)) 0%,
186 | var(--darkgray2) 100%
187 | )
188 | border-box;
189 | border: 1px solid transparent;
190 | transition: background 1s ease;
191 | }
192 |
193 | [data-state="night"] > .glow {
194 | display: block;
195 | }
196 |
197 | .glow {
198 | display: none;
199 | --left: -10%;
200 | --top: -75%;
201 | position: absolute;
202 | width: 100px;
203 | height: 100px;
204 | top: calc(var(--top) + 50px);
205 | left: calc(var(--left) - 50px);
206 | background: white;
207 | opacity: var(--opacity);
208 | filter: blur(50px);
209 | border-radius: 9999px;
210 | }
211 |
212 | .app[data-theme="flat"] .text {
213 | height: 100%;
214 | }
215 |
216 | .text {
217 | position: relative;
218 | display: flex;
219 | flex-direction: column;
220 | justify-content: space-between;
221 | flex: 1;
222 | }
223 |
224 | .progressContainer {
225 | display: flex;
226 | flex-direction: column;
227 | gap: 8px;
228 | }
229 |
230 | .app[data-theme="flat"] .progressContainer {
231 | display: none;
232 | }
233 |
234 | .app[data-theme="xp"][data-state="night"] .progress {
235 | background-color: var(--darkgray8);
236 | box-shadow: 0px 0px 0px 1px var(--darkgray5), inset 0px 0px 2px 0px #6fd35aff;
237 | }
238 |
239 | .app[data-theme="xp"] .progress {
240 | position: relative;
241 | height: 14px;
242 | border-radius: 4px;
243 | box-shadow: 0px 0px 0px 1px var(--gray11), inset 0px 0px 2px 0px var(--gray10);
244 | display: flex;
245 | justify-content: space-between;
246 | gap: 1px;
247 | padding: 3px;
248 | background-color: var(--gray1);
249 | }
250 |
251 | .app[data-theme="xp"] .progress::before {
252 | position: absolute;
253 | content: "";
254 | inset: 2px;
255 | clip-path: inset(0% 0% 0% var(--progress));
256 | }
257 |
258 | .app[data-theme="xp"][data-state="day"] .progress::before {
259 | background-color: var(--gray1);
260 | }
261 |
262 | .app[data-theme="xp"][data-state="night"] .progress::before {
263 | background-color: var(--darkgray8);
264 | }
265 |
266 | .loader {
267 | flex: 1;
268 | height: 100%;
269 | background: linear-gradient(#a4e999, #6fd35a, #a4e999);
270 | }
271 |
272 | .app[data-theme="xp"] .text {
273 | padding: 16px;
274 | }
275 |
276 | .time {
277 | color: var(--secondary-color);
278 | }
279 |
280 | .banner {
281 | width: 100%;
282 | display: flex;
283 | justify-content: space-between;
284 | }
285 |
286 | .location {
287 | color: var(--tertiary-color);
288 | }
289 |
290 | /* Buttons */
291 |
292 | .app[data-theme="flat"] button {
293 | margin-top: 32px;
294 | font-size: 12px;
295 | font-weight: 500;
296 | text-shadow: 0px 0px 1px var(--gray7);
297 | letter-spacing: -0.5px;
298 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
299 | border-radius: 9999px;
300 | padding: 6px 12px;
301 | min-width: 120px;
302 | cursor: pointer;
303 | }
304 |
305 | .app[data-theme="flat"][data-state="day"] button {
306 | color: var(--gray12);
307 | background: linear-gradient(calc(var(--angle) - 90deg), white 0%, white 100%)
308 | padding-box,
309 | linear-gradient(
310 | calc(var(--angle) - 90deg),
311 | hsl(0 0% 88.7% / 0.8) 0%,
312 | var(--gray2) 100%
313 | )
314 | border-box;
315 | border: 1px solid transparent;
316 | }
317 |
318 | .app[data-theme="flat"][data-state="night"] button {
319 | color: var(--darkgray12);
320 | --secondary-color: var(--darkgray11);
321 | --tertiary-color: var(--darkgray10);
322 | background: linear-gradient(
323 | calc(var(--angle) - 90deg),
324 | var(--darkgray3) 0%,
325 | var(--darkgray2) 100%
326 | )
327 | padding-box,
328 | radial-gradient(
329 | 50% 50% at var(--x) calc(var(--y)),
330 | hsl(0, 0%, clamp(11%, calc(-1 * var(--light)), 62.8%)) 0%,
331 | var(--darkgray2) 100%
332 | )
333 | border-box;
334 | border: 1px solid transparent;
335 | transition: background 1s ease;
336 | }
337 |
338 | .app[data-theme="xp"] button {
339 | margin-top: 32px;
340 | font-family: Tahoma;
341 | min-width: 75px;
342 | min-height: 23px;
343 | padding: 0 12px;
344 | font-size: 11px;
345 | box-sizing: border-box;
346 | box-shadow: none;
347 | border-radius: 3px;
348 | }
349 |
350 | .app[data-theme="xp"] button:hover {
351 | box-shadow: inset -1px 1px #fff0cf, inset 1px 2px #fdd889,
352 | inset -2px 2px #fbc761, inset 2px -2px #e5a01a;
353 | }
354 |
355 | .app[data-theme="xp"][data-state="night"] button:active {
356 | background: linear-gradient(
357 | 180deg,
358 | var(--darkgrey4),
359 | var(--darkgrey5) 8%,
360 | var(--darkgrey6) 94%,
361 | var(--darkgrey7)
362 | );
363 | }
364 |
365 | .app[data-theme="xp"][data-state="day"] button:active {
366 | background: linear-gradient(
367 | 180deg,
368 | #cdcac3,
369 | #e3e3db 8%,
370 | #e5e5de 94%,
371 | #f2f2f1
372 | );
373 | }
374 |
375 | .app[data-theme="xp"] button:focus-visible {
376 | box-shadow: inset -1px 1px #cee7ff, inset 1px 2px #98b8ea,
377 | inset -2px 2px #bcd4f6, inset 1px -1px #89ade4, inset 2px -2px #89ade4;
378 | outline: 1px dotted #000;
379 | outline-offset: -4px;
380 | }
381 |
382 | .app[data-theme="xp"][data-state="day"] button {
383 | background: linear-gradient(180deg, #fff, #ecebe5 86%, #d8d0c4);
384 | border: 1px solid #003c74;
385 | }
386 |
387 | .app[data-theme="xp"][data-state="night"] button {
388 | color: var(--darkgray12);
389 | background: linear-gradient(
390 | 180deg,
391 | var(--darkgray5),
392 | var(--darkgray8) 86%,
393 | var(--darkgray8)
394 | );
395 | border: 1px solid #34495d;
396 | }
397 |
398 | .toggleGroup {
399 | position: absolute;
400 | top: 0;
401 | display: flex;
402 | gap: 16px;
403 | }
404 |
--------------------------------------------------------------------------------