diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..57901a0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+node_modules/
+dist/
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+.pnpm-debug.log*
+.DS_Store
+*.local
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..837c3d8
--- /dev/null
+++ b/index.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+ cumtanks.snowsune.net
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..d2ac46b
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,942 @@
+{
+ "name": "cumtanks",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "cumtanks",
+ "version": "0.0.0",
+ "devDependencies": {
+ "vite": "^5.4.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.2.tgz",
+ "integrity": "sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.2.tgz",
+ "integrity": "sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.2.tgz",
+ "integrity": "sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.2.tgz",
+ "integrity": "sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.2.tgz",
+ "integrity": "sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.2.tgz",
+ "integrity": "sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.2.tgz",
+ "integrity": "sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.2.tgz",
+ "integrity": "sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.2.tgz",
+ "integrity": "sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.2.tgz",
+ "integrity": "sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.2.tgz",
+ "integrity": "sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.2.tgz",
+ "integrity": "sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.2.tgz",
+ "integrity": "sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.2.tgz",
+ "integrity": "sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.2.tgz",
+ "integrity": "sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.2.tgz",
+ "integrity": "sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.2.tgz",
+ "integrity": "sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.2.tgz",
+ "integrity": "sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.2.tgz",
+ "integrity": "sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.2.tgz",
+ "integrity": "sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.2.tgz",
+ "integrity": "sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.2.tgz",
+ "integrity": "sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.2.tgz",
+ "integrity": "sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.53.2",
+ "@rollup/rollup-android-arm64": "4.53.2",
+ "@rollup/rollup-darwin-arm64": "4.53.2",
+ "@rollup/rollup-darwin-x64": "4.53.2",
+ "@rollup/rollup-freebsd-arm64": "4.53.2",
+ "@rollup/rollup-freebsd-x64": "4.53.2",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.53.2",
+ "@rollup/rollup-linux-arm-musleabihf": "4.53.2",
+ "@rollup/rollup-linux-arm64-gnu": "4.53.2",
+ "@rollup/rollup-linux-arm64-musl": "4.53.2",
+ "@rollup/rollup-linux-loong64-gnu": "4.53.2",
+ "@rollup/rollup-linux-ppc64-gnu": "4.53.2",
+ "@rollup/rollup-linux-riscv64-gnu": "4.53.2",
+ "@rollup/rollup-linux-riscv64-musl": "4.53.2",
+ "@rollup/rollup-linux-s390x-gnu": "4.53.2",
+ "@rollup/rollup-linux-x64-gnu": "4.53.2",
+ "@rollup/rollup-linux-x64-musl": "4.53.2",
+ "@rollup/rollup-openharmony-arm64": "4.53.2",
+ "@rollup/rollup-win32-arm64-msvc": "4.53.2",
+ "@rollup/rollup-win32-ia32-msvc": "4.53.2",
+ "@rollup/rollup-win32-x64-gnu": "4.53.2",
+ "@rollup/rollup-win32-x64-msvc": "4.53.2",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.4.21",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
+ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..2311bb6
--- /dev/null
+++ b/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "cumtanks",
+ "version": "0.0.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "devDependencies": {
+ "vite": "^5.4.0"
+ }
+}
diff --git a/www/Alice_close_up_sheath_background.png b/public/Alice_close_up_sheath_background.png
similarity index 100%
rename from www/Alice_close_up_sheath_background.png
rename to public/Alice_close_up_sheath_background.png
diff --git a/www/Alice_close_up_sheath_shot.png b/public/Alice_close_up_sheath_shot.png
similarity index 100%
rename from www/Alice_close_up_sheath_shot.png
rename to public/Alice_close_up_sheath_shot.png
diff --git a/www/Composite.png b/public/Composite.png
similarity index 100%
rename from www/Composite.png
rename to public/Composite.png
diff --git a/www/bkground.png b/public/bkground.png
similarity index 100%
rename from www/bkground.png
rename to public/bkground.png
diff --git a/www/characters/Makanix_Internal.png b/public/characters/Makanix_Internal.png
similarity index 100%
rename from www/characters/Makanix_Internal.png
rename to public/characters/Makanix_Internal.png
diff --git a/www/characters/ceirus.png b/public/characters/ceirus.png
similarity index 100%
rename from www/characters/ceirus.png
rename to public/characters/ceirus.png
diff --git a/www/characters/orchid.png b/public/characters/orchid.png
similarity index 100%
rename from www/characters/orchid.png
rename to public/characters/orchid.png
diff --git a/www/characters/orchid.png~ b/public/characters/orchid.png~
similarity index 100%
rename from www/characters/orchid.png~
rename to public/characters/orchid.png~
diff --git a/www/characters/rhettan.png b/public/characters/rhettan.png
similarity index 100%
rename from www/characters/rhettan.png
rename to public/characters/rhettan.png
diff --git a/www/characters/rich.png b/public/characters/rich.png
similarity index 100%
rename from www/characters/rich.png
rename to public/characters/rich.png
diff --git a/www/characters/zae.png b/public/characters/zae.png
similarity index 100%
rename from www/characters/zae.png
rename to public/characters/zae.png
diff --git a/www/characters/zae.png~ b/public/characters/zae.png~
similarity index 100%
rename from www/characters/zae.png~
rename to public/characters/zae.png~
diff --git a/www/data.json b/public/data.json
similarity index 100%
rename from www/data.json
rename to public/data.json
diff --git a/www/favicon.ico b/public/favicon.ico
similarity index 100%
rename from www/favicon.ico
rename to public/favicon.ico
diff --git a/www/preview.kra b/public/preview.kra
similarity index 100%
rename from www/preview.kra
rename to public/preview.kra
diff --git a/www/preview.png b/public/preview.png
similarity index 100%
rename from www/preview.png
rename to public/preview.png
diff --git a/src/main.js b/src/main.js
new file mode 100644
index 0000000..94d0adc
--- /dev/null
+++ b/src/main.js
@@ -0,0 +1,166 @@
+import "./style.css";
+
+const app = document.querySelector("#app");
+
+app.innerHTML = `
+
+
+
+

+

+
+
+
+
+
+`;
+
+const stage = app.querySelector(".stage");
+const logList = app.querySelector("[data-role='log']");
+let tankData;
+
+async function loadData() {
+ try {
+ const response = await fetch("/data.json");
+ tankData = await response.json();
+ renderTank();
+ } catch (error) {
+ console.error("Failed to load tank data", error);
+ }
+}
+
+function renderTank() {
+ if (!tankData) return;
+
+ clearLayers();
+ drawLayers();
+ drawLogbook();
+}
+
+function clearLayers() {
+ stage.querySelectorAll(".liquid-layer").forEach((node) => node.remove());
+}
+
+function drawLayers() {
+ const { settings, liquids } = tankData;
+ const height = stage.offsetHeight;
+ const tankHeight = Math.max(
+ 0,
+ height - settings.tankTopOffset - settings.tankBottomOffset
+ );
+
+ let offset = 0;
+ let cumulative = 0;
+
+ liquids.forEach((liquid) => {
+ const layer = document.createElement("div");
+ layer.className = "liquid-layer";
+ layer.style.bottom = `${settings.tankBottomOffset + offset}px`;
+
+ const layerHeight = (liquid.volume / 100) * tankHeight;
+ layer.style.height = `${layerHeight}px`;
+ offset += layerHeight;
+
+ if (liquid.image) {
+ layer.style.backgroundImage = `url(${liquid.image})`;
+ layer.style.backgroundSize = "800px";
+ layer.style.backgroundRepeat = "no-repeat";
+ layer.style.backgroundPosition = "right 50px top 0";
+ layer.style.backgroundColor = liquid.color ?? "transparent";
+ } else {
+ layer.style.backgroundColor = liquid.color ?? "#fff";
+ }
+
+ const label = liquid.url
+ ? document.createElement("a")
+ : document.createElement("span");
+ label.className = "liquid-label";
+ label.textContent = `${liquid.name} (${liquid.volume}%)`;
+ if (liquid.url) {
+ label.href = liquid.url;
+ label.target = "_blank";
+ label.rel = "noreferrer noopener";
+ }
+
+ cumulative += liquid.volume;
+ if (cumulative < 20) {
+ label.style.left = "18%";
+ label.style.transform = "translate(-30%, -50%)";
+ }
+
+ layer.appendChild(label);
+ stage.appendChild(layer);
+ });
+}
+
+function drawLogbook() {
+ const now = Date.now();
+ logList.innerHTML = "";
+
+ tankData.logs?.forEach((log) => {
+ const li = document.createElement("li");
+ const epochMs =
+ typeof log.date === "number" ? log.date * 1000 : Date.parse(log.date);
+ const ageHours = (now - epochMs) / (1000 * 60 * 60);
+
+ li.textContent = `${formatRelative(epochMs)} — ${log.text}`;
+ li.style.color = mixColor(ageHours);
+ if (ageHours < 8) li.classList.add("log-new");
+
+ logList.appendChild(li);
+ });
+
+ const container = logList.parentElement;
+ container.scrollTo({ top: container.scrollHeight });
+}
+
+function formatRelative(epochMs) {
+ const diffSeconds = Math.round((epochMs - Date.now()) / 1000);
+ const divisions = [
+ { amount: 60, unit: "second" },
+ { amount: 60, unit: "minute" },
+ { amount: 24, unit: "hour" },
+ { amount: 7, unit: "day" },
+ { amount: 4.34524, unit: "week" },
+ { amount: 12, unit: "month" },
+ { amount: Number.POSITIVE_INFINITY, unit: "year" }
+ ];
+
+ let duration = diffSeconds;
+ for (const division of divisions) {
+ if (Math.abs(duration) < division.amount) {
+ return new Intl.RelativeTimeFormat("en", { numeric: "auto" }).format(
+ Math.round(duration),
+ division.unit
+ );
+ }
+ duration /= division.amount;
+ }
+}
+
+function mixColor(hours) {
+ if (hours <= 0) return "#ffffff";
+ if (hours >= 8) return "#888888";
+ const t = hours / 8;
+ const channel = Math.round(255 + (136 - 255) * t);
+ return `rgb(${channel},${channel},${channel})`;
+}
+
+window.addEventListener("resize", () => {
+ if (!tankData) return;
+ requestAnimationFrame(renderTank);
+});
+
+loadData();
diff --git a/src/style.css b/src/style.css
new file mode 100644
index 0000000..b724e64
--- /dev/null
+++ b/src/style.css
@@ -0,0 +1,194 @@
+:root {
+ color-scheme: dark;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+ background: #23232b;
+ font-family: "Inter", Arial, sans-serif;
+ min-height: 100vh;
+ color: #e6e6ff;
+}
+
+#app {
+ padding: 32px;
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+}
+
+.layout {
+ display: flex;
+ gap: 24px;
+ width: 100%;
+ align-items: flex-start;
+}
+
+.logbook {
+ width: 240px;
+ max-width: 30vw;
+ padding: 16px 12px;
+ background: rgba(30, 30, 40, 0.95);
+ border: 3px solid #000;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ max-height: 850px;
+ overflow-y: auto;
+}
+
+.logbook ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+}
+
+.stage {
+ position: relative;
+ flex: 1;
+ max-width: 1200px;
+ margin: 0 auto;
+ aspect-ratio: 1200 / 850;
+ display: flex;
+ justify-content: center;
+ align-items: stretch;
+ overflow: hidden;
+}
+
+.stage img {
+ position: absolute;
+ inset: 0;
+ margin: auto;
+ height: 100%;
+ pointer-events: none;
+}
+
+.stage img.background-back {
+ z-index: 1;
+}
+
+.stage img.background-fore {
+ z-index: 3;
+}
+
+.liquid-layer {
+ position: absolute;
+ left: 50%;
+ width: 80%;
+ transform: translateX(-50%);
+ background-repeat: no-repeat;
+ background-size: contain;
+ background-position: top center;
+ border-radius: 4px;
+ overflow: hidden;
+ z-index: 2;
+}
+
+.liquid-label {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ padding: 4px 8px;
+ background: rgba(0, 0, 0, 0.45);
+ color: #fff;
+ font-weight: 600;
+ border-radius: 4px;
+ white-space: nowrap;
+ text-align: center;
+ text-decoration: none;
+}
+
+.liquid-label:hover {
+ text-decoration: underline;
+}
+
+.artist-tag {
+ position: absolute;
+ top: 80px;
+ right: 100px;
+ width: 150px;
+ height: 100px;
+ transform: rotate(51deg);
+ z-index: 4;
+}
+
+.artist-tag a {
+ position: absolute;
+ inset: 0;
+ opacity: 0;
+}
+
+.home-link {
+ width: 220px;
+ display: flex;
+ justify-content: flex-start;
+}
+
+.home-link a {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 8px 16px;
+ border-radius: 8px;
+ background: rgba(0, 0, 0, 0.75);
+ border: 1px solid rgba(255, 255, 255, 0.2);
+ color: #fff;
+ text-decoration: none;
+ font-weight: 600;
+ transition: all 0.2s ease;
+}
+
+.home-link a:hover {
+ background: rgba(255, 255, 255, 0.9);
+ color: #1f1f29;
+}
+
+.log-new {
+ font-weight: 700;
+ background: linear-gradient(90deg, #fff, #f6f6ff, #ececff, #fff);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ filter: drop-shadow(0 0 3px #fff6);
+}
+
+footer {
+ font-size: 0.75rem;
+ text-align: center;
+ color: #b3b3c3;
+}
+
+footer a {
+ color: inherit;
+}
+
+@media (max-width: 900px) {
+ .layout {
+ flex-direction: column;
+ }
+
+ .logbook,
+ .home-link {
+ width: 100%;
+ max-width: none;
+ }
+
+ .home-link {
+ justify-content: center;
+ }
+
+ .stage {
+ width: 100%;
+ }
+
+ .artist-tag {
+ display: none;
+ }
+}
diff --git a/update.sh b/update.sh
index f0fe5a0..c2f316d 100755
--- a/update.sh
+++ b/update.sh
@@ -1,4 +1,8 @@
#!/bin/bash
-chmod 644 www/data.json
+set -euo pipefail
-rsync -avz --delete www/* joe@snowsunehost:/opt/cumtanks.snowsune.net/www --progress
+npm run build
+
+chmod 644 dist/data.json
+
+rsync -avz --delete dist/ joe@snowsunehost:/opt/cumtanks.snowsune.net/www --progress
diff --git a/vite.config.js b/vite.config.js
new file mode 100644
index 0000000..2763c07
--- /dev/null
+++ b/vite.config.js
@@ -0,0 +1,7 @@
+import { defineConfig } from "vite";
+
+export default defineConfig({
+ server: {
+ host: true
+ }
+});
diff --git a/www/index.html b/www/index.html
deleted file mode 100644
index 80e4f7c..0000000
--- a/www/index.html
+++ /dev/null
@@ -1,225 +0,0 @@
-
-
-
-
-
-
- Cumtanks.snowsune.net
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

-

-
-
-
-
-
Settings
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/www/preview.png~ b/www/preview.png~
deleted file mode 100644
index 386f86c..0000000
Binary files a/www/preview.png~ and /dev/null differ
diff --git a/www/styles.css b/www/styles.css
deleted file mode 100644
index b5a52f2..0000000
--- a/www/styles.css
+++ /dev/null
@@ -1,275 +0,0 @@
-body {
- margin: 0;
- padding: 0;
- background: #23232b;
- font-family: Arial, sans-serif;
- min-height: 100vh;
- display: flex;
- flex-direction: column;
- align-items: center;
-}
-
-.container {
- width: 100%;
- max-width: 1200px;
- padding: 20px;
-}
-
-h1 {
- color: white;
- text-align: center;
- margin: 20px 0;
-}
-
-.tank-container {
- position: relative;
- width: 100%;
- height: 850px;
- background: #23232b;
- overflow: hidden;
- border-radius: 0;
- margin: 0 auto;
- width: 1200px;
- left: 0;
- top: 0;
- padding: 0;
- box-sizing: border-box;
- display: flex;
- justify-content: center;
- align-items: center;
-}
-
-.background-image {
- position: absolute;
- width: auto;
- height: 100%;
- max-width: 110%;
- max-height: 110%;
- object-fit: contain;
- border: 4px solid #000;
- box-sizing: border-box;
- pointer-events: none;
- left: 50%;
- top: 0;
- transform: translateX(-50%);
- margin: 0;
- padding: 0;
-}
-
-.background-image-back {
- z-index: 1;
-}
-
-.background-image-fore {
- z-index: 3;
-}
-
-.liquid-layer {
- position: absolute;
- width: 80%;
- left: 50%;
- transform: translateX(-50%);
- transition: all 0.3s ease;
- z-index: 2;
- overflow: hidden;
- background-position: top center;
- background-repeat: no-repeat;
- background-size: contain;
-}
-
-.liquid-layer > div {
- width: 100%;
- height: 100%;
- position: relative;
-}
-
-.liquid-label {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- color: white;
- text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
- font-weight: bold;
- z-index: 2;
- padding: 5px 10px;
- background: rgba(0, 0, 0, 0.3);
- border-radius: 4px;
- text-align: center;
- white-space: nowrap;
- font-size: 1.2em;
-}
-
-.liquid-link {
- color: white;
- text-decoration: none;
- cursor: pointer;
- transition: opacity 0.2s ease;
-}
-
-.liquid-link:hover {
- opacity: 0.8;
- text-decoration: underline;
-}
-
-/* Settings are hidden but still in the DOM for development */
-.settings {
- display: none;
-}
-
-.artist-credit {
- text-align: center;
- padding: 1rem;
- color: #666;
- font-size: 0.9rem;
- font-style: italic;
- margin-top: 2rem;
-}
-
-.artist-link {
- position: absolute;
- top: 80px;
- right: 100px;
- transform: rotate(51deg);
- z-index: 100;
- width: 150px;
- height: 100px;
- pointer-events: auto;
- /* DEBUG: Temporary visible area for clickable link */
- /* outline: 2px dashed #ff0;
- background: rgba(255, 255, 0, 0.2); */
-}
-
-.artist-link a {
- display: block;
- width: 100%;
- height: 100%;
- text-indent: -9999px;
- background: transparent;
- border: none;
- outline: none;
- cursor: pointer;
- position: absolute;
- top: 0;
- left: 0;
- opacity: 0;
-}
-
-.artist-link a:hover {
- background: #fff;
- color: #23232b;
-}
-
-/* Footer */
-.tiny-footer {
- font-size: 0.75rem;
- color: #aaa;
- text-align: center;
- margin-top: -10px; /* shifts footer upward */
- padding: 5px 10px;
-}
-
-.tiny-footer a {
- color: #aaa;
- text-decoration: underline;
-}
-
-.tiny-footer a:hover {
- color: #fff;
-}
-
-.logbook {
- position: absolute;
- left: 24px;
- top: 20px;
- width: 260px;
- height: 850px;
- background: rgba(30, 30, 40, 0.95);
- border: 4px solid #000;
- box-sizing: border-box;
- font-size: 0.85em;
- color: #eee;
- overflow-y: auto;
- z-index: 10;
- padding: 16px 10px 16px 16px;
- display: flex;
- flex-direction: column;
- justify-content: flex-end;
-}
-
-.logbook h2 {
- font-size: 1em;
- margin: 0 0 8px 0;
- color: #fff;
- text-align: left;
-}
-
-#logList {
- list-style: none;
- padding: 0;
- margin: 0;
- font-size: 0.85em;
- display: flex;
- flex-direction: column;
- gap: 4px;
-}
-
-#logList li {
- color: #ccc;
- font-size: 0.95em;
- word-break: break-word;
-}
-
-.log-shimmer {
- background: linear-gradient(
- 90deg,
- #fff 0%,
- #f8f8ff 20%,
- #ffe 40%,
- #fff 60%,
- #e0e0e0 80%,
- #fff 100%
- );
- background-size: 400% 100%;
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- animation: shimmer 6s linear infinite;
- font-weight: bold;
- filter: drop-shadow(0 0 2px #fff8);
-}
-
-@keyframes shimmer {
- 0% {
- background-position: 400% 0;
- }
- 100% {
- background-position: 0 0;
- }
-}
-
-.homepage-link {
- position: fixed;
- top: 20px;
- right: 20px;
- z-index: 1000;
-}
-
-.homepage-link a {
- display: inline-block;
- padding: 8px 16px;
- background: rgba(0, 0, 0, 0.7);
- color: #fff;
- text-decoration: none;
- border-radius: 6px;
- font-size: 14px;
- font-weight: bold;
- transition: all 0.3s ease;
- border: 2px solid rgba(255, 255, 255, 0.2);
-}
-
-.homepage-link a:hover {
- background: rgba(255, 255, 255, 0.9);
- color: #23232b;
- border-color: rgba(255, 255, 255, 0.8);
- transform: translateY(-1px);
-}