diff options
| -rw-r--r-- | .claude/settings.local.json | 12 | ||||
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | Dockerfile | 2 | ||||
| -rw-r--r-- | biome.json | 26 | ||||
| -rw-r--r-- | docker-entrypoint.sh | 9 | ||||
| -rw-r--r-- | esbuild.config.js | 64 | ||||
| -rw-r--r-- | nginx.conf | 8 | ||||
| -rw-r--r-- | package-lock.json | 576 | ||||
| -rw-r--r-- | package.json | 18 | ||||
| -rw-r--r-- | rolldown.config.js | 36 | ||||
| -rw-r--r-- | src/css/style.css | 1395 | ||||
| -rw-r--r-- | src/index.html | 1167 | ||||
| -rw-r--r-- | src/js/oneko.js | 284 | ||||
| -rw-r--r-- | src/js/oneko.ts | 277 | ||||
| -rw-r--r-- | src/js/script.js | 87 | ||||
| -rw-r--r-- | src/js/script.ts | 78 | ||||
| -rw-r--r-- | src/types/global.d.ts | 7 | ||||
| -rw-r--r-- | tsconfig.json | 17 |
18 files changed, 1999 insertions, 2065 deletions
diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index 282bb4b..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(tree:*)", - "Bash(find:*)", - "Bash(file:*)", - "Bash(npm run dev:*)", - "Bash(npm run build:*)", - "Bash(HOST=\"https://cdn.example.com\" npm run build:*)" - ] - } -} @@ -35,3 +35,4 @@ yarn-error.log # Temporary files *.tmp *.log + @@ -5,7 +5,7 @@ COPY package*.json ./ RUN npm ci COPY src/ ./src/ -COPY esbuild.config.js ./ +COPY rolldown.config.js ./ RUN npm run build FROM nginx:alpine as adelie diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..bcf844c --- /dev/null +++ b/biome.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 4, + "lineWidth": 100 + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "trailingCommas": "es5" + } + }, + "json": { + "formatter": { + "indentWidth": 4 + } + } +} diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 123725b..f2eed53 100644 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -1,12 +1,9 @@ #!/bin/sh -# Rewrite asset URLs in HTML to use the production domain if [ -n "$HOST" ]; then - sed -i "s|href=\"/bundle\.|href=\"$HOST/bundle.|g" /usr/share/nginx/html/index.html - sed -i "s|src=\"/bundle\.|src=\"$HOST/bundle.|g" /usr/share/nginx/html/index.html - sed -i "s|href=\"/img/|href=\"$HOST/img/|g" /usr/share/nginx/html/index.html - sed -i "s|src=\"/oneko/|src=\"$HOST/oneko/|g" /usr/share/nginx/html/index.html + sed -i "s|ASSET_BASE_PLACEHOLDER|$HOST|g" /usr/share/nginx/html/index.html +else + sed -i "s|\"ASSET_BASE_PLACEHOLDER\"|\"\""|g" /usr/share/nginx/html/index.html fi -# Start nginx exec nginx -g 'daemon off;' diff --git a/esbuild.config.js b/esbuild.config.js deleted file mode 100644 index 88d726a..0000000 --- a/esbuild.config.js +++ /dev/null @@ -1,64 +0,0 @@ -const esbuild = require('esbuild'); -const fs = require('fs-extra'); -const path = require('path'); - -const production = process.env.NODE_ENV === 'production'; - -async function buildJS() { - await esbuild.build({ - entryPoints: ['src/js/script.js'], - bundle: true, - minify: production, - sourcemap: true, - target: 'es2020', - outfile: 'dist/bundle.js', - }); -} - -async function buildCSS() { - await esbuild.build({ - entryPoints: ['src/css/style.css'], - bundle: true, - minify: production, - sourcemap: true, - loader: { - '.css': 'css', - '.woff2': 'file', - '.png': 'file', - '.svg': 'file', - }, - outfile: 'dist/bundle.css', - }); -} - -async function copyAssets() { - await fs.copy('src/assets', 'dist', { - overwrite: true, - }); -} - -async function processHTML() { - let html = await fs.readFile('src/index.html', 'utf8'); - await fs.writeFile('dist/index.html', html); -} - -async function clean() { - await fs.remove('dist'); - await fs.ensureDir('dist'); -} - -async function build() { - try { - await clean(); - await Promise.all([ - buildJS(), - buildCSS(), - copyAssets(), - ]); - await processHTML(); - } catch (err) { - process.exit(1); - } -} - -build(); @@ -24,7 +24,6 @@ http { types_hash_max_size 2048; client_max_body_size 20M; - # Gzip compression gzip on; gzip_vary on; gzip_min_length 1024; @@ -37,42 +36,35 @@ http { server_name _; root /usr/share/nginx/html; - # Security headers add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; add_header Referrer-Policy "no-referrer-when-downgrade" always; - # CORS headers - allow requests from any origin add_header Access-Control-Allow-Origin "*" always; add_header Access-Control-Allow-Methods "GET, OPTIONS" always; add_header Access-Control-Allow-Headers "Content-Type" always; - # Handle preflight requests if ($request_method = 'OPTIONS') { return 204; } - # CSS and JS - long cache, versioning handled by filenames location ~* \.(css|js)$ { expires 1y; add_header Cache-Control "public, immutable, max-age=31536000" always; } - # Fonts - long cache location ~* \.(woff|woff2|ttf|otf|eot)$ { expires 1y; add_header Cache-Control "public, immutable, max-age=31536000" always; add_header Access-Control-Allow-Origin "*" always; } - # Images - moderate cache location ~* \.(jpg|jpeg|png|gif|svg|ico|webp)$ { expires 30d; add_header Cache-Control "public, max-age=2592000" always; } - # Everything else - short cache location / { expires 1h; add_header Cache-Control "public, max-age=3600" always; diff --git a/package-lock.json b/package-lock.json index aa28f56..5791614 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,314 +1,309 @@ { - "name": "adelie-css", - "version": "1.0.0", + "name": "adelie", + "version": "0.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "adelie-css", - "version": "1.0.0", + "name": "adelie", + "version": "0.0.1", "dependencies": { "prismjs": "^1.29.0" }, "devDependencies": { - "esbuild": "^0.25.5", + "@biomejs/biome": "^1.9.4", + "@types/node": "^22.10.2", "fs-extra": "^11.2.0", - "http-server": "^14.1.1" + "http-server": "^14.1.1", + "rolldown": "^0.12.1", + "typescript": "^5.9.3" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "node_modules/@biomejs/biome": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz", + "integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==", + "dev": true, + "hasInstallScript": true, + "license": "MIT OR Apache-2.0", + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "1.9.4", + "@biomejs/cli-darwin-x64": "1.9.4", + "@biomejs/cli-linux-arm64": "1.9.4", + "@biomejs/cli-linux-arm64-musl": "1.9.4", + "@biomejs/cli-linux-x64": "1.9.4", + "@biomejs/cli-linux-x64-musl": "1.9.4", + "@biomejs/cli-win32-arm64": "1.9.4", + "@biomejs/cli-win32-x64": "1.9.4" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz", + "integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==", "cpu": [ - "ppc64" + "arm64" ], "dev": true, - "license": "MIT", + "license": "MIT OR Apache-2.0", "optional": true, "os": [ - "aix" + "darwin" ], "engines": { - "node": ">=18" + "node": ">=14.21.3" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "node_modules/@biomejs/cli-darwin-x64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz", + "integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==", "cpu": [ - "arm" + "x64" ], "dev": true, - "license": "MIT", + "license": "MIT OR Apache-2.0", "optional": true, "os": [ - "android" + "darwin" ], "engines": { - "node": ">=18" + "node": ">=14.21.3" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "node_modules/@biomejs/cli-linux-arm64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz", + "integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", + "license": "MIT OR Apache-2.0", "optional": true, "os": [ - "android" + "linux" ], "engines": { - "node": ">=18" + "node": ">=14.21.3" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz", + "integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==", "cpu": [ - "x64" + "arm64" ], "dev": true, - "license": "MIT", + "license": "MIT OR Apache-2.0", "optional": true, "os": [ - "android" + "linux" ], "engines": { - "node": ">=18" + "node": ">=14.21.3" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "node_modules/@biomejs/cli-linux-x64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz", + "integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==", "cpu": [ - "arm64" + "x64" ], "dev": true, - "license": "MIT", + "license": "MIT OR Apache-2.0", "optional": true, "os": [ - "darwin" + "linux" ], "engines": { - "node": ">=18" + "node": ">=14.21.3" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz", + "integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", + "license": "MIT OR Apache-2.0", "optional": true, "os": [ - "darwin" + "linux" ], "engines": { - "node": ">=18" + "node": ">=14.21.3" } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "node_modules/@biomejs/cli-win32-arm64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz", + "integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", + "license": "MIT OR Apache-2.0", "optional": true, "os": [ - "freebsd" + "win32" ], "engines": { - "node": ">=18" + "node": ">=14.21.3" } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "node_modules/@biomejs/cli-win32-x64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz", + "integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", + "license": "MIT OR Apache-2.0", "optional": true, "os": [ - "freebsd" + "win32" ], "engines": { - "node": ">=18" + "node": ">=14.21.3" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], + "node_modules/@emnapi/core": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", + "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" } }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], + "node_modules/@emnapi/runtime": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", + "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "dependencies": { + "tslib": "^2.4.0" } }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "dependencies": { + "tslib": "^2.4.0" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" } }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-0.12.2.tgz", + "integrity": "sha512-Y3Ajye63Z5KymGUwTLaK7Q6YMvycXqNiXtosecgVzjAwMITCmXdzgnWgzzx5UlWHMrDYL4m5zIeXGB5slLwoMA==", "cpu": [ - "mips64el" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } + "darwin" + ] }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-0.12.2.tgz", + "integrity": "sha512-ZcVuVFEFBXhp00TUNn+EDYs7SGGLQCznvCeuW1XkM8EC2/LfewU/o4WuJK7CBC4iCSktuFGpw7+zLe8D6iinzg==", "cpu": [ - "ppc64" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } + "darwin" + ] }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-0.12.2.tgz", + "integrity": "sha512-4fjuQHpm3q/Ly4fcqb8Qn49OQc2EQR2scUbQaOzXr7mIn9Zy8NfdRrsVG4/wpYvihIlTEtVx+ku0IZwcUzzZGg==", "cpu": [ - "riscv64" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } + "freebsd" + ] }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.12.2.tgz", + "integrity": "sha512-MGEDaYLzTQ1kpvt13PzOwnd6O668S1mPM/vgi4O9vCfqJNTXZX8SeAg8Z2dQZbMSUyFDBVKGkz23GRTHqkGIsQ==", "cpu": [ - "s390x" + "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">=18" - } + ] }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.12.2.tgz", + "integrity": "sha512-8uiaMe39twyIAw0do1Gc3O2SpQmyL1A/BucFncEB8eU5jtb3BWIM/X+F+eKDU6c+XZ+S1T025dhR1cGg8y20Xw==", "cpu": [ - "x64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">=18" - } + ] }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.12.2.tgz", + "integrity": "sha512-GOFSKaMJueaSSODcqI+0Hu79buHYtGV7h2tydIDkSDD20mQuvwOF7jIVd27yNdlXWS9wLObwEu3BNgHmIj1M6A==", "cpu": [ "arm64" ], @@ -316,16 +311,13 @@ "license": "MIT", "optional": true, "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } + "linux" + ] }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.12.2.tgz", + "integrity": "sha512-+hlRURUSiVP0+PFtjoTxUsiy/2NQpbf3DyUyMyl8Nv5+1BxjB6452VY1iFI+RzG4iLNJslcVcI6d6lJQ5zZYfw==", "cpu": [ "x64" ], @@ -333,50 +325,44 @@ "license": "MIT", "optional": true, "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } + "linux" + ] }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-0.12.2.tgz", + "integrity": "sha512-9WKIaQSZZ0i9F5msW4I5kEj6ov+TZLteuTqCzI7nYWDBDm7m/hYkOkdIBf41GC8iKFsgVIQO0kRVAT966U8RxQ==", "cpu": [ - "arm64" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } + "linux" + ] }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-0.12.2.tgz", + "integrity": "sha512-KY7bNrR3Jk6O0ne8LAaAnq9yI6xuKwhr/L2d1lBwrraCCyLHk0UEv4g6PfJkyUx6GfN0gVYuSxFgo9OggycldA==", "cpu": [ - "x64" + "wasm32" ], "dev": true, "license": "MIT", "optional": true, - "os": [ - "openbsd" - ], + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.4" + }, "engines": { - "node": ">=18" + "node": ">=14.21.3" } }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.12.2.tgz", + "integrity": "sha512-XYBXifPk5iNcPUTyV/yB4tlj5nI+fYoe/8CLHjyUG8GS0l8rCD+jKaAv3Jvz2T2erlkjEPqJXmzhUfBscOUo6g==", "cpu": [ "arm64" ], @@ -384,78 +370,56 @@ "license": "MIT", "optional": true, "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } + "win32" + ] }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "node_modules/@rolldown/binding-win32-ia32-msvc": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.12.2.tgz", + "integrity": "sha512-SV9LqvEc0d4gCLbkezH+UJ2uj2pTw3mwSKhFJEm6ir1lb05W6y9++m67NRs17fhudkFAz8iKlyPxKtZBKzAKOw==", "cpu": [ - "x64" + "ia32" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } + "win32" + ] }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.12.2.tgz", + "integrity": "sha512-XnRqHx182tyk1M12OMXqLajIj9DZrOUEKSPYrQUSaMCmHHAhULYP6ki240CV16PBWZW4Q1pwQ7YVKYFevRtvKg==", "cpu": [ - "arm64" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "win32" - ], - "engines": { - "node": ">=18" - } + ] }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" + "dependencies": { + "tslib": "^2.4.0" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], + "node_modules/@types/node": { + "version": "22.19.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz", + "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" + "dependencies": { + "undici-types": "~6.21.0" } }, "node_modules/ansi-styles": { @@ -638,48 +602,6 @@ "node": ">= 0.4" } }, - "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -1029,6 +951,33 @@ "dev": true, "license": "MIT" }, + "node_modules/rolldown": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-0.12.2.tgz", + "integrity": "sha512-YJYKiYt2O9XytiQ3Na4Kk29avfIXhvK7udB3wAaVaF4kiSsFKE1167tElO/0eD6tjfJXCvwNxwsyYkBJRtsLmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "zod": "^3.23.8" + }, + "bin": { + "rolldown": "bin/cli.js" + }, + "optionalDependencies": { + "@rolldown/binding-darwin-arm64": "0.12.2", + "@rolldown/binding-darwin-x64": "0.12.2", + "@rolldown/binding-freebsd-x64": "0.12.2", + "@rolldown/binding-linux-arm-gnueabihf": "0.12.2", + "@rolldown/binding-linux-arm64-gnu": "0.12.2", + "@rolldown/binding-linux-arm64-musl": "0.12.2", + "@rolldown/binding-linux-x64-gnu": "0.12.2", + "@rolldown/binding-linux-x64-musl": "0.12.2", + "@rolldown/binding-wasm32-wasi": "0.12.2", + "@rolldown/binding-win32-arm64-msvc": "0.12.2", + "@rolldown/binding-win32-ia32-msvc": "0.12.2", + "@rolldown/binding-win32-x64-msvc": "0.12.2" + } + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -1139,6 +1088,35 @@ "node": ">=8" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, "node_modules/union": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", @@ -1180,6 +1158,16 @@ "engines": { "node": ">=12" } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 9414767..e8f5dac 100644 --- a/package.json +++ b/package.json @@ -1,20 +1,28 @@ { "name": "adelie", "version": "0.0.1", + "type": "module", "private": true, "scripts": { - "build": "NODE_ENV=production node esbuild.config.js", - "dev": "node esbuild.config.js", + "build": "NODE_ENV=production node rolldown.config.js", + "dev": "node rolldown.config.js", "clean": "rm -rf dist", "serve": "npx http-server dist -p 8080 -o", - "dev:serve": "npm run dev && npm run serve" + "dev:serve": "npm run dev && npm run serve", + "lint": "biome lint src", + "format": "biome format --write src", + "format:check": "biome format src", + "check": "biome check src" }, "dependencies": { "prismjs": "^1.29.0" }, "devDependencies": { - "esbuild": "^0.25.5", + "@biomejs/biome": "^1.9.4", + "@types/node": "^22.10.2", "fs-extra": "^11.2.0", - "http-server": "^14.1.1" + "http-server": "^14.1.1", + "rolldown": "^0.12.1", + "typescript": "^5.9.3" } } diff --git a/rolldown.config.js b/rolldown.config.js new file mode 100644 index 0000000..6cc7765 --- /dev/null +++ b/rolldown.config.js @@ -0,0 +1,36 @@ +import { rolldown } from 'rolldown'; +import fs from 'fs-extra'; +import path from 'path'; + +const production = process.env.NODE_ENV === 'production'; + +async function build() { + try { + await fs.remove('dist'); + await fs.ensureDir('dist'); + + const jsBundle = await rolldown({ + input: 'src/js/script.ts', + output: { + file: 'dist/bundle.js', + format: 'es', + sourcemap: !production, + }, + }); + + await jsBundle.write(); + + await fs.copy('src/css/style.css', 'dist/bundle.css'); + await fs.copy('src/assets', 'dist', { + overwrite: true, + }); + + let html = await fs.readFile('src/index.html', 'utf8'); + await fs.writeFile('dist/index.html', html); + } catch (err) { + console.error('Build failed:', err); + process.exit(1); + } +} + +build(); diff --git a/src/css/style.css b/src/css/style.css index 50c6f48..97b14f3 100644 --- a/src/css/style.css +++ b/src/css/style.css @@ -1,271 +1,259 @@ :root { - /* Win95 "strawberry latte" tokens */ - - /* Desktop (rarely used, but handy for full-page demos) */ - --desktop: #d7a5b7; - - /* Page + surfaces */ - --bg: #f4ece7; /* page behind windows */ - --bg-pattern: #efe5df; - - /* Background texture (dither/checker overlay) */ - --dither-ink: rgba(59, 46, 44, 0.22); - --dither-opacity: 0.18; - --surface: #e9dcd5; /* window face */ - --surface-alt: #fff7f2; /* milk highlight */ - - /* Text */ - --fg: #2a1f1d; - --muted: #6a5550; - - /* 3D border system */ - --border: #3b2e2c; - --border-light: #fff7f2; - --border-dark: #8e7670; - - /* Girly coffee accents */ - --primary: #e56aa6; /* strawberry */ - --primary-light: #f08dbe; /* strawberry milk */ - --primary-dark: #c84d86; /* rose */ - - --secondary: #b69cff; /* lilac */ - --secondary-light: #d7c8ff; - --secondary-dark: #8f78d6; - - --accent-brown: #b88a68; /* latte */ - --accent-brown-light: #d4a574; - --accent-brown-dark: #7a5245; - - /* Title bar (Win95-ish window chrome) */ - --titlebar-a: #d85b9b; - --titlebar-b: #7a3e8e; - --titlebar-fg: #fff7f2; - - /* Links */ - --link: #c84d86; - --link-visited: #6f2f80; - --link-active: #b5173d; - - /* Keep existing names as aliases */ - --accent-pink: var(--primary); - --accent-lavender: var(--secondary); - - /* Status colors (slightly softened) */ - --success: #2e8b57; - --error: #b3261e; - --warning: #b7791f; - --info: #2b6cb0; - - /* Spacing */ - --space-xs: 0.5rem; - --space-sm: 1rem; - --space-md: 1.5rem; - --space-lg: 2rem; - --space-xl: 3rem; - - /* Borders */ - --border-width: 2px; - --border-style: solid; - - /* Type */ - --font-mono: "Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; - --line-height: 1.35; - - /* Shadows */ - --shadow-sm: 1px 1px 0; - --shadow-md: 2px 2px 0; - --shadow-lg: 3px 3px 0; - --shadow-color: rgba(0, 0, 0, 0.22); - --shadow-box: 2px 2px 0 rgba(0, 0, 0, 0.12); - --shadow-button: 1px 1px 0 rgba(0, 0, 0, 0.18); - - --highlight: rgba(255, 247, 242, 0.9); - --lowlight: rgba(59, 46, 44, 0.55); - - --content-max-width: 1000px; + /* Win95 "strawberry latte" tokens */ + + /* Desktop (rarely used, but handy for full-page demos) */ + --desktop: #d7a5b7; + + /* Page + surfaces */ + --bg: #f4ece7; /* page behind windows */ + --bg-pattern: #efe5df; + + /* Background texture (dither/checker overlay) */ + --dither-ink: rgba(59, 46, 44, 0.22); + --dither-opacity: 0.18; + --surface: #e9dcd5; /* window face */ + --surface-alt: #fff7f2; /* milk highlight */ + + /* Text */ + --fg: #2a1f1d; + --muted: #6a5550; + + /* 3D border system */ + --border: #3b2e2c; + --border-light: #fff7f2; + --border-dark: #8e7670; + + /* Girly coffee accents */ + --primary: #e56aa6; /* strawberry */ + --primary-light: #f08dbe; /* strawberry milk */ + --primary-dark: #c84d86; /* rose */ + + --secondary: #b69cff; /* lilac */ + --secondary-light: #d7c8ff; + --secondary-dark: #8f78d6; + + --accent-brown: #b88a68; /* latte */ + --accent-brown-light: #d4a574; + --accent-brown-dark: #7a5245; + + /* Title bar (Win95-ish window chrome) */ + --titlebar-a: #d85b9b; + --titlebar-b: #7a3e8e; + --titlebar-fg: #fff7f2; + + /* Links */ + --link: #c84d86; + --link-visited: #6f2f80; + --link-active: #b5173d; + + /* Keep existing names as aliases */ + --accent-pink: var(--primary); + --accent-lavender: var(--secondary); + + /* Status colors (slightly softened) */ + --success: #2e8b57; + --error: #b3261e; + --warning: #b7791f; + --info: #2b6cb0; + + /* Spacing */ + --space-xs: 0.5rem; + --space-sm: 1rem; + --space-md: 1.5rem; + --space-lg: 2rem; + --space-xl: 3rem; + + /* Borders */ + --border-width: 2px; + --border-style: solid; + + /* Type */ + --font-mono: "Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", + "Courier New", monospace; + --line-height: 1.35; + + /* Shadows */ + --shadow-sm: 1px 1px 0; + --shadow-md: 2px 2px 0; + --shadow-lg: 3px 3px 0; + --shadow-color: rgba(0, 0, 0, 0.22); + --shadow-box: 2px 2px 0 rgba(0, 0, 0, 0.12); + --shadow-button: 1px 1px 0 rgba(0, 0, 0, 0.18); + + --highlight: rgba(255, 247, 242, 0.9); + --lowlight: rgba(59, 46, 44, 0.55); + + --content-max-width: 1000px; } [data-theme="dark"] { - /* Espresso-night variant */ - --bg: #2d2523; - --bg-pattern: #241e1c; + /* Espresso-night variant */ + --bg: #2d2523; + --bg-pattern: #241e1c; - --dither-ink: rgba(255, 247, 242, 0.12); - --dither-opacity: 0.14; - --surface: #3b312f; - --surface-alt: #463a37; + --dither-ink: rgba(255, 247, 242, 0.12); + --dither-opacity: 0.14; + --surface: #3b312f; + --surface-alt: #463a37; - --fg: #f9efea; - --muted: #cbb8b1; + --fg: #f9efea; + --muted: #cbb8b1; - --border: #f1e6e0; - --border-light: #6e5b57; - --border-dark: #120d0c; + --border: #f1e6e0; + --border-light: #6e5b57; + --border-dark: #120d0c; - --primary: #f06aa6; - --primary-light: #ff97c8; - --primary-dark: #d94b8e; + --primary: #f06aa6; + --primary-light: #ff97c8; + --primary-dark: #d94b8e; - --secondary: #b69cff; - --secondary-light: #d7c8ff; - --secondary-dark: #8f78d6; + --secondary: #b69cff; + --secondary-light: #d7c8ff; + --secondary-dark: #8f78d6; - --accent-brown: #a57353; - --accent-brown-light: #c9936a; - --accent-brown-dark: #6b4a34; + --accent-brown: #a57353; + --accent-brown-light: #c9936a; + --accent-brown-dark: #6b4a34; - --titlebar-a: #b04a80; - --titlebar-b: #4f245e; - --titlebar-fg: #fff7f2; + --titlebar-a: #b04a80; + --titlebar-b: #4f245e; + --titlebar-fg: #fff7f2; - --link: #ff97c8; - --link-visited: #d7c8ff; - --link-active: #fff0f7; + --link: #ff97c8; + --link-visited: #d7c8ff; + --link-active: #fff0f7; - /* Status colors (brightened for dark mode) */ - --success: #4caf50; - --error: #ff6b6b; - --warning: #ffa500; - --info: #64b5f6; + /* Status colors (brightened for dark mode) */ + --success: #4caf50; + --error: #ff6b6b; + --warning: #ffa500; + --info: #64b5f6; - --shadow-color: rgba(0, 0, 0, 0.75); - --highlight: rgba(255, 255, 255, 0.2); - --lowlight: rgba(0, 0, 0, 0.85); + --shadow-color: rgba(0, 0, 0, 0.75); + --highlight: rgba(255, 255, 255, 0.2); + --lowlight: rgba(0, 0, 0, 0.85); } @font-face { - font-family: "Mono"; - src: url("../assets/fonts/Maple.woff2") format("woff2"); - font-display: swap; - font-weight: normal; + font-family: "Mono"; + src: url("../assets/fonts/Maple.woff2") format("woff2"); + font-display: swap; + font-weight: normal; } @supports (font-synthesis: none) { - @font-face { - font-family: "Mono"; - src: url("../assets/fonts/Maple.woff2") format("woff2"); - font-synthesis: none; - } + @font-face { + font-family: "Mono"; + src: url("../assets/fonts/Maple.woff2") format("woff2"); + font-synthesis: none; + } } /* base */ * { - box-sizing: border-box; - margin: 0; - padding: 0; - cursor: - url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 24" width="32" height="24"><path d="M1 3h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v2H9v1h1v2h1v2h-1v1H8v-1H7v-2H6v-2H5v1H4v1H3v1H1" fill="%23ff69b4" stroke="%23b19cd9" stroke-width="0.5"/><path d="M2 5h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1H8v2h1v2h1v2H8v-2H7v-2H6v-1H5v1H4v1H3v1H2" fill="white" stroke="%23ff69b4" stroke-width="0.5"/></svg>') - 0 0, - auto !important; + box-sizing: border-box; + margin: 0; + padding: 0; + cursor: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 24" width="32" height="24"><path d="M1 3h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v2H9v1h1v2h1v2h-1v1H8v-1H7v-2H6v-2H5v1H4v1H3v1H1" fill="%23ff69b4" stroke="%23b19cd9" stroke-width="0.5"/><path d="M2 5h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1H8v2h1v2h1v2H8v-2H7v-2H6v-1H5v1H4v1H3v1H2" fill="white" stroke="%23ff69b4" stroke-width="0.5"/></svg>') + 0 0, auto !important; } ::selection { - background: var(--primary); - color: var(--surface-alt); + background: var(--primary); + color: var(--surface-alt); } :focus-visible { - outline: 1px dotted var(--fg); - outline-offset: 2px; + outline: 1px dotted var(--fg); + outline-offset: 2px; } html { - font-size: 16px; - background-color: var(--bg); - position: relative; - line-height: 1; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - height: 100vh; + font-size: 16px; + background-color: var(--bg); + position: relative; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + height: 100vh; } html::before { - content: ""; - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-image: linear-gradient( - 45deg, - var(--dither-ink) 25%, - transparent 25%, - transparent 75%, - var(--dither-ink) 75% - ), - linear-gradient( - 45deg, - var(--dither-ink) 25%, - transparent 25%, - transparent 75%, - var(--dither-ink) 75% - ), - url("../assets/img/bg.png"); - background-size: - 10px 10px, - 10px 10px, - auto; - background-position: - 0 0, - 5px 5px, - 0 0; - background-repeat: repeat; - opacity: var(--dither-opacity); - z-index: -1; - pointer-events: none; + content: ""; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-image: linear-gradient( + 45deg, + var(--dither-ink) 25%, + transparent 25%, + transparent 75%, + var(--dither-ink) 75% + ), + linear-gradient( + 45deg, + var(--dither-ink) 25%, + transparent 25%, + transparent 75%, + var(--dither-ink) 75% + ), url("../assets/img/bg.png"); + background-size: 10px 10px, 10px 10px, auto; + background-position: 0 0, 5px 5px, 0 0; + background-repeat: repeat; + opacity: var(--dither-opacity); + z-index: -1; + pointer-events: none; } body { - background: transparent; - color: var(--fg); - line-height: var(--line-height); - padding: var(--space-md); - padding-top: calc(var(--space-md) + 3rem); - max-width: var(--content-max-width); - margin: 0 auto; - font-family: var(--font-mono); - scrollbar-color: var(--accent-lavender) var(--bg); - scrollbar-width: thin; - display: flex; - flex-direction: column; - height: 100vh; + background: transparent; + color: var(--fg); + line-height: var(--line-height); + padding: var(--space-md); + padding-top: calc(var(--space-md) + 3rem); + max-width: var(--content-max-width); + margin: 0 auto; + font-family: var(--font-mono); + scrollbar-color: var(--accent-lavender) var(--bg); + scrollbar-width: thin; + display: flex; + flex-direction: column; + height: 100vh; } /* webkit scrollbar (chrome, safari, edge) */ ::-webkit-scrollbar { - width: 12px; + width: 12px; } ::-webkit-scrollbar-track { - background: var(--bg); - border: 2px solid var(--border); - box-shadow: - inset 1px 1px 0 var(--border-light), - inset -1px -1px 0 var(--border-dark); + background: var(--bg); + border: 2px solid var(--border); + box-shadow: inset 1px 1px 0 var(--border-light), inset -1px -1px 0 var(--border-dark); } ::-webkit-scrollbar-thumb { - background: var(--accent-lavender); - border: 2px outset var(--border-light); - box-shadow: - inset 1px 1px 0 var(--highlight), - inset -1px -1px 0 var(--lowlight); + background: var(--accent-lavender); + border: 2px outset var(--border-light); + box-shadow: inset 1px 1px 0 var(--highlight), inset -1px -1px 0 var(--lowlight); } ::-webkit-scrollbar-thumb:hover { - background: var(--accent-pink); + background: var(--accent-pink); } .div-centered { - max-width: var(--content-max-width); - margin: auto; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - min-height: calc(100vh - 4.5rem); - text-align: justify; + max-width: var(--content-max-width); + margin: auto; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + min-height: calc(100vh - 4.5rem); + text-align: justify; } /* typography */ @@ -276,169 +264,163 @@ h3, h4, h5, h6 { - font-family: var(--font-mono); - font-weight: 700; - line-height: 1.15; - margin-bottom: var(--space-md); - color: var(--fg); + font-family: var(--font-mono); + font-weight: 700; + line-height: 1.15; + margin-bottom: var(--space-md); + color: var(--fg); } h1 { - font-size: 2rem; - margin-bottom: var(--space-lg); - background: linear-gradient(90deg, var(--titlebar-a), var(--titlebar-b)); + font-size: 2rem; + margin-bottom: var(--space-lg); + background: linear-gradient(90deg, var(--titlebar-a), var(--titlebar-b)); } /* "Titlebar" headings: retro, but still cute */ h1, h2, h3 { - padding: 0.55rem 0.75rem; - border: var(--border-width) solid var(--border); - box-shadow: - inset 1px 1px 0 var(--border-light), - inset -1px -1px 0 var(--border-dark); - color: var(--titlebar-fg); - text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.25); + padding: 0.55rem 0.75rem; + border: var(--border-width) solid var(--border); + box-shadow: inset 1px 1px 0 var(--border-light), inset -1px -1px 0 var(--border-dark); + color: var(--titlebar-fg); + text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.25); } h2 { - font-size: 1.5rem; - background: linear-gradient(90deg, var(--titlebar-a), var(--titlebar-b)); + font-size: 1.5rem; + background: linear-gradient(90deg, var(--titlebar-a), var(--titlebar-b)); } h3 { - font-size: 1.25rem; - background: linear-gradient(90deg, var(--accent-brown-dark), var(--accent-brown)); + font-size: 1.25rem; + background: linear-gradient(90deg, var(--accent-brown-dark), var(--accent-brown)); } h4 { - font-size: 1.1rem; + font-size: 1.1rem; } h5 { - font-size: 1rem; + font-size: 1rem; } h6 { - font-size: 0.9rem; + font-size: 0.9rem; } p { - margin-bottom: var(--space-sm); + margin-bottom: var(--space-sm); } a { - color: var(--link); - text-decoration: underline; - text-decoration-thickness: 2px; - text-underline-offset: 2px; + color: var(--link); + text-decoration: underline; + text-decoration-thickness: 2px; + text-underline-offset: 2px; } a:visited { - color: var(--link-visited); + color: var(--link-visited); } a:hover { - background: var(--primary-light); - color: var(--fg); + background: var(--primary-light); + color: var(--fg); } a:active { - color: var(--link-active); + color: var(--link-active); } small { - color: var(--muted); - font-size: 0.875rem; + color: var(--muted); + font-size: 0.875rem; } blockquote { - border-left: 3px solid var(--accent-brown); - padding-left: var(--space-md); - margin: var(--space-md) 0; - font-style: italic; - color: var(--muted); + border-left: 3px solid var(--accent-brown); + padding-left: var(--space-md); + margin: var(--space-md) 0; + font-style: italic; + color: var(--muted); } /* lists */ ul, ol { - margin: var(--space-sm) 0; - padding-left: var(--space-lg); + margin: var(--space-sm) 0; + padding-left: var(--space-lg); } li { - margin-bottom: var(--space-xs); + margin-bottom: var(--space-xs); } /* tables */ table { - width: 100%; - border-collapse: collapse; - margin: var(--space-md) 0; - border: var(--border-width) solid var(--border); - background: var(--surface); - box-shadow: - inset 1px 1px 0 var(--border-light), - inset -1px -1px 0 var(--border-dark), - 3px 3px 0 rgba(0, 0, 0, 0.12); + width: 100%; + border-collapse: collapse; + margin: var(--space-md) 0; + border: var(--border-width) solid var(--border); + background: var(--surface); + box-shadow: inset 1px 1px 0 var(--border-light), inset -1px -1px 0 var(--border-dark), 3px 3px 0 + rgba(0, 0, 0, 0.12); } thead { - background: linear-gradient(90deg, var(--titlebar-a), var(--titlebar-b)); - color: var(--titlebar-fg); - font-weight: 700; + background: linear-gradient(90deg, var(--titlebar-a), var(--titlebar-b)); + color: var(--titlebar-fg); + font-weight: 700; } th, td { - padding: var(--space-xs) var(--space-sm); - border: 1px solid var(--border-dark); - text-align: left; + padding: var(--space-xs) var(--space-sm); + border: 1px solid var(--border-dark); + text-align: left; } tbody tr:nth-child(odd) { - background: var(--surface-alt); + background: var(--surface-alt); } tbody tr:nth-child(even) { - background: var(--surface); + background: var(--surface); } tbody tr:hover { - background: color-mix(in srgb, var(--primary) 12%, transparent); + background: color-mix(in srgb, var(--primary) 12%, transparent); } /* code */ code { - background: var(--surface-alt); - padding: 0.25rem 0.5rem; - border: 1px solid var(--border-dark); - font-size: 0.875rem; - font-family: var(--font-mono); + background: var(--surface-alt); + padding: 0.25rem 0.5rem; + border: 1px solid var(--border-dark); + font-size: 0.875rem; + font-family: var(--font-mono); } pre { - background: var(--surface-alt); - border: var(--border-width) solid var(--border); - padding: var(--space-md); - margin: var(--space-md) 0; - overflow-x: auto; - box-shadow: - inset 1px 1px 0 var(--border-light), - inset -1px -1px 0 var(--border-dark), - 3px 3px 0 rgba(0, 0, 0, 0.12); + background: var(--surface-alt); + border: var(--border-width) solid var(--border); + padding: var(--space-md); + margin: var(--space-md) 0; + overflow-x: auto; + box-shadow: inset 1px 1px 0 var(--border-light), inset -1px -1px 0 var(--border-dark), 3px 3px 0 + rgba(0, 0, 0, 0.12); } pre code { - background: transparent; - border: none; - padding: 0; - font-size: 0.875rem; + background: transparent; + border: none; + padding: 0; + font-size: 0.875rem; } /* Prism.js syntax highlighting */ @@ -447,12 +429,12 @@ pre code { .token.prolog, .token.doctype, .token.cdata { - color: var(--muted); - font-style: italic; + color: var(--muted); + font-style: italic; } .token.punctuation { - color: var(--fg); + color: var(--fg); } .token.property, @@ -461,7 +443,7 @@ pre code { .token.number, .token.constant, .token.symbol { - color: var(--secondary-dark); + color: var(--secondary-dark); } .token.selector, @@ -469,7 +451,7 @@ pre code { .token.string, .token.char, .token.builtin { - color: var(--accent-brown-dark); + color: var(--accent-brown-dark); } .token.operator, @@ -477,183 +459,179 @@ pre code { .token.url, .language-css .token.string, .style .token.string { - color: var(--primary-dark); + color: var(--primary-dark); } .token.atrule, .token.attr-value, .token.keyword { - color: var(--primary-dark); - font-weight: 700; + color: var(--primary-dark); + font-weight: 700; } .token.function, .token.class-name { - color: var(--primary); - font-weight: 700; + color: var(--primary); + font-weight: 700; } .token.regex, .token.important, .token.variable { - color: var(--accent-brown); + color: var(--accent-brown); } .token.important, .token.bold { - font-weight: bold; + font-weight: bold; } .token.italic { - font-style: italic; + font-style: italic; } /* Dark theme variants */ [data-theme="dark"] .token.keyword, [data-theme="dark"] .token.operator { - color: var(--primary-light); + color: var(--primary-light); } [data-theme="dark"] .token.string, [data-theme="dark"] .token.selector { - color: var(--accent-brown-light); + color: var(--accent-brown-light); } [data-theme="dark"] .token.number, [data-theme="dark"] .token.constant { - color: var(--secondary-light); + color: var(--secondary-light); } /* dividers */ hr { - border: none; - border-top: 2px solid var(--border-dark); - margin: var(--space-lg) 0; - height: 0; + border: none; + border-top: 2px solid var(--border-dark); + margin: var(--space-lg) 0; + height: 0; } /* layout */ header { - position: fixed; - top: 0; - left: 0; - right: 0; - z-index: 1000; - border-bottom: 2px solid var(--border); - padding: 0.45rem var(--space-md); - margin-bottom: 0; - background: linear-gradient(90deg, var(--titlebar-a), var(--titlebar-b)); - color: var(--titlebar-fg); - display: flex; - align-items: center; - justify-content: space-between; - gap: var(--space-md); - box-shadow: - inset 1px 1px 0 var(--border-light), - inset -1px -1px 0 var(--border-dark), - 0 2px 0 rgba(0, 0, 0, 0.08); + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 1000; + border-bottom: 2px solid var(--border); + padding: 0.45rem var(--space-md); + margin-bottom: 0; + background: linear-gradient(90deg, var(--titlebar-a), var(--titlebar-b)); + color: var(--titlebar-fg); + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-md); + box-shadow: inset 1px 1px 0 var(--border-light), inset -1px -1px 0 var(--border-dark), 0 2px 0 + rgba(0, 0, 0, 0.08); } header h1 { - display: none; + display: none; } header::before { - content: ""; - display: inline-block; - width: 1.5rem; - height: 1.5rem; - background-image: url("../assets/img/coffee.svg"); - background-size: 1.1rem 1.1rem; - background-position: center; - background-repeat: no-repeat; - margin-right: 0.5rem; - background-color: var(--surface); - border: 2px outset var(--border-light); - box-shadow: - inset 1px 1px 0 var(--highlight), - inset -1px -1px 0 var(--lowlight); + content: ""; + display: inline-block; + width: 1.5rem; + height: 1.5rem; + background-image: url("../assets/img/coffee.svg"); + background-size: 1.1rem 1.1rem; + background-position: center; + background-repeat: no-repeat; + margin-right: 0.5rem; + background-color: var(--surface); + border: 2px outset var(--border-light); + box-shadow: inset 1px 1px 0 var(--highlight), inset -1px -1px 0 var(--lowlight); } nav { - display: flex; - gap: var(--space-md); - flex-wrap: wrap; - align-items: center; - margin: 0; - flex: 1; + display: flex; + gap: var(--space-md); + flex-wrap: wrap; + align-items: center; + margin: 0; + flex: 1; } nav a { - font-size: 0.85rem; - padding: 0.1rem 0.15rem; - color: var(--titlebar-fg); - text-decoration: none; - border-bottom: 2px solid color-mix(in srgb, var(--titlebar-fg) 55%, transparent); + font-size: 0.85rem; + padding: 0.1rem 0.15rem; + color: var(--titlebar-fg); + text-decoration: none; + border-bottom: 2px solid color-mix(in srgb, var(--titlebar-fg) 55%, transparent); } nav a:visited { - color: var(--titlebar-fg); + color: var(--titlebar-fg); } nav a:hover { - background: color-mix(in srgb, var(--surface-alt) 18%, transparent); - border-bottom-color: var(--primary-light); + background: color-mix(in srgb, var(--surface-alt) 18%, transparent); + border-bottom-color: var(--primary-light); } nav a:active { - background: color-mix(in srgb, var(--surface-alt) 28%, transparent); - border-bottom-color: var(--primary); + background: color-mix(in srgb, var(--surface-alt) 28%, transparent); + border-bottom-color: var(--primary); } main { - flex: 1; + flex: 1; } article { - border: var(--border-width) solid var(--border); - padding: var(--space-md); - margin-bottom: var(--space-lg); - background: var(--surface); - box-shadow: - inset 1px 1px 0 var(--border-light), - inset -1px -1px 0 var(--border-dark), - 4px 4px 0 rgba(0, 0, 0, 0.14), - 5px 5px 0 rgba(0, 0, 0, 0.06); + border: var(--border-width) solid var(--border); + padding: var(--space-md); + margin-bottom: var(--space-lg); + background: var(--surface); + box-shadow: + inset 1px 1px 0 var(--border-light), + inset -1px -1px 0 var(--border-dark), + 4px 4px 0 rgba(0, 0, 0, 0.14), + 5px 5px 0 rgba(0, 0, 0, 0.06); } article > :last-child { - margin-bottom: 0; + margin-bottom: 0; } article h2 { - margin: calc(var(--space-md) * -1) calc(var(--space-md) * -1) var(--space-md); - border-left: none; - border-right: none; - border-top: none; - border-bottom: var(--border-width) solid var(--border); - box-shadow: none; - text-shadow: none; + margin: calc(var(--space-md) * -1) calc(var(--space-md) * -1) var(--space-md); + border-left: none; + border-right: none; + border-top: none; + border-bottom: var(--border-width) solid var(--border); + box-shadow: none; + text-shadow: none; } footer { - border: var(--border-width) solid var(--border); - padding: var(--space-md); - text-align: center; - background: var(--surface); - font-size: 0.875rem; - box-shadow: - inset 1px 1px 0 var(--border-light), - inset -1px -1px 0 var(--border-dark), - 4px 4px 0 rgba(0, 0, 0, 0.14), - 5px 5px 0 rgba(0, 0, 0, 0.06); + border: var(--border-width) solid var(--border); + padding: var(--space-md); + text-align: center; + background: var(--surface); + font-size: 0.875rem; + box-shadow: + inset 1px 1px 0 var(--border-light), + inset -1px -1px 0 var(--border-dark), + 4px 4px 0 rgba(0, 0, 0, 0.14), + 5px 5px 0 rgba(0, 0, 0, 0.06); } footer > :last-child { - margin-bottom: 0; + margin-bottom: 0; } /* components */ @@ -663,615 +641,608 @@ footer > :last-child { input, textarea, select { - font-family: var(--font-mono); - padding: var(--space-xs) var(--space-sm); - border: 2px inset var(--border-light); - background: var(--surface-alt); - color: var(--fg); - font-size: 0.875rem; - box-shadow: - inset 1px 1px 0 var(--lowlight), - inset -1px -1px 0 var(--highlight); - transition: border-color 0.15s, box-shadow 0.15s; - width: 100%; - max-width: 100%; + font-family: var(--font-mono); + padding: var(--space-xs) var(--space-sm); + border: 2px inset var(--border-light); + background: var(--surface-alt); + color: var(--fg); + font-size: 0.875rem; + box-shadow: inset 1px 1px 0 var(--lowlight), inset -1px -1px 0 var(--highlight); + transition: border-color 0.15s, box-shadow 0.15s; + width: 100%; + max-width: 100%; } input::placeholder, textarea::placeholder, select::placeholder { - color: var(--muted); - opacity: 0.8; + color: var(--muted); + opacity: 0.8; } [data-theme="dark"] input::placeholder, [data-theme="dark"] textarea::placeholder, [data-theme="dark"] select::placeholder { - color: #b8b0a0; - opacity: 1; + color: #b8b0a0; + opacity: 1; } input:focus, textarea:focus, select:focus { - outline: 1px dotted var(--fg); - outline-offset: 2px; - border-color: var(--primary); - box-shadow: - inset 1px 1px 0 var(--lowlight), - inset -1px -1px 0 var(--highlight), - 0 0 0 2px var(--primary); + outline: 1px dotted var(--fg); + outline-offset: 2px; + border-color: var(--primary); + box-shadow: inset 1px 1px 0 var(--lowlight), inset -1px -1px 0 var(--highlight), 0 0 0 2px + var(--primary); } input:invalid, textarea:invalid { - border-color: var(--error); + border-color: var(--error); } input:invalid:focus, textarea:invalid:focus { - box-shadow: - inset 1px 1px 0 var(--highlight), - inset -1px -1px 0 var(--lowlight), - 0 0 0 2px var(--error); + box-shadow: inset 1px 1px 0 var(--highlight), inset -1px -1px 0 var(--lowlight), 0 0 0 2px + var(--error); } textarea { - resize: vertical; - min-height: 4rem; - width: 100%; + resize: vertical; + min-height: 4rem; + width: 100%; } select { - cursor: pointer; - appearance: none; - background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%232a1f1d' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); - background-repeat: no-repeat; - background-position: right 0.5rem center; - background-size: 1.2em; - padding-right: 2.5rem; + cursor: pointer; + appearance: none; + background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%232a1f1d' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 0.5rem center; + background-size: 1.2em; + padding-right: 2.5rem; } [data-theme="dark"] select { - background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23f9efea' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23f9efea' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); } option { - background: var(--bg); - color: var(--fg); - padding: var(--space-sm); + background: var(--bg); + color: var(--fg); + padding: var(--space-sm); } option:checked { - background: linear-gradient(var(--primary), var(--primary)); - color: var(--bg); + background: linear-gradient(var(--primary), var(--primary)); + color: var(--bg); } input[type="checkbox"], input[type="radio"] { - appearance: none; - width: 1.3rem; - height: 1.3rem; - min-width: 1.3rem; - min-height: 1.3rem; - padding: 0; - cursor: pointer; - margin: 0 0.5rem 0 0; - vertical-align: middle; - border: 2px inset var(--border-light); - box-shadow: - inset 1px 1px 0 var(--lowlight), - inset -1px -1px 0 var(--highlight); - background: var(--surface-alt); - display: inline-flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - font-weight: normal; + appearance: none; + width: 1.3rem; + height: 1.3rem; + min-width: 1.3rem; + min-height: 1.3rem; + padding: 0; + cursor: pointer; + margin: 0 0.5rem 0 0; + vertical-align: middle; + border: 2px inset var(--border-light); + box-shadow: inset 1px 1px 0 var(--lowlight), inset -1px -1px 0 var(--highlight); + background: var(--surface-alt); + display: inline-flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + font-weight: normal; } input[type="radio"] { - border-radius: 50%; + border-radius: 50%; } input[type="checkbox"]:checked, input[type="radio"]:checked { - background: var(--primary); - border-style: inset; + background: var(--primary); + border-style: inset; } label { - display: block; - margin-bottom: var(--space-xs); - font-weight: bold; - color: var(--fg); + display: block; + margin-bottom: var(--space-xs); + font-weight: bold; + color: var(--fg); } input[type="checkbox"] + label, input[type="radio"] + label { - display: inline; - margin: 0; - font-weight: normal; + display: inline; + margin: 0; + font-weight: normal; } /* toggle switches */ input[type="checkbox"].toggle { - appearance: none; - width: 2.8rem; - height: 1.6rem; - padding: 0; - margin: 0 0.5rem 0 0; - border: 2px outset var(--border-light); - background: var(--muted); - cursor: pointer; - position: relative; - vertical-align: middle; - box-shadow: - inset 1px 1px 0 rgba(255, 255, 255, 0.3), - inset -1px -1px 0 rgba(0, 0, 0, 0.6); - transition: background 0.2s; + appearance: none; + width: 2.8rem; + height: 1.6rem; + padding: 0; + margin: 0 0.5rem 0 0; + border: 2px outset var(--border-light); + background: var(--muted); + cursor: pointer; + position: relative; + vertical-align: middle; + box-shadow: inset 1px 1px 0 rgba(255, 255, 255, 0.3), inset -1px -1px 0 rgba(0, 0, 0, 0.6); + transition: background 0.2s; } input[type="checkbox"].toggle:checked { - background: var(--primary); + background: var(--primary); } input[type="checkbox"].toggle::after { - content: ""; - position: absolute; - width: 0.6rem; - height: 0.6rem; - background: var(--surface); - border: 2px outset var(--border-light); - top: 50%; - transform: translateY(-50%); - left: 0.2rem; - transition: left 0.2s; - box-shadow: - inset 1px 1px 0 var(--highlight), - inset -1px -1px 0 var(--lowlight); + content: ""; + position: absolute; + width: 0.6rem; + height: 0.6rem; + background: var(--surface); + border: 2px outset var(--border-light); + top: 50%; + transform: translateY(-50%); + left: 0.2rem; + transition: left 0.2s; + box-shadow: inset 1px 1px 0 var(--highlight), inset -1px -1px 0 var(--lowlight); } input[type="checkbox"].toggle:checked::after { - left: 1.5rem; + left: 1.5rem; } [data-theme="dark"] input[type="checkbox"].toggle { - background: var(--secondary); + background: var(--secondary); } [data-theme="dark"] input[type="checkbox"].toggle:checked { - background: var(--primary); + background: var(--primary); } [data-theme="dark"] input[type="checkbox"].toggle::after { - background: var(--accent-brown-light); + background: var(--accent-brown-light); } #theme-toggle::before { - content: "☾"; - position: absolute; - right: 0.4rem; - top: 50%; - transform: translateY(-50%); - font-size: 0.9rem; - line-height: 1; - color: var(--titlebar-fg); - transition: opacity 0.2s; + content: "☾"; + position: absolute; + right: 0.4rem; + top: 50%; + transform: translateY(-50%); + font-size: 0.9rem; + line-height: 1; + color: var(--titlebar-fg); + transition: opacity 0.2s; } #theme-toggle:checked::before { - content: "☀"; - right: auto; - left: 0.4rem; + content: "☀"; + right: auto; + left: 0.4rem; } /* range sliders */ input[type="range"] { - width: 100%; - height: 2.2rem; - background: transparent; - cursor: pointer; - appearance: none; - vertical-align: middle; + width: 100%; + height: 2.2rem; + background: transparent; + cursor: pointer; + appearance: none; + vertical-align: middle; } input[type="range"]::-webkit-slider-thumb { - appearance: none; - width: 1.5rem; - height: 1rem; - margin-top: -0.25rem; - background: linear-gradient(135deg, var(--primary-light) 0%, var(--primary) 50%, var(--primary-dark) 100%); - border: 2px outset var(--border-light); - cursor: pointer; - box-shadow: - inset 1px 1px 0 var(--highlight), - inset -1px -1px 0 var(--lowlight), - 2px 2px 0 rgba(0, 0, 0, 0.3), - 3px 3px 0 rgba(0, 0, 0, 0.1); + appearance: none; + width: 1.5rem; + height: 1rem; + margin-top: -0.25rem; + background: linear-gradient( + 135deg, + var(--primary-light) 0%, + var(--primary) 50%, + var(--primary-dark) 100% + ); + border: 2px outset var(--border-light); + cursor: pointer; + box-shadow: + inset 1px 1px 0 var(--highlight), + inset -1px -1px 0 var(--lowlight), + 2px 2px 0 rgba(0, 0, 0, 0.3), + 3px 3px 0 rgba(0, 0, 0, 0.1); } input[type="range"]::-webkit-slider-thumb:active { - box-shadow: - inset 1px 1px 0 var(--lowlight), - inset -1px -1px 0 var(--highlight), - 1px 1px 0 rgba(0, 0, 0, 0.2); + box-shadow: inset 1px 1px 0 var(--lowlight), inset -1px -1px 0 var(--highlight), 1px 1px 0 + rgba(0, 0, 0, 0.2); } input[type="range"]::-moz-range-thumb { - width: 1.5rem; - height: 0.8rem; - margin-top: -0.15rem; - background: linear-gradient(135deg, var(--primary-light) 0%, var(--primary) 50%, var(--primary-dark) 100%); - border: 2px outset var(--border-light); - cursor: pointer; - box-shadow: - inset 1px 1px 0 var(--highlight), - inset -1px -1px 0 var(--lowlight), - 2px 2px 0 rgba(0, 0, 0, 0.3), - 3px 3px 0 rgba(0, 0, 0, 0.1); - border-radius: 0.3rem; + width: 1.5rem; + height: 0.8rem; + margin-top: -0.15rem; + background: linear-gradient( + 135deg, + var(--primary-light) 0%, + var(--primary) 50%, + var(--primary-dark) 100% + ); + border: 2px outset var(--border-light); + cursor: pointer; + box-shadow: + inset 1px 1px 0 var(--highlight), + inset -1px -1px 0 var(--lowlight), + 2px 2px 0 rgba(0, 0, 0, 0.3), + 3px 3px 0 rgba(0, 0, 0, 0.1); + border-radius: 0.3rem; } input[type="range"]::-moz-range-thumb:active { - box-shadow: - inset 1px 1px 0 var(--lowlight), - inset -1px -1px 0 var(--highlight), - 1px 1px 0 rgba(0, 0, 0, 0.2); + box-shadow: inset 1px 1px 0 var(--lowlight), inset -1px -1px 0 var(--highlight), 1px 1px 0 + rgba(0, 0, 0, 0.2); } input[type="range"]::-webkit-slider-runnable-track { - background: linear-gradient(180deg, - #c0c0c0 0%, - #e8e8e8 1px, - #dfdfdf 2px, - #d0d0d0 100%); - border: 2px inset var(--border-light); - height: 0.8rem; - box-shadow: - inset 2px 2px 0 rgba(255, 255, 255, 0.8), - inset -2px -2px 0 rgba(0, 0, 0, 0.4), - inset 1px 1px 0 rgba(0, 0, 0, 0.1); + background: linear-gradient(180deg, #c0c0c0 0%, #e8e8e8 1px, #dfdfdf 2px, #d0d0d0 100%); + border: 2px inset var(--border-light); + height: 0.8rem; + box-shadow: inset 2px 2px 0 rgba(255, 255, 255, 0.8), inset -2px -2px 0 rgba(0, 0, 0, 0.4), + inset 1px 1px 0 rgba(0, 0, 0, 0.1); } input[type="range"]::-moz-range-track { - background: transparent; - border: none; + background: transparent; + border: none; } input[type="range"]::-moz-range-progress { - background: linear-gradient(180deg, - var(--secondary-light) 0%, - var(--secondary) 50%, - var(--secondary-dark) 100%); - height: 0.8rem; - border: 2px inset var(--border-light); - box-shadow: - inset 2px 2px 0 rgba(255, 255, 255, 0.6), - inset -2px -2px 0 rgba(0, 0, 0, 0.3); + background: linear-gradient( + 180deg, + var(--secondary-light) 0%, + var(--secondary) 50%, + var(--secondary-dark) 100% + ); + height: 0.8rem; + border: 2px inset var(--border-light); + box-shadow: inset 2px 2px 0 rgba(255, 255, 255, 0.6), inset -2px -2px 0 rgba(0, 0, 0, 0.3); } fieldset { - border: 2px solid var(--border-light); - padding: var(--space-md); - margin: var(--space-md) 0; - background: var(--surface); - box-shadow: - inset 1px 1px 0 rgba(255, 255, 255, 0.3), - inset -1px -1px 0 rgba(0, 0, 0, 0.6); + border: 2px solid var(--border-light); + padding: var(--space-md); + margin: var(--space-md) 0; + background: var(--surface); + box-shadow: inset 1px 1px 0 rgba(255, 255, 255, 0.3), inset -1px -1px 0 rgba(0, 0, 0, 0.6); } fieldset > :last-child { - margin-bottom: 0; + margin-bottom: 0; } legend { - padding: 0 var(--space-xs); - margin-left: calc(var(--space-xs) * -1); - font-weight: bold; - color: var(--fg); - background: var(--surface); + padding: 0 var(--space-xs); + margin-left: calc(var(--space-xs) * -1); + font-weight: bold; + color: var(--fg); + background: var(--surface); } /* buttons */ button { - font-family: var(--font-mono); - font-weight: 700; - padding: var(--space-xs) var(--space-sm); - border: 2px outset var(--border-light); - background: var(--surface); - color: var(--fg); - font-size: 0.875rem; - box-shadow: - inset 1px 1px 0 var(--highlight), - inset -1px -1px 0 var(--lowlight), - 2px 2px 0 rgba(0, 0, 0, 0.18); - transition: - transform 0.05s, - box-shadow 0.05s, - background 0.1s; - cursor: pointer; + font-family: var(--font-mono); + font-weight: 700; + padding: var(--space-xs) var(--space-sm); + border: 2px outset var(--border-light); + background: var(--surface); + color: var(--fg); + font-size: 0.875rem; + box-shadow: inset 1px 1px 0 var(--highlight), inset -1px -1px 0 var(--lowlight), 2px 2px 0 + rgba(0, 0, 0, 0.18); + transition: transform 0.05s, box-shadow 0.05s, background 0.1s; + cursor: pointer; } button:hover { - background: var(--surface-alt); + background: var(--surface-alt); } button:active { - transform: translate(1px, 1px); - border-style: inset; - box-shadow: - inset 1px 1px 0 var(--lowlight), - inset -1px -1px 0 var(--highlight); + transform: translate(1px, 1px); + border-style: inset; + box-shadow: inset 1px 1px 0 var(--lowlight), inset -1px -1px 0 var(--highlight); } /* button variants */ button.primary { - background: var(--primary); - color: var(--surface-alt); + background: var(--primary); + color: var(--surface-alt); } button.primary:hover { - background: var(--primary-light); + background: var(--primary-light); } button.primary:active { - background: var(--primary-dark); + background: var(--primary-dark); } button.secondary { - background: var(--secondary); - color: var(--fg); + background: var(--secondary); + color: var(--fg); } button.secondary:hover { - background: var(--secondary-light); + background: var(--secondary-light); } button.secondary:active { - background: var(--secondary-dark); + background: var(--secondary-dark); } button.success, button.error, button.warning, button.info { - color: var(--surface-alt); + color: var(--surface-alt); } button.success { - background: var(--success); + background: var(--success); } button.error { - background: var(--error); + background: var(--error); } button.warning { - background: var(--warning); + background: var(--warning); } button.info { - background: var(--info); + background: var(--info); } button.success:hover, button.error:hover, button.warning:hover, button.info:hover { - filter: brightness(1.06); + filter: brightness(1.06); } button.contrast { - background: var(--fg); - color: var(--surface-alt); + background: var(--fg); + color: var(--surface-alt); } button.contrast:hover { - background: var(--muted); + background: var(--muted); } button:disabled { - opacity: 0.6; - cursor: not-allowed; - transform: none !important; + opacity: 0.6; + cursor: not-allowed; + transform: none !important; } /* button groups */ .button-group { - display: inline-flex; - border: var(--border-width) solid var(--border); - box-shadow: - inset 1px 1px 0 var(--border-light), - inset -1px -1px 0 var(--border-dark), - 3px 3px 0 rgba(0, 0, 0, 0.12); + display: inline-flex; + border: var(--border-width) solid var(--border); + box-shadow: inset 1px 1px 0 var(--border-light), inset -1px -1px 0 var(--border-dark), 3px 3px 0 + rgba(0, 0, 0, 0.12); } .button-group button { - border: none; - margin: 0; - border-radius: 0; - box-shadow: none; + border: none; + margin: 0; + border-radius: 0; + box-shadow: none; } .button-group button:not(:last-child) { - border-right: 1px solid var(--border-dark); + border-right: 1px solid var(--border-dark); } .button-group button:hover { - box-shadow: none; - transform: none; + box-shadow: none; + transform: none; } .button-group button:active { - box-shadow: none; - transform: none; + box-shadow: none; + transform: none; } /* alerts & messages */ .alert { - padding: var(--space-md); - margin: var(--space-md) 0; - border: var(--border-width) solid var(--border); - border-left: 4px solid var(--info); - background: var(--surface); - box-shadow: - inset 1px 1px 0 var(--border-light), - inset -1px -1px 0 var(--border-dark), - 3px 3px 0 rgba(0, 0, 0, 0.12); + padding: var(--space-md); + margin: var(--space-md) 0; + border: var(--border-width) solid var(--border); + border-left: 4px solid var(--info); + background: var(--surface); + box-shadow: inset 1px 1px 0 var(--border-light), inset -1px -1px 0 var(--border-dark), 3px 3px 0 + rgba(0, 0, 0, 0.12); } .alert.success { - border-left-color: var(--success); - background-color: color-mix(in srgb, var(--success) 10%, transparent); + border-left-color: var(--success); + background-color: color-mix(in srgb, var(--success) 10%, transparent); } .alert.error { - border-left-color: var(--error); - background-color: color-mix(in srgb, var(--error) 10%, transparent); + border-left-color: var(--error); + background-color: color-mix(in srgb, var(--error) 10%, transparent); } .alert.warning { - border-left-color: var(--warning); - background-color: color-mix(in srgb, var(--warning) 10%, transparent); + border-left-color: var(--warning); + background-color: color-mix(in srgb, var(--warning) 10%, transparent); } .alert.info { - border-left-color: var(--info); - background-color: color-mix(in srgb, var(--info) 10%, transparent); + border-left-color: var(--info); + background-color: color-mix(in srgb, var(--info) 10%, transparent); } /* badges & pills */ .badge { - display: inline-block; - padding: 0.25rem 0.5rem; - margin: 0 0.25rem; - background: var(--surface-alt); - color: var(--fg); - font-size: 0.75rem; - font-weight: 700; - border: 1px solid var(--border-dark); - box-shadow: - inset 1px 1px 0 var(--border-light), - inset -1px -1px 0 var(--border-dark); + display: inline-block; + padding: 0.25rem 0.5rem; + margin: 0 0.25rem; + background: var(--surface-alt); + color: var(--fg); + font-size: 0.75rem; + font-weight: 700; + border: 1px solid var(--border-dark); + box-shadow: inset 1px 1px 0 var(--border-light), inset -1px -1px 0 var(--border-dark); } .badge.primary { - background: var(--primary); - color: var(--surface-alt); + background: var(--primary); + color: var(--surface-alt); } .badge.success { - background: var(--success); - color: var(--surface-alt); + background: var(--success); + color: var(--surface-alt); } .badge.error { - background: var(--error); - color: var(--surface-alt); + background: var(--error); + color: var(--surface-alt); } .badge.warning { - background: var(--warning); - color: var(--surface-alt); + background: var(--warning); + color: var(--surface-alt); } .badge.info { - background: var(--info); - color: var(--surface-alt); + background: var(--info); + color: var(--surface-alt); } .badge.contrast { - background: var(--fg); - color: var(--surface-alt); + background: var(--fg); + color: var(--surface-alt); } /* helpers */ .text-success { - color: var(--success); - font-weight: bold; + color: var(--success); + font-weight: bold; } .text-error { - color: var(--error); - font-weight: bold; + color: var(--error); + font-weight: bold; } .text-warning { - color: var(--warning); - font-weight: bold; + color: var(--warning); + font-weight: bold; } .text-info { - color: var(--info); - font-weight: bold; + color: var(--info); + font-weight: bold; } .text-muted { - color: var(--muted); + color: var(--muted); } .text-contrast { - color: var(--fg); - font-weight: bold; + color: var(--fg); + font-weight: bold; } .text-center { - text-align: center; + text-align: center; } .text-right { - text-align: right; + text-align: right; } .text-left { - text-align: left; + text-align: left; } -.mt-xs { margin-top: var(--space-xs); } -.mt-sm { margin-top: var(--space-sm); } -.mt-md { margin-top: var(--space-md); } -.mt-lg { margin-top: var(--space-lg); } -.mt-xl { margin-top: var(--space-xl); } +.mt-xs { + margin-top: var(--space-xs); +} +.mt-sm { + margin-top: var(--space-sm); +} +.mt-md { + margin-top: var(--space-md); +} +.mt-lg { + margin-top: var(--space-lg); +} +.mt-xl { + margin-top: var(--space-xl); +} -.mb-xs { margin-bottom: var(--space-xs); } -.mb-sm { margin-bottom: var(--space-sm); } -.mb-md { margin-bottom: var(--space-md); } -.mb-lg { margin-bottom: var(--space-lg); } -.mb-xl { margin-bottom: var(--space-xl); } +.mb-xs { + margin-bottom: var(--space-xs); +} +.mb-sm { + margin-bottom: var(--space-sm); +} +.mb-md { + margin-bottom: var(--space-md); +} +.mb-lg { + margin-bottom: var(--space-lg); +} +.mb-xl { + margin-bottom: var(--space-xl); +} .mx-auto { - margin-left: auto; - margin-right: auto; + margin-left: auto; + margin-right: auto; } .py-md { - padding-top: var(--space-md); - padding-bottom: var(--space-md); + padding-top: var(--space-md); + padding-bottom: var(--space-md); } #theme-toggle { - margin-left: auto; - margin-bottom: 0; + margin-left: auto; + margin-bottom: 0; } /* fairy dust */ @keyframes fairy-float { - 0% { - opacity: 1; - transform: translateY(0) scale(1); - } - 100% { - opacity: 0; - transform: translateY(40px) scale(0.5); - } + 0% { + opacity: 1; + transform: translateY(0) scale(1); + } + 100% { + opacity: 0; + transform: translateY(40px) scale(0.5); + } } diff --git a/src/index.html b/src/index.html index c5f83a2..b20e703 100644 --- a/src/index.html +++ b/src/index.html @@ -1,599 +1,582 @@ <!doctype html> <html lang="en"> - <head> - <meta charset="UTF-8" /> - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> - <title>Liz CSS - Framework Demo</title> - <link rel="stylesheet" href="/bundle.css" /> - <link rel="icon" href="/img/favicon.ico" /> - <style> - .demo-section { - margin-bottom: var(--space-xl); - } - - .component-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); - gap: var(--space-md); - margin: var(--space-md) 0; - } - - .component-box { - border: var(--border-width) solid var(--border); - padding: var(--space-md); - background: var(--surface); - box-shadow: - inset 1px 1px 0 var(--border-light), - inset -1px -1px 0 var(--border-dark), - var(--shadow-box); - } - - .component-box > :last-child { - margin-bottom: 0; - } - - .component-label { - display: block; - font-size: 0.75rem; - color: var(--muted); - margin-bottom: var(--space-xs); - text-transform: uppercase; - letter-spacing: 0.05em; - } - - .button-row { - display: flex; - gap: var(--space-sm); - flex-wrap: wrap; - margin: var(--space-md) 0; - } - - .form-group { - margin-bottom: var(--space-md); - } - - code { - background: var(--bg); - padding: 0.25rem 0.5rem; - border: 1px solid var(--border-light); - font-size: 0.875rem; - font-family: var(--font-mono); - } - - .color-swatch { - display: inline-block; - width: 40px; - height: 40px; - border: var(--border-width) solid var(--border); - margin-right: var(--space-sm); - vertical-align: middle; - box-shadow: - inset 1px 1px 0 var(--border-light), - inset -1px -1px 0 var(--border-dark), - var(--shadow-sm); - } - </style> - </head> - <body> - <header> - <nav> - <a href="/">Home</a> - <a href="/demo.html">Components</a> - </nav> - <input type="checkbox" id="theme-toggle" class="toggle" aria-label="Toggle dark mode" /> - </header> - - <main> - <!-- Color Palette --> - <article> - <h2>Color Palette</h2> - <p>A retro-themed minimal CSS framework with carefully selected colors for light and dark modes.</p> - - <h3>Primary Colors</h3> - <div class="component-grid"> - <div class="component-box"> - <span class="component-label">Primary</span> - <div style="display: flex; align-items: center;"> - <div - class="color-swatch" - style="background: var(--primary);" - ></div> - <code>#e56aa6</code> - </div> - </div> - <div class="component-box"> - <span class="component-label">Primary Light</span> - <div style="display: flex; align-items: center;"> - <div - class="color-swatch" - style="background: var(--primary-light);" - ></div> - <code>#f08dbe</code> - </div> - </div> - <div class="component-box"> - <span class="component-label">Primary Dark</span> - <div style="display: flex; align-items: center;"> - <div - class="color-swatch" - style="background: var(--primary-dark);" - ></div> - <code>#c84d86</code> - </div> - </div> - </div> - - <h3>Secondary Colors</h3> - <div class="component-grid"> - <div class="component-box"> - <span class="component-label">Secondary</span> - <div style="display: flex; align-items: center;"> - <div - class="color-swatch" - style="background: var(--secondary);" - ></div> - <code>#b69cff</code> - </div> - </div> - <div class="component-box"> - <span class="component-label">Secondary Light</span> - <div style="display: flex; align-items: center;"> - <div - class="color-swatch" - style="background: var(--secondary-light);" - ></div> - <code>#d7c8ff</code> - </div> - </div> - <div class="component-box"> - <span class="component-label">Secondary Dark</span> - <div style="display: flex; align-items: center;"> - <div - class="color-swatch" - style="background: var(--secondary-dark);" - ></div> - <code>#8f78d6</code> - </div> - </div> - </div> - - <h3>Status Colors</h3> - <div class="component-grid"> - <div class="component-box"> - <span class="component-label">Success</span> - <div style="display: flex; align-items: center;"> - <div - class="color-swatch" - style="background: var(--success);" - ></div> - <code>#2e8b57</code> - </div> - </div> - <div class="component-box"> - <span class="component-label">Error</span> - <div style="display: flex; align-items: center;"> - <div class="color-swatch" style="background: var(--error);"></div> - <code>#b3261e</code> - </div> - </div> - <div class="component-box"> - <span class="component-label">Warning</span> - <div style="display: flex; align-items: center;"> - <div - class="color-swatch" - style="background: var(--warning);" - ></div> - <code>#b7791f</code> - </div> - </div> - <div class="component-box"> - <span class="component-label">Info</span> - <div style="display: flex; align-items: center;"> - <div class="color-swatch" style="background: var(--info);"></div> - <code>#2b6cb0</code> - </div> - </div> - </div> - </article> - - <!-- Buttons --> - <article> - <h2>Buttons</h2> - <p>Retro-styled buttons with multiple variants and states.</p> - - <h3>Basic Buttons</h3> - <div class="button-row"> - <button>Default (Secondary)</button> - <button class="primary">Primary</button> - <button class="secondary">Secondary</button> - <button class="contrast">Contrast</button> - </div> - - <h3>Status Buttons</h3> - <div class="button-row"> - <button class="success">Success</button> - <button class="error">Error</button> - <button class="warning">Warning</button> - <button class="info">Info</button> - </div> - - <h3>Button States</h3> - <div class="button-row"> - <button>Normal</button> - <button disabled>Disabled</button> - </div> - </article> - - <!-- Forms --> - <article> - <h2>Forms</h2> - <p>Complete form styling with retro aesthetics.</p> - - <form> - <div class="form-group"> - <label for="text-input">Text Input</label> - <input - type="text" - id="text-input" - placeholder="Enter some text..." - /> - </div> - - <div class="form-group"> - <label for="email-input">Email Input</label> - <input - type="email" - id="email-input" - placeholder="you@example.com" - /> - </div> - - <div class="form-group"> - <label for="number-input">Number Input</label> - <input type="number" id="number-input" placeholder="42" /> - </div> - - <div class="form-group"> - <label for="textarea">Textarea</label> - <textarea - id="textarea" - placeholder="Write your message here..." - ></textarea> - </div> - - <div class="form-group"> - <label for="select">Select Dropdown</label> - <select id="select"> - <option>Choose an option</option> - <option>Option 1</option> - <option>Option 2</option> - <option>Option 3</option> - </select> - </div> - - <fieldset> - <legend>Checkboxes</legend> - <div class="form-group"> - <input type="checkbox" id="check1" /> - <label for="check1" style="display: inline; margin: 0;"> - Checkbox 1 - </label> - </div> - <div class="form-group"> - <input type="checkbox" id="check2" /> - <label for="check2" style="display: inline; margin: 0;"> - Checkbox 2 - </label> - </div> - </fieldset> - - <fieldset> - <legend>Radio Buttons</legend> - <div class="form-group"> - <input type="radio" id="radio1" name="radio-group" /> - <label for="radio1" style="display: inline; margin: 0;"> - Option A - </label> - </div> - <div class="form-group"> - <input type="radio" id="radio2" name="radio-group" /> - <label for="radio2" style="display: inline; margin: 0;"> - Option B - </label> - </div> - </fieldset> - - <div class="button-row mt-md"> - <button type="submit" class="primary">Submit</button> - <button type="reset" class="secondary">Reset</button> - <button type="button" class="contrast">Cancel</button> - </div> - </form> - </article> - - <!-- Alerts --> - <article> - <h2>Alerts</h2> - <p>Status messages and notifications with semantic colors.</p> - - <div class="alert info"> - <strong>ℹ️ Info:</strong> This is an informational message. - </div> - - <div class="alert success"> - <strong>✓ Success:</strong> Operation completed successfully! - </div> - - <div class="alert warning"> - <strong>⚠️ Warning:</strong> Please review this before proceeding. - </div> - - <div class="alert error"> - <strong>✗ Error:</strong> Something went wrong. Please try again. - </div> - </article> - - <!-- Badges --> - <article> - <h2>Badges & Pills</h2> - <p>Small inline elements for labels, tags, and status indicators.</p> - - <h3>Default Badges</h3> - <p> - <span class="badge">Default</span> - <span class="badge primary">Primary</span> - <span class="badge secondary">Secondary</span> - <span class="badge contrast">Contrast</span> - </p> - - <h3>Status Badges</h3> - <p> - <span class="badge success">✓ Success</span> - <span class="badge error">✗ Error</span> - <span class="badge warning">⚠️ Warning</span> - <span class="badge info">ℹ️ Info</span> - </p> - </article> - - <!-- Typography & Helpers --> - <article> - <h2>Typography & Helper Classes</h2> - <p>Text utilities and spacing helpers for quick styling.</p> - - <h3>Text Colors</h3> - <p> - Default text <span class="text-muted">(muted)</span> - <span class="text-success">success text</span> - <span class="text-error">error text</span> - <span class="text-warning">warning text</span> - <span class="text-info">info text</span> - <span class="text-contrast">contrast text</span> - </p> - - <h3>Text Alignment</h3> - <p class="text-left">Left aligned</p> - <p class="text-center">Center aligned</p> - <p class="text-right">Right aligned</p> - - <h3>Spacing Helpers</h3> - <p> - Use <code>.mt-xs</code>, <code>.mt-sm</code>, <code>.mt-md</code>, - <code>.mt-lg</code>, <code>.mt-xl</code> for margin-top. - </p> - <p> - Use <code>.mb-xs</code>, <code>.mb-sm</code>, <code>.mb-md</code>, - <code>.mb-lg</code>, <code>.mb-xl</code> for margin-bottom. - </p> - <p> - Use <code>.mx-auto</code> for horizontal centering and - <code>.py-md</code> for vertical padding. - </p> - - <div class="component-box mt-md mb-lg mx-auto" style="max-width: 400px;"> - <p class="text-center">Example: Component with margin and padding utilities</p> - </div> - </article> - - <!-- Headings --> - <article> - <h2>Heading Styles</h2> - <h1>Heading 1</h1> - <h2>Heading 2</h2> - <h3>Heading 3</h3> - <h4>Heading 4</h4> - <h5>Heading 5</h5> - <h6>Heading 6</h6> - </article> - - <!-- Lists --> - <article> - <h2>Lists</h2> - - <h3>Unordered List</h3> - <ul> - <li>Item one with some text</li> - <li>Item two with more text</li> - <li>Item three with even more text</li> - <li>Nested lists: - <ul> - <li>Sub-item one</li> - <li>Sub-item two</li> - </ul> - </li> - </ul> - - <h3>Ordered List</h3> - <ol> - <li>First step in the process</li> - <li>Second step follows naturally</li> - <li>Third step completes the sequence</li> - </ol> - </article> - - <!-- Blockquotes --> - <article> - <h2>Blockquotes</h2> - <blockquote> - "The best time to plant a tree was 20 years ago. The second best time is now." - <br /> - <small>— Chinese Proverb</small> - </blockquote> - </article> - - <!-- Links --> - <article> - <h2>Links</h2> - <p> - This is a <a href="#demo">hyperlink</a> styled with the framework. Links - have a bottom border and change color on hover. You can use them - <a href="#inline">inline within text</a> or as standalone elements. - </p> - </article> - - <!-- Tables --> - <article> - <h2>Tables</h2> - <p>Tables with retro styling, alternating row colors, and hover effects.</p> - <table> - <thead> - <tr> - <th>Feature</th> - <th>Light Mode</th> - <th>Dark Mode</th> - </tr> - </thead> - <tbody> - <tr> - <td>Background</td> - <td>Warm cream</td> - <td>Warm taupe</td> - </tr> - <tr> - <td>Buttons</td> - <td>Lavender primary</td> - <td>Rose primary</td> - </tr> - <tr> - <td>Borders</td> - <td>Retro 90s beveled</td> - <td>Retro 90s beveled</td> - </tr> - <tr> - <td>Accents</td> - <td>Pink & brown</td> - <td>Pink & brown</td> - </tr> - </tbody> - </table> - </article> - - <!-- Code --> - <article> - <h2>Code</h2> - <p>Inline <code>const x = 42;</code> looks different from block code:</p> - <pre><code class="language-javascript">function retro() { + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>Liz CSS - Framework Demo</title> + <script> + window.ASSET_BASE = 'ASSET_BASE_PLACEHOLDER'; + </script> + <link rel="stylesheet" href="/bundle.css" /> + <link rel="icon" href="/img/favicon.ico" /> + <style> + .demo-section { + margin-bottom: var(--space-xl); + } + + .component-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: var(--space-md); + margin: var(--space-md) 0; + } + + .component-box { + border: var(--border-width) solid var(--border); + padding: var(--space-md); + background: var(--surface); + box-shadow: + inset 1px 1px 0 var(--border-light), + inset -1px -1px 0 var(--border-dark), + var(--shadow-box); + } + + .component-box > :last-child { + margin-bottom: 0; + } + + .component-label { + display: block; + font-size: 0.75rem; + color: var(--muted); + margin-bottom: var(--space-xs); + text-transform: uppercase; + letter-spacing: 0.05em; + } + + .button-row { + display: flex; + gap: var(--space-sm); + flex-wrap: wrap; + margin: var(--space-md) 0; + } + + .form-group { + margin-bottom: var(--space-md); + } + + code { + background: var(--bg); + padding: 0.25rem 0.5rem; + border: 1px solid var(--border-light); + font-size: 0.875rem; + font-family: var(--font-mono); + } + + .color-swatch { + display: inline-block; + width: 40px; + height: 40px; + border: var(--border-width) solid var(--border); + margin-right: var(--space-sm); + vertical-align: middle; + box-shadow: + inset 1px 1px 0 var(--border-light), + inset -1px -1px 0 var(--border-dark), + var(--shadow-sm); + } + </style> + </head> + <body> + <header> + <nav> + <a href="/">Home</a> + <a href="/demo.html">Components</a> + </nav> + <input type="checkbox" id="theme-toggle" class="toggle" aria-label="Toggle dark mode" /> + </header> + + <main> + <!-- Color Palette --> + <article> + <h2>Color Palette</h2> + <p> + A retro-themed minimal CSS framework with carefully selected colors for light + and dark modes. + </p> + + <h3>Primary Colors</h3> + <div class="component-grid"> + <div class="component-box"> + <span class="component-label">Primary</span> + <div style="display: flex; align-items: center"> + <div class="color-swatch" style="background: var(--primary)"></div> + <code>#e56aa6</code> + </div> + </div> + <div class="component-box"> + <span class="component-label">Primary Light</span> + <div style="display: flex; align-items: center"> + <div + class="color-swatch" + style="background: var(--primary-light)" + ></div> + <code>#f08dbe</code> + </div> + </div> + <div class="component-box"> + <span class="component-label">Primary Dark</span> + <div style="display: flex; align-items: center"> + <div class="color-swatch" style="background: var(--primary-dark)"></div> + <code>#c84d86</code> + </div> + </div> + </div> + + <h3>Secondary Colors</h3> + <div class="component-grid"> + <div class="component-box"> + <span class="component-label">Secondary</span> + <div style="display: flex; align-items: center"> + <div class="color-swatch" style="background: var(--secondary)"></div> + <code>#b69cff</code> + </div> + </div> + <div class="component-box"> + <span class="component-label">Secondary Light</span> + <div style="display: flex; align-items: center"> + <div + class="color-swatch" + style="background: var(--secondary-light)" + ></div> + <code>#d7c8ff</code> + </div> + </div> + <div class="component-box"> + <span class="component-label">Secondary Dark</span> + <div style="display: flex; align-items: center"> + <div + class="color-swatch" + style="background: var(--secondary-dark)" + ></div> + <code>#8f78d6</code> + </div> + </div> + </div> + + <h3>Status Colors</h3> + <div class="component-grid"> + <div class="component-box"> + <span class="component-label">Success</span> + <div style="display: flex; align-items: center"> + <div class="color-swatch" style="background: var(--success)"></div> + <code>#2e8b57</code> + </div> + </div> + <div class="component-box"> + <span class="component-label">Error</span> + <div style="display: flex; align-items: center"> + <div class="color-swatch" style="background: var(--error)"></div> + <code>#b3261e</code> + </div> + </div> + <div class="component-box"> + <span class="component-label">Warning</span> + <div style="display: flex; align-items: center"> + <div class="color-swatch" style="background: var(--warning)"></div> + <code>#b7791f</code> + </div> + </div> + <div class="component-box"> + <span class="component-label">Info</span> + <div style="display: flex; align-items: center"> + <div class="color-swatch" style="background: var(--info)"></div> + <code>#2b6cb0</code> + </div> + </div> + </div> + </article> + + <!-- Buttons --> + <article> + <h2>Buttons</h2> + <p>Retro-styled buttons with multiple variants and states.</p> + + <h3>Basic Buttons</h3> + <div class="button-row"> + <button>Default (Secondary)</button> + <button class="primary">Primary</button> + <button class="secondary">Secondary</button> + <button class="contrast">Contrast</button> + </div> + + <h3>Status Buttons</h3> + <div class="button-row"> + <button class="success">Success</button> + <button class="error">Error</button> + <button class="warning">Warning</button> + <button class="info">Info</button> + </div> + + <h3>Button States</h3> + <div class="button-row"> + <button>Normal</button> + <button disabled>Disabled</button> + </div> + </article> + + <!-- Forms --> + <article> + <h2>Forms</h2> + <p>Complete form styling with retro aesthetics.</p> + + <form> + <div class="form-group"> + <label for="text-input">Text Input</label> + <input type="text" id="text-input" placeholder="Enter some text..." /> + </div> + + <div class="form-group"> + <label for="email-input">Email Input</label> + <input type="email" id="email-input" placeholder="you@example.com" /> + </div> + + <div class="form-group"> + <label for="number-input">Number Input</label> + <input type="number" id="number-input" placeholder="42" /> + </div> + + <div class="form-group"> + <label for="textarea">Textarea</label> + <textarea id="textarea" placeholder="Write your message here..."></textarea> + </div> + + <div class="form-group"> + <label for="select">Select Dropdown</label> + <select id="select"> + <option>Choose an option</option> + <option>Option 1</option> + <option>Option 2</option> + <option>Option 3</option> + </select> + </div> + + <fieldset> + <legend>Checkboxes</legend> + <div class="form-group"> + <input type="checkbox" id="check1" /> + <label for="check1" style="display: inline; margin: 0"> + Checkbox 1 + </label> + </div> + <div class="form-group"> + <input type="checkbox" id="check2" /> + <label for="check2" style="display: inline; margin: 0"> + Checkbox 2 + </label> + </div> + </fieldset> + + <fieldset> + <legend>Radio Buttons</legend> + <div class="form-group"> + <input type="radio" id="radio1" name="radio-group" /> + <label for="radio1" style="display: inline; margin: 0"> + Option A + </label> + </div> + <div class="form-group"> + <input type="radio" id="radio2" name="radio-group" /> + <label for="radio2" style="display: inline; margin: 0"> + Option B + </label> + </div> + </fieldset> + + <div class="button-row mt-md"> + <button type="submit" class="primary">Submit</button> + <button type="reset" class="secondary">Reset</button> + <button type="button" class="contrast">Cancel</button> + </div> + </form> + </article> + + <!-- Alerts --> + <article> + <h2>Alerts</h2> + <p>Status messages and notifications with semantic colors.</p> + + <div class="alert info"> + <strong>ℹ️ Info:</strong> This is an informational message. + </div> + + <div class="alert success"> + <strong>✓ Success:</strong> Operation completed successfully! + </div> + + <div class="alert warning"> + <strong>⚠️ Warning:</strong> Please review this before proceeding. + </div> + + <div class="alert error"> + <strong>✗ Error:</strong> Something went wrong. Please try again. + </div> + </article> + + <!-- Badges --> + <article> + <h2>Badges & Pills</h2> + <p>Small inline elements for labels, tags, and status indicators.</p> + + <h3>Default Badges</h3> + <p> + <span class="badge">Default</span> + <span class="badge primary">Primary</span> + <span class="badge secondary">Secondary</span> + <span class="badge contrast">Contrast</span> + </p> + + <h3>Status Badges</h3> + <p> + <span class="badge success">✓ Success</span> + <span class="badge error">✗ Error</span> + <span class="badge warning">⚠️ Warning</span> + <span class="badge info">ℹ️ Info</span> + </p> + </article> + + <!-- Typography & Helpers --> + <article> + <h2>Typography & Helper Classes</h2> + <p>Text utilities and spacing helpers for quick styling.</p> + + <h3>Text Colors</h3> + <p> + Default text <span class="text-muted">(muted)</span> + <span class="text-success">success text</span> + <span class="text-error">error text</span> + <span class="text-warning">warning text</span> + <span class="text-info">info text</span> + <span class="text-contrast">contrast text</span> + </p> + + <h3>Text Alignment</h3> + <p class="text-left">Left aligned</p> + <p class="text-center">Center aligned</p> + <p class="text-right">Right aligned</p> + + <h3>Spacing Helpers</h3> + <p> + Use <code>.mt-xs</code>, <code>.mt-sm</code>, <code>.mt-md</code>, + <code>.mt-lg</code>, <code>.mt-xl</code> for margin-top. + </p> + <p> + Use <code>.mb-xs</code>, <code>.mb-sm</code>, <code>.mb-md</code>, + <code>.mb-lg</code>, <code>.mb-xl</code> for margin-bottom. + </p> + <p> + Use <code>.mx-auto</code> for horizontal centering and <code>.py-md</code> for + vertical padding. + </p> + + <div class="component-box mt-md mb-lg mx-auto" style="max-width: 400px"> + <p class="text-center">Example: Component with margin and padding utilities</p> + </div> + </article> + + <!-- Headings --> + <article> + <h2>Heading Styles</h2> + <h1>Heading 1</h1> + <h2>Heading 2</h2> + <h3>Heading 3</h3> + <h4>Heading 4</h4> + <h5>Heading 5</h5> + <h6>Heading 6</h6> + </article> + + <!-- Lists --> + <article> + <h2>Lists</h2> + + <h3>Unordered List</h3> + <ul> + <li>Item one with some text</li> + <li>Item two with more text</li> + <li>Item three with even more text</li> + <li> + Nested lists: + <ul> + <li>Sub-item one</li> + <li>Sub-item two</li> + </ul> + </li> + </ul> + + <h3>Ordered List</h3> + <ol> + <li>First step in the process</li> + <li>Second step follows naturally</li> + <li>Third step completes the sequence</li> + </ol> + </article> + + <!-- Blockquotes --> + <article> + <h2>Blockquotes</h2> + <blockquote> + "The best time to plant a tree was 20 years ago. The second best time is now." + <br /> + <small>— Chinese Proverb</small> + </blockquote> + </article> + + <!-- Links --> + <article> + <h2>Links</h2> + <p> + This is a <a href="#demo">hyperlink</a> styled with the framework. Links have a + bottom border and change color on hover. You can use them + <a href="#inline">inline within text</a> or as standalone elements. + </p> + </article> + + <!-- Tables --> + <article> + <h2>Tables</h2> + <p>Tables with retro styling, alternating row colors, and hover effects.</p> + <table> + <thead> + <tr> + <th>Feature</th> + <th>Light Mode</th> + <th>Dark Mode</th> + </tr> + </thead> + <tbody> + <tr> + <td>Background</td> + <td>Warm cream</td> + <td>Warm taupe</td> + </tr> + <tr> + <td>Buttons</td> + <td>Lavender primary</td> + <td>Rose primary</td> + </tr> + <tr> + <td>Borders</td> + <td>Retro 90s beveled</td> + <td>Retro 90s beveled</td> + </tr> + <tr> + <td>Accents</td> + <td>Pink & brown</td> + <td>Pink & brown</td> + </tr> + </tbody> + </table> + </article> + + <!-- Code --> + <article> + <h2>Code</h2> + <p>Inline <code>const x = 42;</code> looks different from block code:</p> + <pre><code class="language-javascript">function retro() { return "That's totally rad!"; } retro(); // "That's totally rad!"</code></pre> - </article> - - <!-- Dividers --> - <article> - <h2>Dividers</h2> - <p>Use <code><hr></code> to separate content sections:</p> - <hr /> - <p>Content after the divider looks fresh and organized.</p> - </article> - - <!-- Form Validation --> - <article> - <h2>Form Validation</h2> - <p>Invalid inputs show a red border when they fail validation.</p> - <form> - <div class="form-group"> - <label for="valid-email">Valid Email</label> - <input - type="email" - id="valid-email" - placeholder="you@example.com" - value="user@example.com" - /> - </div> - <div class="form-group"> - <label for="invalid-email">Invalid Email</label> - <input - type="email" - id="invalid-email" - placeholder="you@example.com" - value="not-an-email" - /> - </div> - <div class="form-group"> - <label for="required-field">Required Field (empty = invalid)</label> - <input - type="text" - id="required-field" - placeholder="This field is required" - required - /> - </div> - </form> - </article> - - <!-- Button Groups --> - <article> - <h2>Button Groups</h2> - <p>Combine buttons into a group with the <code>.button-group</code> class:</p> - <div class="button-group mt-md"> - <button class="primary">First</button> - <button class="secondary">Second</button> - <button class="contrast">Third</button> - </div> - </article> - - <!-- Toggle Switches --> - <article> - <h2>Toggle Switches</h2> - <p>Use checkboxes with the <code>.toggle</code> class to create stylish toggle switches:</p> - <div class="form-group mt-md"> - <input type="checkbox" id="toggle1" class="toggle" /> - <label for="toggle1">Dark mode</label> - </div> - <div class="form-group"> - <input type="checkbox" id="toggle2" class="toggle" checked /> - <label for="toggle2">Notifications enabled</label> - </div> - <div class="form-group"> - <input type="checkbox" id="toggle3" class="toggle" /> - <label for="toggle3">Auto-save</label> - </div> - </article> - - <!-- Range Sliders --> - <article> - <h2>Range Sliders</h2> - <p>Native range inputs styled with retro beveled appearance:</p> - <div class="form-group mt-md"> - <label for="volume">Volume</label> - <input type="range" id="volume" min="0" max="100" value="50" /> - </div> - <div class="form-group"> - <label for="brightness">Brightness</label> - <input type="range" id="brightness" min="0" max="100" value="75" /> - </div> - <div class="form-group"> - <label for="saturation">Saturation</label> - <input type="range" id="saturation" min="0" max="100" value="25" /> - </div> - </article> - - </main> - - <footer> - <p>© 2025 Liz CSS Framework. Made with coffee and retro vibes.</p> - </footer> - - <script src="/bundle.js"></script> - </body> + </article> + + <!-- Dividers --> + <article> + <h2>Dividers</h2> + <p>Use <code><hr></code> to separate content sections:</p> + <hr /> + <p>Content after the divider looks fresh and organized.</p> + </article> + + <!-- Form Validation --> + <article> + <h2>Form Validation</h2> + <p>Invalid inputs show a red border when they fail validation.</p> + <form> + <div class="form-group"> + <label for="valid-email">Valid Email</label> + <input + type="email" + id="valid-email" + placeholder="you@example.com" + value="user@example.com" + /> + </div> + <div class="form-group"> + <label for="invalid-email">Invalid Email</label> + <input + type="email" + id="invalid-email" + placeholder="you@example.com" + value="not-an-email" + /> + </div> + <div class="form-group"> + <label for="required-field">Required Field (empty = invalid)</label> + <input + type="text" + id="required-field" + placeholder="This field is required" + required + /> + </div> + </form> + </article> + + <!-- Button Groups --> + <article> + <h2>Button Groups</h2> + <p>Combine buttons into a group with the <code>.button-group</code> class:</p> + <div class="button-group mt-md"> + <button class="primary">First</button> + <button class="secondary">Second</button> + <button class="contrast">Third</button> + </div> + </article> + + <!-- Toggle Switches --> + <article> + <h2>Toggle Switches</h2> + <p> + Use checkboxes with the <code>.toggle</code> class to create stylish toggle + switches: + </p> + <div class="form-group mt-md"> + <input type="checkbox" id="toggle1" class="toggle" /> + <label for="toggle1">Dark mode</label> + </div> + <div class="form-group"> + <input type="checkbox" id="toggle2" class="toggle" checked /> + <label for="toggle2">Notifications enabled</label> + </div> + <div class="form-group"> + <input type="checkbox" id="toggle3" class="toggle" /> + <label for="toggle3">Auto-save</label> + </div> + </article> + + <!-- Range Sliders --> + <article> + <h2>Range Sliders</h2> + <p>Native range inputs styled with retro beveled appearance:</p> + <div class="form-group mt-md"> + <label for="volume">Volume</label> + <input type="range" id="volume" min="0" max="100" value="50" /> + </div> + <div class="form-group"> + <label for="brightness">Brightness</label> + <input type="range" id="brightness" min="0" max="100" value="75" /> + </div> + <div class="form-group"> + <label for="saturation">Saturation</label> + <input type="range" id="saturation" min="0" max="100" value="25" /> + </div> + </article> + </main> + + <footer> + <p>© 2025 Liz CSS Framework. Made with coffee and retro vibes.</p> + </footer> + + <script src="/bundle.js"></script> + </body> </html> diff --git a/src/js/oneko.js b/src/js/oneko.js deleted file mode 100644 index dcf927f..0000000 --- a/src/js/oneko.js +++ /dev/null @@ -1,284 +0,0 @@ -// oneko.js: https://github.com/adryd325/oneko.js - -export function initOneko() { - const isReducedMotion = - window.matchMedia(`(prefers-reduced-motion: reduce)`) === true || - window.matchMedia(`(prefers-reduced-motion: reduce)`).matches === true; - - if (isReducedMotion) return; - - const nekoEl = document.createElement("div"); - let persistPosition = true; - - let nekoPosX = 32; - let nekoPosY = 32; - - let mousePosX = 0; - let mousePosY = 0; - - let frameCount = 0; - let idleTime = 0; - let idleAnimation = null; - let idleAnimationFrame = 0; - - const nekoSpeed = 10; - const spriteSets = { - idle: [[-3, -3]], - alert: [[-7, -3]], - scratchSelf: [ - [-5, 0], - [-6, 0], - [-7, 0], - ], - scratchWallN: [ - [0, 0], - [0, -1], - ], - scratchWallS: [ - [-7, -1], - [-6, -2], - ], - scratchWallE: [ - [-2, -2], - [-2, -3], - ], - scratchWallW: [ - [-4, 0], - [-4, -1], - ], - tired: [[-3, -2]], - sleeping: [ - [-2, 0], - [-2, -1], - ], - N: [ - [-1, -2], - [-1, -3], - ], - NE: [ - [0, -2], - [0, -3], - ], - E: [ - [-3, 0], - [-3, -1], - ], - SE: [ - [-5, -1], - [-5, -2], - ], - S: [ - [-6, -3], - [-7, -2], - ], - SW: [ - [-5, -3], - [-6, -1], - ], - W: [ - [-4, -2], - [-4, -3], - ], - NW: [ - [-1, 0], - [-1, -1], - ], - }; - - function init() { - let nekoFile = "/oneko/oneko.gif"; - const curScript = document.currentScript; - if (curScript && curScript.dataset.cat) { - nekoFile = curScript.dataset.cat; - } - if (curScript && curScript.dataset.persistPosition) { - if (curScript.dataset.persistPosition === "") { - persistPosition = true; - } else { - persistPosition = JSON.parse( - curScript.dataset.persistPosition.toLowerCase(), - ); - } - } - - if (persistPosition) { - let storedNeko = JSON.parse(window.localStorage.getItem("oneko")); - if (storedNeko !== null) { - nekoPosX = storedNeko.nekoPosX; - nekoPosY = storedNeko.nekoPosY; - mousePosX = storedNeko.mousePosX; - mousePosY = storedNeko.mousePosY; - frameCount = storedNeko.frameCount; - idleTime = storedNeko.idleTime; - idleAnimation = storedNeko.idleAnimation; - idleAnimationFrame = storedNeko.idleAnimationFrame; - nekoEl.style.backgroundPosition = storedNeko.bgPos; - } - } - - nekoEl.id = "oneko"; - nekoEl.ariaHidden = true; - nekoEl.style.width = "32px"; - nekoEl.style.height = "32px"; - nekoEl.style.position = "fixed"; - nekoEl.style.pointerEvents = "none"; - nekoEl.style.imageRendering = "pixelated"; - nekoEl.style.left = `${nekoPosX - 16}px`; - nekoEl.style.top = `${nekoPosY - 16}px`; - nekoEl.style.zIndex = 2147483647; - - nekoEl.style.backgroundImage = `url(${nekoFile})`; - - document.body.appendChild(nekoEl); - - document.addEventListener("mousemove", function (event) { - mousePosX = event.clientX; - mousePosY = event.clientY; - }); - - if (persistPosition) { - window.addEventListener("beforeunload", function (event) { - window.localStorage.setItem( - "oneko", - JSON.stringify({ - nekoPosX: nekoPosX, - nekoPosY: nekoPosY, - mousePosX: mousePosX, - mousePosY: mousePosY, - frameCount: frameCount, - idleTime: idleTime, - idleAnimation: idleAnimation, - idleAnimationFrame: idleAnimationFrame, - bgPos: nekoEl.style.backgroundPosition, - }), - ); - }); - } - - window.requestAnimationFrame(onAnimationFrame); - } - - let lastFrameTimestamp; - - function onAnimationFrame(timestamp) { - // Stops execution if the neko element is removed from DOM - if (!nekoEl.isConnected) { - return; - } - if (!lastFrameTimestamp) { - lastFrameTimestamp = timestamp; - } - if (timestamp - lastFrameTimestamp > 100) { - lastFrameTimestamp = timestamp; - frame(); - } - window.requestAnimationFrame(onAnimationFrame); - } - - function setSprite(name, frame) { - const sprite = spriteSets[name][frame % spriteSets[name].length]; - nekoEl.style.backgroundPosition = `${sprite[0] * 32}px ${sprite[1] * 32}px`; - } - - function resetIdleAnimation() { - idleAnimation = null; - idleAnimationFrame = 0; - } - - function idle() { - idleTime += 1; - - // every ~ 20 seconds - if ( - idleTime > 10 && - Math.floor(Math.random() * 200) == 0 && - idleAnimation == null - ) { - let avalibleIdleAnimations = ["sleeping", "scratchSelf"]; - if (nekoPosX < 32) { - avalibleIdleAnimations.push("scratchWallW"); - } - if (nekoPosY < 32) { - avalibleIdleAnimations.push("scratchWallN"); - } - if (nekoPosX > window.innerWidth - 32) { - avalibleIdleAnimations.push("scratchWallE"); - } - if (nekoPosY > window.innerHeight - 32) { - avalibleIdleAnimations.push("scratchWallS"); - } - idleAnimation = - avalibleIdleAnimations[ - Math.floor(Math.random() * avalibleIdleAnimations.length) - ]; - } - - switch (idleAnimation) { - case "sleeping": - if (idleAnimationFrame < 8) { - setSprite("tired", 0); - break; - } - setSprite("sleeping", Math.floor(idleAnimationFrame / 4)); - if (idleAnimationFrame > 192) { - resetIdleAnimation(); - } - break; - case "scratchWallN": - case "scratchWallS": - case "scratchWallE": - case "scratchWallW": - case "scratchSelf": - setSprite(idleAnimation, idleAnimationFrame); - if (idleAnimationFrame > 9) { - resetIdleAnimation(); - } - break; - default: - setSprite("idle", 0); - return; - } - idleAnimationFrame += 1; - } - - function frame() { - frameCount += 1; - const diffX = nekoPosX - mousePosX; - const diffY = nekoPosY - mousePosY; - const distance = Math.sqrt(diffX ** 2 + diffY ** 2); - - if (distance < nekoSpeed || distance < 48) { - idle(); - return; - } - - idleAnimation = null; - idleAnimationFrame = 0; - - if (idleTime > 1) { - setSprite("alert", 0); - // count down after being alerted before moving - idleTime = Math.min(idleTime, 7); - idleTime -= 1; - return; - } - - let direction; - direction = diffY / distance > 0.5 ? "N" : ""; - direction += diffY / distance < -0.5 ? "S" : ""; - direction += diffX / distance > 0.5 ? "W" : ""; - direction += diffX / distance < -0.5 ? "E" : ""; - setSprite(direction, frameCount); - - nekoPosX -= (diffX / distance) * nekoSpeed; - nekoPosY -= (diffY / distance) * nekoSpeed; - - nekoPosX = Math.min(Math.max(16, nekoPosX), window.innerWidth - 16); - nekoPosY = Math.min(Math.max(16, nekoPosY), window.innerHeight - 16); - - nekoEl.style.left = `${nekoPosX - 16}px`; - nekoEl.style.top = `${nekoPosY - 16}px`; - } - - init(); -} diff --git a/src/js/oneko.ts b/src/js/oneko.ts new file mode 100644 index 0000000..236c7cb --- /dev/null +++ b/src/js/oneko.ts @@ -0,0 +1,277 @@ +// oneko.js: https://github.com/adryd325/oneko.js + +export function initOneko(): void { + const isReducedMotion = + window.matchMedia('(prefers-reduced-motion: reduce)') === true || + window.matchMedia('(prefers-reduced-motion: reduce)').matches === true; + + if (isReducedMotion) return; + + const nekoEl = document.createElement('div'); + let persistPosition = true; + + let nekoPosX = 32; + let nekoPosY = 32; + + let mousePosX = 0; + let mousePosY = 0; + + let frameCount = 0; + let idleTime = 0; + let idleAnimation: string | null = null; + let idleAnimationFrame = 0; + + const nekoSpeed = 10; + const spriteSets: Record<string, [number, number][]> = { + idle: [[-3, -3]], + alert: [[-7, -3]], + scratchSelf: [ + [-5, 0], + [-6, 0], + [-7, 0], + ], + scratchWallN: [ + [0, 0], + [0, -1], + ], + scratchWallS: [ + [-7, -1], + [-6, -2], + ], + scratchWallE: [ + [-2, -2], + [-2, -3], + ], + scratchWallW: [ + [-4, 0], + [-4, -1], + ], + tired: [[-3, -2]], + sleeping: [ + [-2, 0], + [-2, -1], + ], + N: [ + [-1, -2], + [-1, -3], + ], + NE: [ + [0, -2], + [0, -3], + ], + E: [ + [-3, 0], + [-3, -1], + ], + SE: [ + [-5, -1], + [-5, -2], + ], + S: [ + [-6, -3], + [-7, -2], + ], + SW: [ + [-5, -3], + [-6, -1], + ], + W: [ + [-4, -2], + [-4, -3], + ], + NW: [ + [-1, 0], + [-1, -1], + ], + }; + + function init(): void { + const assetBase = window.ASSET_BASE || ''; + let nekoFile = `${assetBase}/oneko/oneko.gif`; + const curScript = document.currentScript as HTMLScriptElement; + if (curScript?.dataset.cat) { + nekoFile = curScript.dataset.cat; + } + if (curScript?.dataset.persistPosition) { + if (curScript.dataset.persistPosition === '') { + persistPosition = true; + } else { + persistPosition = JSON.parse(curScript.dataset.persistPosition.toLowerCase()); + } + } + + if (persistPosition) { + const storedNekoStr = window.localStorage.getItem('oneko'); + const storedNeko = storedNekoStr ? JSON.parse(storedNekoStr) : null; + if (storedNeko !== null) { + nekoPosX = storedNeko.nekoPosX; + nekoPosY = storedNeko.nekoPosY; + mousePosX = storedNeko.mousePosX; + mousePosY = storedNeko.mousePosY; + frameCount = storedNeko.frameCount; + idleTime = storedNeko.idleTime; + idleAnimation = storedNeko.idleAnimation; + idleAnimationFrame = storedNeko.idleAnimationFrame; + nekoEl.style.backgroundPosition = storedNeko.bgPos; + } + } + + nekoEl.id = 'oneko'; + nekoEl.ariaHidden = 'true'; + nekoEl.style.width = '32px'; + nekoEl.style.height = '32px'; + nekoEl.style.position = 'fixed'; + nekoEl.style.pointerEvents = 'none'; + nekoEl.style.imageRendering = 'pixelated'; + nekoEl.style.left = `${nekoPosX - 16}px`; + nekoEl.style.top = `${nekoPosY - 16}px`; + nekoEl.style.zIndex = '2147483647'; + + nekoEl.style.backgroundImage = `url(${nekoFile})`; + + document.body.appendChild(nekoEl); + + document.addEventListener('mousemove', (event: MouseEvent) => { + mousePosX = event.clientX; + mousePosY = event.clientY; + }); + + if (persistPosition) { + window.addEventListener('beforeunload', () => { + window.localStorage.setItem( + 'oneko', + JSON.stringify({ + nekoPosX: nekoPosX, + nekoPosY: nekoPosY, + mousePosX: mousePosX, + mousePosY: mousePosY, + frameCount: frameCount, + idleTime: idleTime, + idleAnimation: idleAnimation, + idleAnimationFrame: idleAnimationFrame, + bgPos: nekoEl.style.backgroundPosition, + }) + ); + }); + } + + window.requestAnimationFrame(onAnimationFrame); + } + + let lastFrameTimestamp: number | undefined; + + function onAnimationFrame(timestamp: number): void { + if (!nekoEl.isConnected) { + return; + } + if (!lastFrameTimestamp) { + lastFrameTimestamp = timestamp; + } + if (lastFrameTimestamp && timestamp - lastFrameTimestamp > 100) { + lastFrameTimestamp = timestamp; + frame(); + } + window.requestAnimationFrame(onAnimationFrame); + } + + function setSprite(name: string, frame: number): void { + const sprite = spriteSets[name][frame % spriteSets[name].length]; + nekoEl.style.backgroundPosition = `${sprite[0] * 32}px ${sprite[1] * 32}px`; + } + + function resetIdleAnimation(): void { + idleAnimation = null; + idleAnimationFrame = 0; + } + + function idle(): void { + idleTime += 1; + + // every ~ 20 seconds + if (idleTime > 10 && Math.floor(Math.random() * 200) === 0 && idleAnimation == null) { + const avalibleIdleAnimations: string[] = ['sleeping', 'scratchSelf']; + if (nekoPosX < 32) { + avalibleIdleAnimations.push('scratchWallW'); + } + if (nekoPosY < 32) { + avalibleIdleAnimations.push('scratchWallN'); + } + if (nekoPosX > window.innerWidth - 32) { + avalibleIdleAnimations.push('scratchWallE'); + } + if (nekoPosY > window.innerHeight - 32) { + avalibleIdleAnimations.push('scratchWallS'); + } + idleAnimation = + avalibleIdleAnimations[Math.floor(Math.random() * avalibleIdleAnimations.length)]; + } + + switch (idleAnimation) { + case 'sleeping': + if (idleAnimationFrame < 8) { + setSprite('tired', 0); + break; + } + setSprite('sleeping', Math.floor(idleAnimationFrame / 4)); + if (idleAnimationFrame > 192) { + resetIdleAnimation(); + } + break; + case 'scratchWallN': + case 'scratchWallS': + case 'scratchWallE': + case 'scratchWallW': + case 'scratchSelf': + setSprite(idleAnimation, idleAnimationFrame); + if (idleAnimationFrame > 9) { + resetIdleAnimation(); + } + break; + default: + setSprite('idle', 0); + return; + } + idleAnimationFrame += 1; + } + + function frame(): void { + frameCount += 1; + const diffX = nekoPosX - mousePosX; + const diffY = nekoPosY - mousePosY; + const distance = Math.sqrt(diffX ** 2 + diffY ** 2); + + if (distance < nekoSpeed || distance < 48) { + idle(); + return; + } + + idleAnimation = null; + idleAnimationFrame = 0; + + if (idleTime > 1) { + setSprite('alert', 0); + // count down after being alerted before moving + idleTime = Math.min(idleTime, 7); + idleTime -= 1; + return; + } + + let direction = ''; + direction += diffY / distance > 0.5 ? 'N' : ''; + direction += diffY / distance < -0.5 ? 'S' : ''; + direction += diffX / distance > 0.5 ? 'W' : ''; + direction += diffX / distance < -0.5 ? 'E' : ''; + setSprite(direction, frameCount); + + nekoPosX -= (diffX / distance) * nekoSpeed; + nekoPosY -= (diffY / distance) * nekoSpeed; + + nekoPosX = Math.min(Math.max(16, nekoPosX), window.innerWidth - 16); + nekoPosY = Math.min(Math.max(16, nekoPosY), window.innerHeight - 16); + + nekoEl.style.left = `${nekoPosX - 16}px`; + nekoEl.style.top = `${nekoPosY - 16}px`; + } + + init(); +} diff --git a/src/js/script.js b/src/js/script.js deleted file mode 100644 index 8ba3d82..0000000 --- a/src/js/script.js +++ /dev/null @@ -1,87 +0,0 @@ -import Prism from 'prismjs'; -import 'prismjs/components/prism-javascript'; -import 'prismjs/components/prism-css'; -import 'prismjs/components/prism-markup'; -import { initOneko } from './oneko.js'; - -(() => { - const toggleButton = document.getElementById("theme-toggle"); - const html = document.documentElement; - - const sessionTheme = sessionStorage.getItem("theme"); - const systemPrefersDark = window.matchMedia( - "(prefers-color-scheme: dark)", - ).matches; - - const initialTheme = sessionTheme || (systemPrefersDark ? "dark" : "light"); - - if (initialTheme === "dark") { - html.setAttribute("data-theme", "dark"); - toggleButton.checked = true; - } - - toggleButton.addEventListener("change", () => { - const theme = html.getAttribute("data-theme"); - - if (theme === "dark") { - html.removeAttribute("data-theme"); - sessionStorage.setItem("theme", "light"); - toggleButton.checked = false; - } else { - html.setAttribute("data-theme", "dark"); - sessionStorage.setItem("theme", "dark"); - toggleButton.checked = true; - } - }); -})(); - -(() => { - const colors = [ - "#ff69b4", - "#b19cd9", - "#8b6f47", - "#ff85c0", - "#c4b5fd", - "#d4a574", - ]; - const shapes = ["❀", "✿", "✽", "✾", "✻", "❊", "❋", "✼"]; - - document.addEventListener("mousemove", (e) => { - createParticle(e.clientX, e.clientY); - }); - - const createParticle = (x, y) => { - const particle = document.createElement("div"); - particle.className = "fairy-dust"; - - const shape = shapes[Math.floor(Math.random() * shapes.length)]; - const size = Math.random() * 8 + 6; - const color = colors[Math.floor(Math.random() * colors.length)]; - const offsetX = (Math.random() - 0.5) * 20; - const offsetY = (Math.random() - 0.5) * 20; - const rotation = Math.random() * 360; - - particle.textContent = shape; - particle.style.cssText = ` - position: fixed; - left: ${x + offsetX}px; - top: ${y + offsetY}px; - font-size: ${size}px; - color: ${color}; - opacity: 0.4; - pointer-events: none; - z-index: 9001; /* it's over 9000 */ - line-height: 1; - transform: rotate(${rotation}deg); - animation: fairy-float 0.8s ease-out forwards; - `; - - document.body.appendChild(particle); - setTimeout(() => particle.remove(), 800); - }; -})(); - -document.addEventListener('DOMContentLoaded', () => { - Prism.highlightAll(); - initOneko(); -}); diff --git a/src/js/script.ts b/src/js/script.ts new file mode 100644 index 0000000..e0b0b85 --- /dev/null +++ b/src/js/script.ts @@ -0,0 +1,78 @@ +import Prism from 'prismjs'; +import 'prismjs/components/prism-javascript'; +import 'prismjs/components/prism-css'; +import 'prismjs/components/prism-markup'; +import { initOneko } from './oneko'; + +(() => { + const toggleButton = document.getElementById('theme-toggle') as HTMLInputElement; + const html = document.documentElement; + + const sessionTheme = sessionStorage.getItem('theme'); + const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + + const initialTheme = sessionTheme || (systemPrefersDark ? 'dark' : 'light'); + + if (initialTheme === 'dark') { + html.setAttribute('data-theme', 'dark'); + toggleButton.checked = true; + } + + toggleButton.addEventListener('change', () => { + const theme = html.getAttribute('data-theme'); + + if (theme === 'dark') { + html.removeAttribute('data-theme'); + sessionStorage.setItem('theme', 'light'); + toggleButton.checked = false; + } else { + html.setAttribute('data-theme', 'dark'); + sessionStorage.setItem('theme', 'dark'); + toggleButton.checked = true; + } + }); +})(); + +(() => { + const colors = ['#ff69b4', '#b19cd9', '#8b6f47', '#ff85c0', '#c4b5fd', '#d4a574']; + const shapes = ['❀', '✿', '✽', '✾', '✻', '❊', '❋', '✼']; + + document.addEventListener('mousemove', (e: MouseEvent) => { + createParticle(e.clientX, e.clientY); + }); + + const createParticle = (x: number, y: number) => { + const particle = document.createElement('div'); + particle.className = 'fairy-dust'; + + const shape = shapes[Math.floor(Math.random() * shapes.length)]; + const size = Math.random() * 8 + 6; + const color = colors[Math.floor(Math.random() * colors.length)]; + const offsetX = (Math.random() - 0.5) * 20; + const offsetY = (Math.random() - 0.5) * 20; + const rotation = Math.random() * 360; + + particle.textContent = shape; + particle.style.cssText = ` + position: fixed; + left: ${x + offsetX}px; + top: ${y + offsetY}px; + font-size: ${size}px; + color: ${color}; + opacity: 0.4; + pointer-events: none; + z-index: 9001; /* it's over 9000 */ + line-height: 1; + transform: rotate(${rotation}deg); + animation: fairy-float 0.8s ease-out forwards; + `; + + document.body.appendChild(particle); + setTimeout(() => particle.remove(), 800); + }; +})(); + +document.addEventListener('DOMContentLoaded', () => { + Prism.highlightAll(); + initOneko(); +}); diff --git a/src/types/global.d.ts b/src/types/global.d.ts new file mode 100644 index 0000000..21aec91 --- /dev/null +++ b/src/types/global.d.ts @@ -0,0 +1,7 @@ +declare global { + interface Window { + ASSET_BASE?: string; + } +} + +export {}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..cba458e --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "lib": ["ES2020", "DOM"], + "declaration": false, + "sourceMap": true, + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node", + "resolveJsonModule": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} |
