├── 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 | --------------------------------------------------------------------------------