redesign(frontend)
No files matched your search
@@ -1,10 +1,10 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM node:24-alpine AS web
|
||||
FROM oven/bun:1.4.4-alpine AS web
|
||||
WORKDIR /src/web
|
||||
COPY web/package.json web/package-lock.json ./
|
||||
RUN npm ci
|
||||
COPY web/package.json web/bun.lock ./
|
||||
RUN bun install --frozen-lockfile
|
||||
COPY web ./
|
||||
RUN npm run build
|
||||
RUN bun run build
|
||||
|
||||
FROM rust:1-alpine AS build
|
||||
RUN apk add --no-cache musl-dev cmake make perl clang ca-certificates
|
||||
|
||||
@@ -45,6 +45,8 @@ Not affiliated with pl8list.
|
||||
|
||||
## Screenshots
|
||||
|
||||
The [monochrome redesign previews](docs/redesign/README.md) show the current visual treatment using a synthetic track. The live-playback captures below predate that pass.
|
||||
|
||||

|
||||
|
||||
<p align="center">
|
||||
@@ -62,17 +64,17 @@ Not affiliated with pl8list.
|
||||
docker compose up -d --build # http://127.0.0.1:8790
|
||||
```
|
||||
|
||||
or without Docker (Rust 1.85+, Node 22+):
|
||||
or without Docker (Rust 1.85+, Bun 1.4.4+):
|
||||
|
||||
```sh
|
||||
cd web && npm ci && npm run build # front end into web/dist
|
||||
cd web && bun install --frozen-lockfile && bun run build # front end into web/dist
|
||||
cd ../backend && cargo run --release # embeds web/dist at build time
|
||||
cargo test # unit tests + API tests against a fake pl8list
|
||||
```
|
||||
|
||||
For front-end work run the server, then `npm run dev` in `web/`: Vite serves the page with
|
||||
For front-end work run the server, then `bun run dev` in `web/`: Vite serves the page with
|
||||
hot reload on http://127.0.0.1:5173/static/ and passes API calls to the server (`BACKEND=` to point it
|
||||
elsewhere). Debug builds of the server read `web/dist` from disk, so `npm run build` is enough
|
||||
elsewhere). Debug builds of the server read `web/dist` from disk, so `bun run build` is enough
|
||||
to see changes on :8790 too.
|
||||
|
||||
The server is the Rust crate in `backend/`, the front end is TypeScript in `web/` (Vite, no
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The front end is built separately (`cd web && npm ci && npm run build`) and
|
||||
// The front end is built separately (`cd web && bun install --frozen-lockfile && bun run build`) and
|
||||
// embedded from web/dist. Create the folder if it is missing so the backend still
|
||||
// compiles (and its tests run) without Node; the pages are then empty.
|
||||
fn main() {
|
||||
@@ -6,6 +6,6 @@ fn main() {
|
||||
println!("cargo:rerun-if-changed=../web/dist");
|
||||
if !dist.join("index.html").exists() {
|
||||
std::fs::create_dir_all(dist).expect("create ../web/dist");
|
||||
println!("cargo:warning=web/dist has no index.html; run `npm run build` in web/ to include the front end");
|
||||
println!("cargo:warning=web/dist has no index.html; run `bun run build` in web/ to include the front end");
|
||||
}
|
||||
}
|
||||
@@ -286,7 +286,7 @@ pub type Peer = ConnectInfo<SocketAddr>;
|
||||
|
||||
// Static files ------------------------------------------------------------
|
||||
|
||||
/// The built front end (`web/`, `npm run build`). Hashed asset names, so they
|
||||
/// The built front end (`web/`, `bun run build`). Hashed asset names, so they
|
||||
/// can be cached for good; index.html and fixed-name files are revalidated.
|
||||
#[derive(Embed)]
|
||||
#[folder = "../web/dist/"]
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# Monochrome visual pass
|
||||
|
||||
These screenshots use a synthetic track and deterministic audio features in Chromium, not live pl8list playback. They demonstrate desktop (1440×1000), mobile (390×844), short mobile (390×667), settings and login styling.
|
||||
|
||||
Validated: Bun frozen-lockfile installation, TypeScript check, production build, local font loading, WebGL shader creation/rendering, opening and dismissing settings, and a sustained synthetic drop whose one-shot envelope decays without retriggering. Chromium reported no page errors.
|
||||
|
||||
Still requires real-device validation: real-track onset timing, touch gestures, GPU context restoration, reduced-motion transitions, audio fallback on iOS, and comparative frame-time/battery measurements. Docker's Bun build stage was updated but the full container image was not built.
|
||||
|
||||
Run the frontend with `cd web && bun install --frozen-lockfile && bun run dev`. Build with `bun run build`.
|
||||
|
After Width: | Height: | Size: 245 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 137 KiB |
|
After Width: | Height: | Size: 217 KiB |
|
After Width: | Height: | Size: 91 KiB |
@@ -0,0 +1,141 @@
|
||||
{
|
||||
"lockfileVersion": 2,
|
||||
"configVersion": 0,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "pl8inf-web",
|
||||
"dependencies": {
|
||||
"hls.js": "1.7.4",
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "7.0.2",
|
||||
"vite": "8.3.5",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@oxc-project/types": ["@oxc-project/types@0.156.0", "", {}, "sha512-1Duyg48TdJNdyhEB8Xn1L36bT9Pv6GUtlKjHxOys8xkaF/rQq3XJE+bAurSK/AgIz5mGFsb77cn9Pb20+WCScw=="],
|
||||
|
||||
"@rolldown/binding-android-arm-eabi": ["@rolldown/binding-android-arm-eabi@1.2.16", "", { "os": "android", "cpu": "arm" }, "sha512-ia3DwAs0OsMGS7+IConptSiyk9pEXkOx/AbJ2n/lAGxH3QkWQus1t8d7gnAT38FCU2sQu8lUbeR6AiZFv4JxaA=="],
|
||||
|
||||
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.16", "", { "os": "android", "cpu": "arm64" }, "sha512-kx5wBiMB6E7TEvpl2B9gjLU004BkvEm2Dr941BFjx/SontPwSzNuQUIoVKxHHoSjpFIfHMmcguyLOtyn0hObRg=="],
|
||||
|
||||
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.16", "", { "os": "darwin", "cpu": "arm64" }, "sha512-qslZrXnKJU4cUpjoAmtAo/KQM9e2IRMlmpig6Zpwj+8KWxW8v+nkAf9216Bsek9lOC1jvg2yCjygCFo2f83msQ=="],
|
||||
|
||||
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.2.16", "", { "os": "darwin", "cpu": "x64" }, "sha512-FdF+y48ND13eAyM2mQn+85kvAqNlvRWROy2rhgA463X/0Gv4vGtazZtfaF1mRBLscYvtOlh4QnLjOWiFzX69OQ=="],
|
||||
|
||||
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.2.16", "", { "os": "freebsd", "cpu": "x64" }, "sha512-ddjqFM5e95AgLbUPtKIS7V1KTVlA0RUgCVr5mn+/IEhsdy3pr+Vuj/HZ7nlMN2FyxIwUktOH1LEPIQXO1v9y1Q=="],
|
||||
|
||||
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.2.16", "", { "os": "linux", "cpu": "arm" }, "sha512-tHdKWbFkFBI687iim3LIyIvyL7j66VvQYkDPss5Stn/qyE2tWkR1YD0dXmXPFKKqT/o5ZEJVsaRsWS2meQwGOA=="],
|
||||
|
||||
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.2.16", "", { "os": "linux", "cpu": "arm64", "libc": "glibc" }, "sha512-FJiLGAblPBMlBEdbOqw6kW6QcXDWAf/oJu7gYs9ZJZwmg+K+/zjFLxPTIY9izJUzxzc24FHvmseyA0IvJSl17w=="],
|
||||
|
||||
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.2.16", "", { "os": "linux", "cpu": "arm64", "libc": "musl" }, "sha512-hrJpblMsrQ58U9veWpbXzkhV5zStunds6HWLprqq1qauvgBr1E6P+24kLj6lSLI9vjRn/RYpjprhkHQPKGWpNg=="],
|
||||
|
||||
"@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.2.16", "", { "os": "linux", "cpu": "ppc64", "libc": "glibc" }, "sha512-qJd/dC6j6CwLG9/s/RP2nb1RZ0GIgj7Ioj7x/SYa3RzjVrlzrmH0fhIOpfvqQDeIsdP0loi+kHaMhJzY1PBIaA=="],
|
||||
|
||||
"@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.2.16", "", { "os": "linux", "cpu": "s390x", "libc": "glibc" }, "sha512-Vx7Xg5uUJuNcujJjI70OICaH0EYj80BtOLZTSN7uOYg8QOLlyKnaG0IW7Fn/J0IpJsaKzmDKMhOQv1JBM9m9vg=="],
|
||||
|
||||
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.2.16", "", { "os": "linux", "cpu": "x64", "libc": "glibc" }, "sha512-TNKhgE3uJTsTzatUSSYt0Kgo+Oa2uSlSjz4r1aEIea7HX6ULBym4fAlYUhG1i6pjNf2njfCUAfKwr6C3E9Xg1w=="],
|
||||
|
||||
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.2.16", "", { "os": "linux", "cpu": "x64", "libc": "musl" }, "sha512-7chXdpjXZ8qG0+mIxECN3cT9kvhZ3/oZhMX6Pp876P7CDrIy8nJswva6Mq357JR7eYXfuskwZxMeV1SzoZdHrA=="],
|
||||
|
||||
"@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.2.16", "", { "os": "none", "cpu": "arm64" }, "sha512-Kwjt+7uNTJK5/MDkGWGVO2KQPnj8yAV75dMpfwyHZrJSXWHrniOB0aUEWlVYf3H1hOqFLIStLdM5QXq6bnKmOQ=="],
|
||||
|
||||
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.2.16", "", { "os": "win32", "cpu": "arm64" }, "sha512-/LH6WfjDRXXJz/p5xl4TmU+jpcnwcxbKEQBW/GZE0JHfN0lGIyTtsUEGaQ/q/GikkGJ2yLKe6pwm7AnEWO8pdw=="],
|
||||
|
||||
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.16", "", { "os": "win32", "cpu": "x64" }, "sha512-1lv1KvX0Z871zfk7vw+dnXMMnHNyDwMzWG8Kv3HxDXMOhzCX4SHc7HK319fInP0i+0p9D8brkfzL7pJOaGl+tw=="],
|
||||
|
||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="],
|
||||
|
||||
"@typescript/typescript-aix-ppc64": ["@typescript/typescript-aix-ppc64@7.0.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ=="],
|
||||
|
||||
"@typescript/typescript-darwin-arm64": ["@typescript/typescript-darwin-arm64@7.0.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA=="],
|
||||
|
||||
"@typescript/typescript-darwin-x64": ["@typescript/typescript-darwin-x64@7.0.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA=="],
|
||||
|
||||
"@typescript/typescript-freebsd-arm64": ["@typescript/typescript-freebsd-arm64@7.0.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ=="],
|
||||
|
||||
"@typescript/typescript-freebsd-x64": ["@typescript/typescript-freebsd-x64@7.0.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw=="],
|
||||
|
||||
"@typescript/typescript-linux-arm": ["@typescript/typescript-linux-arm@7.0.2", "", { "os": "linux", "cpu": "arm" }, "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ=="],
|
||||
|
||||
"@typescript/typescript-linux-arm64": ["@typescript/typescript-linux-arm64@7.0.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ=="],
|
||||
|
||||
"@typescript/typescript-linux-loong64": ["@typescript/typescript-linux-loong64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ=="],
|
||||
|
||||
"@typescript/typescript-linux-mips64el": ["@typescript/typescript-linux-mips64el@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA=="],
|
||||
|
||||
"@typescript/typescript-linux-ppc64": ["@typescript/typescript-linux-ppc64@7.0.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA=="],
|
||||
|
||||
"@typescript/typescript-linux-riscv64": ["@typescript/typescript-linux-riscv64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ=="],
|
||||
|
||||
"@typescript/typescript-linux-s390x": ["@typescript/typescript-linux-s390x@7.0.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw=="],
|
||||
|
||||
"@typescript/typescript-linux-x64": ["@typescript/typescript-linux-x64@7.0.2", "", { "os": "linux", "cpu": "x64" }, "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A=="],
|
||||
|
||||
"@typescript/typescript-netbsd-arm64": ["@typescript/typescript-netbsd-arm64@7.0.2", "", { "os": "none", "cpu": "arm64" }, "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA=="],
|
||||
|
||||
"@typescript/typescript-netbsd-x64": ["@typescript/typescript-netbsd-x64@7.0.2", "", { "os": "none", "cpu": "x64" }, "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA=="],
|
||||
|
||||
"@typescript/typescript-openbsd-arm64": ["@typescript/typescript-openbsd-arm64@7.0.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ=="],
|
||||
|
||||
"@typescript/typescript-openbsd-x64": ["@typescript/typescript-openbsd-x64@7.0.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg=="],
|
||||
|
||||
"@typescript/typescript-sunos-x64": ["@typescript/typescript-sunos-x64@7.0.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g=="],
|
||||
|
||||
"@typescript/typescript-win32-arm64": ["@typescript/typescript-win32-arm64@7.0.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ=="],
|
||||
|
||||
"@typescript/typescript-win32-x64": ["@typescript/typescript-win32-x64@7.0.2", "", { "os": "win32", "cpu": "x64" }, "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g=="],
|
||||
|
||||
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
|
||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||
|
||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin", "hasInstallScript": true }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"hls.js": ["hls.js@1.7.4", "", {}, "sha512-3GlxGdPvT2YHvQYOt+yOzT/DEior/qJVNIylHA77kgeoBTdQQqi70GwefNEUmHQeNGyjC8U62+bruqW/Tpm+tg=="],
|
||||
|
||||
"lightningcss": ["lightningcss@1.33.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.33.0", "lightningcss-darwin-arm64": "1.33.0", "lightningcss-darwin-x64": "1.33.0", "lightningcss-freebsd-x64": "1.33.0", "lightningcss-linux-arm-gnueabihf": "1.33.0", "lightningcss-linux-arm64-gnu": "1.33.0", "lightningcss-linux-arm64-musl": "1.33.0", "lightningcss-linux-x64-gnu": "1.33.0", "lightningcss-linux-x64-musl": "1.33.0", "lightningcss-win32-arm64-msvc": "1.33.0", "lightningcss-win32-x64-msvc": "1.33.0" } }, "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA=="],
|
||||
|
||||
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.33.0", "", { "os": "android", "cpu": "arm64" }, "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg=="],
|
||||
|
||||
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.33.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg=="],
|
||||
|
||||
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.33.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ=="],
|
||||
|
||||
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.33.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg=="],
|
||||
|
||||
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.33.0", "", { "os": "linux", "cpu": "arm" }, "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ=="],
|
||||
|
||||
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.33.0", "", { "os": "linux", "cpu": "arm64", "libc": "glibc" }, "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg=="],
|
||||
|
||||
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.33.0", "", { "os": "linux", "cpu": "arm64", "libc": "musl" }, "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ=="],
|
||||
|
||||
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.33.0", "", { "os": "linux", "cpu": "x64", "libc": "glibc" }, "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg=="],
|
||||
|
||||
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.33.0", "", { "os": "linux", "cpu": "x64", "libc": "musl" }, "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw=="],
|
||||
|
||||
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.33.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA=="],
|
||||
|
||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.33.0", "", { "os": "win32", "cpu": "x64" }, "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA=="],
|
||||
|
||||
"nanoid": ["nanoid@3.3.20", "", { "bin": "bin/nanoid.cjs" }, "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ=="],
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
||||
"picomatch": ["picomatch@4.0.7", "", {}, "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA=="],
|
||||
|
||||
"postcss": ["postcss@8.5.32", "", { "dependencies": { "nanoid": "^3.3.20", "picocolors": "^1.1.1", "source-map-js": "^1.2.2" } }, "sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g=="],
|
||||
|
||||
"rolldown": ["rolldown@1.2.16", "", { "dependencies": { "@oxc-project/types": "=0.156.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm-eabi": "1.2.16", "@rolldown/binding-android-arm64": "1.2.16", "@rolldown/binding-darwin-arm64": "1.2.16", "@rolldown/binding-darwin-x64": "1.2.16", "@rolldown/binding-freebsd-x64": "1.2.16", "@rolldown/binding-linux-arm-gnueabihf": "1.2.16", "@rolldown/binding-linux-arm64-gnu": "1.2.16", "@rolldown/binding-linux-arm64-musl": "1.2.16", "@rolldown/binding-linux-ppc64-gnu": "1.2.16", "@rolldown/binding-linux-s390x-gnu": "1.2.16", "@rolldown/binding-linux-x64-gnu": "1.2.16", "@rolldown/binding-linux-x64-musl": "1.2.16", "@rolldown/binding-openharmony-arm64": "1.2.16", "@rolldown/binding-win32-arm64-msvc": "1.2.16", "@rolldown/binding-win32-x64-msvc": "1.2.16" }, "bin": "bin/cli.mjs" }, "sha512-bWvlMpFKiTfDKfnK7FC2MPgPeWCf/Kpwsq49ApdW+FQaBZb6O6dZ4MAPbPUjp7hYQrw/RobqCzn64BRrQecGHw=="],
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.2", "", {}, "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw=="],
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="],
|
||||
|
||||
"typescript": ["typescript@7.0.2", "", { "optionalDependencies": { "@typescript/typescript-aix-ppc64": "7.0.2", "@typescript/typescript-darwin-arm64": "7.0.2", "@typescript/typescript-darwin-x64": "7.0.2", "@typescript/typescript-freebsd-arm64": "7.0.2", "@typescript/typescript-freebsd-x64": "7.0.2", "@typescript/typescript-linux-arm": "7.0.2", "@typescript/typescript-linux-arm64": "7.0.2", "@typescript/typescript-linux-loong64": "7.0.2", "@typescript/typescript-linux-mips64el": "7.0.2", "@typescript/typescript-linux-ppc64": "7.0.2", "@typescript/typescript-linux-riscv64": "7.0.2", "@typescript/typescript-linux-s390x": "7.0.2", "@typescript/typescript-linux-x64": "7.0.2", "@typescript/typescript-netbsd-arm64": "7.0.2", "@typescript/typescript-netbsd-x64": "7.0.2", "@typescript/typescript-openbsd-arm64": "7.0.2", "@typescript/typescript-openbsd-x64": "7.0.2", "@typescript/typescript-sunos-x64": "7.0.2", "@typescript/typescript-win32-arm64": "7.0.2", "@typescript/typescript-win32-x64": "7.0.2" }, "bin": { "tsc": "bin/tsc" } }, "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA=="],
|
||||
|
||||
"vite": ["vite@8.3.5", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.7", "postcss": "^8.5.30", "rolldown": "~1.2.14", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.7.1 || ^0.8.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": "bin/vite.js" }, "sha512-z1YpjieJ9g0ebTmQpLBv/oq9E3F9EIlOe+7LahW8SrE3LTiG3udBRUXQOlYXP9b+IC2q2phHeVxj9xGlw34OEQ=="],
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<title>pl8list rewind</title>
|
||||
<meta name="description" content="Endless riddim from pl8list. Like it, and it comes back.">
|
||||
<meta name="theme-color" content="#000000">
|
||||
<meta name="theme-color" content="#090a0b">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc --noEmit && vite build",
|
||||
"check": "tsc --noEmit"
|
||||
"dev": "bun --bun vite",
|
||||
"build": "bun run check && bun --bun vite build",
|
||||
"check": "bun --bun tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"hls.js": "1.7.4"
|
||||
@@ -13,5 +13,6 @@
|
||||
"devDependencies": {
|
||||
"typescript": "7.0.2",
|
||||
"vite": "8.3.5"
|
||||
}
|
||||
},
|
||||
"packageManager": "bun@1.4.4"
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
Copyright 2017 The Barlow Project Authors (https://github.com/jpt/barlow)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -0,0 +1,93 @@
|
||||
Copyright 2017 The Barlow Project Authors (https://github.com/jpt/barlow)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 122 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 368 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<defs>
|
||||
<radialGradient id="g" cx="30%" cy="20%" r="90%">
|
||||
<stop offset="0" stop-color="#3a1019"/>
|
||||
<stop offset="0" stop-color="#303236"/>
|
||||
<stop offset="0.6" stop-color="#0b0b10"/>
|
||||
<stop offset="1" stop-color="#05050a"/>
|
||||
</radialGradient>
|
||||
@@ -15,9 +15,9 @@
|
||||
<rect width="512" height="512" rx="112" fill="url(#g)"/>
|
||||
<circle cx="256" cy="256" r="196" fill="#09090c"/>
|
||||
<circle cx="256" cy="256" r="182.3" fill="none" stroke="#fff" stroke-opacity="0.07" stroke-width="2"/><circle cx="256" cy="256" r="168.6" fill="none" stroke="#fff" stroke-opacity="0.07" stroke-width="2"/><circle cx="256" cy="256" r="154.8" fill="none" stroke="#fff" stroke-opacity="0.07" stroke-width="2"/><circle cx="256" cy="256" r="103.9" fill="none" stroke="#fff" stroke-opacity="0.07" stroke-width="2"/><circle cx="256" cy="256" r="92.1" fill="none" stroke="#fff" stroke-opacity="0.07" stroke-width="2"/>
|
||||
<path d="M385.4 256.0L385.6 257.1L385.9 258.3L386.1 259.4L386.4 260.6L386.5 261.7L386.5 262.8L386.4 264.0L386.1 265.1L385.5 266.2L384.9 267.3L384.1 268.3L383.2 269.4L382.4 270.4L381.7 271.4L381.1 272.5L380.9 273.6L381.0 274.7L381.5 275.9L382.3 277.1L383.4 278.5L384.6 279.8L385.9 281.2L387.0 282.6L387.8 284.0L388.1 285.3L387.8 286.4L386.9 287.4L385.4 288.3L383.4 288.9L381.0 289.5L378.3 289.9L375.7 290.3L373.4 290.8L371.6 291.4L370.6 292.1L370.4 293.2L371.1 294.5L372.7 296.2L374.9 298.1L377.6 300.2L380.4 302.5L383.0 304.8L385.2 306.9L386.5 308.7L386.8 310.2L386.0 311.2L384.1 311.7L381.1 311.7L377.4 311.3L373.2 310.7L369.0 309.9L365.0 309.2L361.7 308.7L359.3 308.6L358.1 309.2L358.2 310.3L359.4 312.1L361.7 314.6L364.7 317.5L368.0 320.7L371.4 324.0L374.3 327.1L376.4 329.8L377.5 331.9L377.4 333.4L376.1 334.0L373.6 333.9L370.3 333.1L366.2 331.8L362.0 330.2L357.8 328.6L354.1 327.2L351.1 326.4L349.1 326.1L348.1 326.7L348.1 328.0L349.1 330.1L350.8 332.7L352.9 335.9L355.1 339.2L357.1 342.4L358.7 345.3L359.7 347.7L359.9 349.5L359.2 350.6L357.8 350.9L355.7 350.6L353.1 349.8L350.3 348.7L347.5 347.5L344.8 346.4L342.5 345.5L340.6 345.1L339.3 345.3L338.4 346.0L338.1 347.2L338.1 348.8L338.4 350.8L338.8 352.9L339.2 355.1L339.3 357.1L339.3 358.9L339.0 360.3L338.3 361.3L337.3 362.0L336.1 362.3L334.7 362.4L333.2 362.3L331.7 362.1L330.2 362.0L328.8 361.9L327.5 362.0L326.4 362.3L325.3 362.8L324.5 363.4L323.6 364.3L322.9 365.2L322.2 366.1L321.4 367.1L320.7 368.0L319.9 368.9L319.0 369.6L318.0 370.3L317.1 370.8L316.0 371.3L315.0 371.7L313.9 372.1L312.8 372.5L311.7 372.9L310.7 373.2L309.6 373.6L308.6 374.1L307.5 374.5L306.5 375.0L305.5 375.4L304.4 375.9L303.4 376.3L302.3 376.7L301.3 377.2L300.2 377.6L299.2 377.9L298.1 378.3L297.0 378.7L296.0 379.0L294.9 379.4L293.8 379.7L292.7 380.0L291.7 380.3L290.6 380.7L289.5 381.0L288.4 381.2L287.3 381.5L286.2 381.8L285.1 382.0L284.0 382.3L282.9 382.5L281.8 382.8L280.7 383.0L279.6 383.2L278.5 383.4L277.4 383.6L276.2 383.8L275.1 384.0L274.0 384.1L272.9 384.3L271.8 384.4L270.6 384.6L269.5 384.7L268.4 384.8L267.3 384.9L266.1 384.9L265.0 384.9L263.9 384.9L262.8 384.9L261.6 384.9L260.5 384.9L259.4 384.9L258.3 385.0L257.1 385.2L256.0 385.4L254.9 385.6L253.7 385.9L252.6 386.1L251.4 386.4L250.3 386.5L249.2 386.5L248.0 386.4L246.9 386.1L245.8 385.5L244.7 384.9L243.7 384.1L242.6 383.2L241.6 382.4L240.6 381.7L239.5 381.1L238.4 380.9L237.3 381.0L236.1 381.5L234.9 382.3L233.5 383.4L232.2 384.6L230.8 385.9L229.4 387.0L228.0 387.8L226.7 388.1L225.6 387.8L224.6 386.9L223.7 385.4L223.1 383.4L222.5 381.0L222.1 378.3L221.7 375.7L221.2 373.4L220.6 371.6L219.9 370.6L218.8 370.4L217.5 371.1L215.8 372.7L213.9 374.9L211.8 377.6L209.5 380.4L207.2 383.0L205.1 385.2L203.3 386.5L201.8 386.8L200.8 386.0L200.3 384.1L200.3 381.1L200.7 377.4L201.3 373.2L202.1 369.0L202.8 365.0L203.3 361.7L203.4 359.3L202.8 358.1L201.7 358.2L199.9 359.4L197.4 361.7L194.5 364.7L191.3 368.0L188.0 371.4L184.9 374.3L182.2 376.4L180.1 377.5L178.6 377.4L178.0 376.1L178.1 373.6L178.9 370.3L180.2 366.2L181.8 362.0L183.4 357.8L184.8 354.1L185.6 351.1L185.9 349.1L185.3 348.1L184.0 348.1L181.9 349.1L179.3 350.8L176.1 352.9L172.8 355.1L169.6 357.1L166.7 358.7L164.3 359.7L162.5 359.9L161.4 359.2L161.1 357.8L161.4 355.7L162.2 353.1L163.3 350.3L164.5 347.5L165.6 344.8L166.5 342.5L166.9 340.6L166.7 339.3L166.0 338.4L164.8 338.1L163.2 338.1L161.2 338.4L159.1 338.8L156.9 339.2L154.9 339.3L153.1 339.3L151.7 339.0L150.7 338.3L150.0 337.3L149.7 336.1L149.6 334.7L149.7 333.2L149.9 331.7L150.0 330.2L150.1 328.8L150.0 327.5L149.7 326.4L149.2 325.3L148.6 324.5L147.7 323.6L146.8 322.9L145.9 322.2L144.9 321.4L144.0 320.7L143.1 319.9L142.4 319.0L141.7 318.0L141.2 317.1L140.7 316.0L140.3 315.0L139.9 313.9L139.5 312.8L139.1 311.7L138.8 310.7L138.4 309.6L137.9 308.6L137.5 307.5L137.0 306.5L136.6 305.5L136.1 304.4L135.7 303.4L135.3 302.3L134.8 301.3L134.4 300.2L134.1 299.2L133.7 298.1L133.3 297.0L133.0 296.0L132.6 294.9L132.3 293.8L132.0 292.7L131.7 291.7L131.3 290.6L131.0 289.5L130.8 288.4L130.5 287.3L130.2 286.2L130.0 285.1L129.7 284.0L129.5 282.9L129.2 281.8L129.0 280.7L128.8 279.6L128.6 278.5L128.4 277.4L128.2 276.2L128.0 275.1L127.9 274.0L127.7 272.9L127.6 271.8L127.4 270.6L127.3 269.5L127.2 268.4L127.1 267.3L127.1 266.1L127.1 265.0L127.1 263.9L127.1 262.8L127.1 261.6L127.1 260.5L127.1 259.4L127.0 258.3L126.8 257.1L126.6 256.0L126.4 254.9L126.1 253.7L125.9 252.6L125.6 251.4L125.5 250.3L125.5 249.2L125.6 248.0L125.9 246.9L126.5 245.8L127.1 244.7L127.9 243.7L128.8 242.6L129.6 241.6L130.3 240.6L130.9 239.5L131.1 238.4L131.0 237.3L130.5 236.1L129.7 234.9L128.6 233.5L127.4 232.2L126.1 230.8L125.0 229.4L124.2 228.0L123.9 226.7L124.2 225.6L125.1 224.6L126.6 223.7L128.6 223.1L131.0 222.5L133.7 222.1L136.3 221.7L138.6 221.2L140.4 220.6L141.4 219.9L141.6 218.8L140.9 217.5L139.3 215.8L137.1 213.9L134.4 211.8L131.6 209.5L129.0 207.2L126.8 205.1L125.5 203.3L125.2 201.8L126.0 200.8L127.9 200.3L130.9 200.3L134.6 200.7L138.8 201.3L143.0 202.1L147.0 202.8L150.3 203.3L152.7 203.4L153.9 2Line truncated
|
||||
<path d="M385.4 256.0L385.6 257.1L385.9 258.3L386.1 259.4L386.4 260.6L386.5 261.7L386.5 262.8L386.4 264.0L386.1 265.1L385.5 266.2L384.9 267.3L384.1 268.3L383.2 269.4L382.4 270.4L381.7 271.4L381.1 272.5L380.9 273.6L381.0 274.7L381.5 275.9L382.3 277.1L383.4 278.5L384.6 279.8L385.9 281.2L387.0 282.6L387.8 284.0L388.1 285.3L387.8 286.4L386.9 287.4L385.4 288.3L383.4 288.9L381.0 289.5L378.3 289.9L375.7 290.3L373.4 290.8L371.6 291.4L370.6 292.1L370.4 293.2L371.1 294.5L372.7 296.2L374.9 298.1L377.6 300.2L380.4 302.5L383.0 304.8L385.2 306.9L386.5 308.7L386.8 310.2L386.0 311.2L384.1 311.7L381.1 311.7L377.4 311.3L373.2 310.7L369.0 309.9L365.0 309.2L361.7 308.7L359.3 308.6L358.1 309.2L358.2 310.3L359.4 312.1L361.7 314.6L364.7 317.5L368.0 320.7L371.4 324.0L374.3 327.1L376.4 329.8L377.5 331.9L377.4 333.4L376.1 334.0L373.6 333.9L370.3 333.1L366.2 331.8L362.0 330.2L357.8 328.6L354.1 327.2L351.1 326.4L349.1 326.1L348.1 326.7L348.1 328.0L349.1 330.1L350.8 332.7L352.9 335.9L355.1 339.2L357.1 342.4L358.7 345.3L359.7 347.7L359.9 349.5L359.2 350.6L357.8 350.9L355.7 350.6L353.1 349.8L350.3 348.7L347.5 347.5L344.8 346.4L342.5 345.5L340.6 345.1L339.3 345.3L338.4 346.0L338.1 347.2L338.1 348.8L338.4 350.8L338.8 352.9L339.2 355.1L339.3 357.1L339.3 358.9L339.0 360.3L338.3 361.3L337.3 362.0L336.1 362.3L334.7 362.4L333.2 362.3L331.7 362.1L330.2 362.0L328.8 361.9L327.5 362.0L326.4 362.3L325.3 362.8L324.5 363.4L323.6 364.3L322.9 365.2L322.2 366.1L321.4 367.1L320.7 368.0L319.9 368.9L319.0 369.6L318.0 370.3L317.1 370.8L316.0 371.3L315.0 371.7L313.9 372.1L312.8 372.5L311.7 372.9L310.7 373.2L309.6 373.6L308.6 374.1L307.5 374.5L306.5 375.0L305.5 375.4L304.4 375.9L303.4 376.3L302.3 376.7L301.3 377.2L300.2 377.6L299.2 377.9L298.1 378.3L297.0 378.7L296.0 379.0L294.9 379.4L293.8 379.7L292.7 380.0L291.7 380.3L290.6 380.7L289.5 381.0L288.4 381.2L287.3 381.5L286.2 381.8L285.1 382.0L284.0 382.3L282.9 382.5L281.8 382.8L280.7 383.0L279.6 383.2L278.5 383.4L277.4 383.6L276.2 383.8L275.1 384.0L274.0 384.1L272.9 384.3L271.8 384.4L270.6 384.6L269.5 384.7L268.4 384.8L267.3 384.9L266.1 384.9L265.0 384.9L263.9 384.9L262.8 384.9L261.6 384.9L260.5 384.9L259.4 384.9L258.3 385.0L257.1 385.2L256.0 385.4L254.9 385.6L253.7 385.9L252.6 386.1L251.4 386.4L250.3 386.5L249.2 386.5L248.0 386.4L246.9 386.1L245.8 385.5L244.7 384.9L243.7 384.1L242.6 383.2L241.6 382.4L240.6 381.7L239.5 381.1L238.4 380.9L237.3 381.0L236.1 381.5L234.9 382.3L233.5 383.4L232.2 384.6L230.8 385.9L229.4 387.0L228.0 387.8L226.7 388.1L225.6 387.8L224.6 386.9L223.7 385.4L223.1 383.4L222.5 381.0L222.1 378.3L221.7 375.7L221.2 373.4L220.6 371.6L219.9 370.6L218.8 370.4L217.5 371.1L215.8 372.7L213.9 374.9L211.8 377.6L209.5 380.4L207.2 383.0L205.1 385.2L203.3 386.5L201.8 386.8L200.8 386.0L200.3 384.1L200.3 381.1L200.7 377.4L201.3 373.2L202.1 369.0L202.8 365.0L203.3 361.7L203.4 359.3L202.8 358.1L201.7 358.2L199.9 359.4L197.4 361.7L194.5 364.7L191.3 368.0L188.0 371.4L184.9 374.3L182.2 376.4L180.1 377.5L178.6 377.4L178.0 376.1L178.1 373.6L178.9 370.3L180.2 366.2L181.8 362.0L183.4 357.8L184.8 354.1L185.6 351.1L185.9 349.1L185.3 348.1L184.0 348.1L181.9 349.1L179.3 350.8L176.1 352.9L172.8 355.1L169.6 357.1L166.7 358.7L164.3 359.7L162.5 359.9L161.4 359.2L161.1 357.8L161.4 355.7L162.2 353.1L163.3 350.3L164.5 347.5L165.6 344.8L166.5 342.5L166.9 340.6L166.7 339.3L166.0 338.4L164.8 338.1L163.2 338.1L161.2 338.4L159.1 338.8L156.9 339.2L154.9 339.3L153.1 339.3L151.7 339.0L150.7 338.3L150.0 337.3L149.7 336.1L149.6 334.7L149.7 333.2L149.9 331.7L150.0 330.2L150.1 328.8L150.0 327.5L149.7 326.4L149.2 325.3L148.6 324.5L147.7 323.6L146.8 322.9L145.9 322.2L144.9 321.4L144.0 320.7L143.1 319.9L142.4 319.0L141.7 318.0L141.2 317.1L140.7 316.0L140.3 315.0L139.9 313.9L139.5 312.8L139.1 311.7L138.8 310.7L138.4 309.6L137.9 308.6L137.5 307.5L137.0 306.5L136.6 305.5L136.1 304.4L135.7 303.4L135.3 302.3L134.8 301.3L134.4 300.2L134.1 299.2L133.7 298.1L133.3 297.0L133.0 296.0L132.6 294.9L132.3 293.8L132.0 292.7L131.7 291.7L131.3 290.6L131.0 289.5L130.8 288.4L130.5 287.3L130.2 286.2L130.0 285.1L129.7 284.0L129.5 282.9L129.2 281.8L129.0 280.7L128.8 279.6L128.6 278.5L128.4 277.4L128.2 276.2L128.0 275.1L127.9 274.0L127.7 272.9L127.6 271.8L127.4 270.6L127.3 269.5L127.2 268.4L127.1 267.3L127.1 266.1L127.1 265.0L127.1 263.9L127.1 262.8L127.1 261.6L127.1 260.5L127.1 259.4L127.0 258.3L126.8 257.1L126.6 256.0L126.4 254.9L126.1 253.7L125.9 252.6L125.6 251.4L125.5 250.3L125.5 249.2L125.6 248.0L125.9 246.9L126.5 245.8L127.1 244.7L127.9 243.7L128.8 242.6L129.6 241.6L130.3 240.6L130.9 239.5L131.1 238.4L131.0 237.3L130.5 236.1L129.7 234.9L128.6 233.5L127.4 232.2L126.1 230.8L125.0 229.4L124.2 228.0L123.9 226.7L124.2 225.6L125.1 224.6L126.6 223.7L128.6 223.1L131.0 222.5L133.7 222.1L136.3 221.7L138.6 221.2L140.4 220.6L141.4 219.9L141.6 218.8L140.9 217.5L139.3 215.8L137.1 213.9L134.4 211.8L131.6 209.5L129.0 207.2L126.8 205.1L125.5 203.3L125.2 201.8L126.0 200.8L127.9 200.3L130.9 200.3L134.6 200.7L138.8 201.3L143.0 202.1L147.0 202.8L150.3 203.3L152.7 203.4L153.9 2Line truncated
|
||||
<circle cx="256" cy="256" r="196" fill="url(#shine)"/>
|
||||
<circle cx="256" cy="256" r="78.4" fill="#ff4d6d"/>
|
||||
<circle cx="256" cy="256" r="78.4" fill="#e9e8e3"/>
|
||||
<path d="M275.6 217.6A43.12 43.12 0 1 0 297.0 269.3" fill="none" stroke="#09090c" stroke-width="13" stroke-linecap="round"/><path d="M303.9 248.2L312.7 280.9L277.5 269.5Z" fill="#09090c" stroke="#09090c" stroke-width="4.55" stroke-linejoin="round"/>
|
||||
<circle cx="256" cy="256" r="6.0" fill="#09090c"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
@@ -1,6 +1,6 @@
|
||||
/* Impressum and privacy pages. Plain CSS outside the app bundle, so the server's
|
||||
templates can link it by a fixed name. */
|
||||
:root { color-scheme: dark; --fg: #f2f2f2; --muted: #9a9a9a; --accent: #ff4d6d; }
|
||||
:root { color-scheme: dark; --fg: #f2f2f2; --muted: #9a9a9a; --accent: #e9e8e3; }
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
"scope": "/",
|
||||
"display": "standalone",
|
||||
"orientation": "portrait",
|
||||
"background_color": "#000000",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#090a0b",
|
||||
"theme_color": "#090a0b",
|
||||
"icons": [
|
||||
{ "src": "/static/icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
|
||||
{ "src": "/static/icons/icon-512.png", "sizes": "512x512", "type": "image/png" },
|
||||
|
||||
@@ -17,13 +17,13 @@ const DIST = SPILL / Math.tan(FOV / 2);
|
||||
const MORPH_IN = 1.4; // s from flat artwork to liquid
|
||||
// The jump: a spring that each prominent kick or bass hit kicks upwards. Hits are
|
||||
// weighed by prominence squared, so only the strongest really make it jump.
|
||||
const JUMP = 1.9; // upward speed of a full-strength hit, in radii per second
|
||||
const JUMP_DAMPING = 7; // 1/s; low enough that it jiggles a few times as it lands
|
||||
const MAX_LIFT = 0.16; // radii
|
||||
const JUMP = 0.5; // upward speed of a full-strength hit, in radii per second
|
||||
const JUMP_DAMPING = 22; // 1/s; low enough that it jiggles a few times as it lands
|
||||
const MAX_LIFT = 0.035; // radii
|
||||
const STRETCH = 0.08; // stretch per unit of speed: long while flying, squashed on landing
|
||||
const PUMP = 0.7; // size kick of a snare hit
|
||||
const PUMP_STIFF = 160;
|
||||
const PUMP_DAMPING = 12;
|
||||
const PUMP_DAMPING = 24;
|
||||
const THROB = 0.09; // size swing with the bass line's envelope
|
||||
const WHIRL = 15; // rad/s of hand spin at which it bulges and sloshes the most
|
||||
const SNARE_ATTACK = 0.03; // s; spikes grow in rather than pop
|
||||
@@ -117,6 +117,8 @@ export class LiquidBlob {
|
||||
private jump = [0, 0];
|
||||
private pump = [0, 0];
|
||||
private snare = 0;
|
||||
private dropArmed = true;
|
||||
private dropImpulse = 0;
|
||||
/// The icon: its shape (0 pause, 1 play), how much it shows, and since when the
|
||||
/// playing state is what it is. `iconFor` is null until the first frame of a track.
|
||||
private iconShape = 1;
|
||||
@@ -241,6 +243,8 @@ export class LiquidBlob {
|
||||
this.slow = this.fast = 0;
|
||||
this.spin.reset();
|
||||
this.ferro.reset();
|
||||
this.dropArmed = true;
|
||||
this.dropImpulse = 0;
|
||||
this.light = 0;
|
||||
this.iconFor = null;
|
||||
const source = await artwork(art, initial, tint).catch(() => artwork(null, initial, tint));
|
||||
@@ -295,11 +299,17 @@ export class LiquidBlob {
|
||||
this.morph = Math.min(1, this.morph + dt / MORPH_IN);
|
||||
const morph = this.morph * this.morph * (3 - 2 * this.morph);
|
||||
// The liquid flows with the tempo, faster when the music moves more.
|
||||
this.time += dt * look.flow * (playing ? 0.6 + 0.8 * f.wobble + 0.6 * f.drop : 0.25);
|
||||
this.time += dt * look.flow * (playing ? (0.25 + 0.8 * f.wobble + 0.3 * this.dropImpulse) * (1 - 0.9 * f.gap) : 0.25);
|
||||
const k = 1 - Math.exp(-dt / 0.15);
|
||||
for (let i = 0; i < 4; i++) this.pointer[i] += (this.pointerGoal[i] - this.pointer[i]) * k;
|
||||
const spinning = this.spin.step(dt);
|
||||
const showing = this.showIcon(s, dt);
|
||||
if (f.drop < 0.2) this.dropArmed = true;
|
||||
if (playing && this.dropArmed && f.drop > 0.55) {
|
||||
this.dropImpulse = 1;
|
||||
this.dropArmed = false;
|
||||
}
|
||||
this.dropImpulse *= Math.exp(-dt / 0.22);
|
||||
const moving = this.bounce(f, dt, look);
|
||||
|
||||
const u = this.u;
|
||||
@@ -310,12 +320,12 @@ export class LiquidBlob {
|
||||
gl.uniform1f(u.uWobble, f.wobble);
|
||||
gl.uniform1f(u.uKick, f.kick);
|
||||
gl.uniform1f(u.uKickAge, f.kickAge);
|
||||
gl.uniform1f(u.uDrop, f.drop);
|
||||
gl.uniform1f(u.uDrop, this.dropImpulse);
|
||||
gl.uniform1f(u.uBass, f.bass * look.throb);
|
||||
gl.uniform1f(u.uGrowl, f.growl);
|
||||
this.snare += (f.snare - this.snare) * ease(dt, f.snare > this.snare ? SNARE_ATTACK : SNARE_RELEASE);
|
||||
gl.uniform1f(u.uSnare, this.snare);
|
||||
gl.uniform1f(u.uGain, look.gain);
|
||||
gl.uniform1f(u.uGain, look.gain * (1 - 0.85 * f.gap));
|
||||
gl.uniform1f(u.uSpike, look.spike);
|
||||
gl.uniform1f(u.uStep, look.step);
|
||||
gl.uniform1f(u.uTwist, look.twist);
|
||||
@@ -368,7 +378,7 @@ export class LiquidBlob {
|
||||
if (f.kickHit) jump[1] += JUMP * look.bounce * f.kickHit ** 2;
|
||||
if (f.snareHit) pump[1] += PUMP * look.bounce * f.snareHit ** 2;
|
||||
// The size also follows the bass and the drop, through the same spring.
|
||||
const pumpRest = 0.04 * f.low + 0.05 * f.drop;
|
||||
const pumpRest = (0.025 * f.low + 0.065 * this.dropImpulse - 0.045 * f.kick) * (1 - f.gap);
|
||||
// Small fixed steps keep the springs stable on slow frames.
|
||||
for (let left = Math.min(dt, 0.1); left > 0; left -= 1 / 240) {
|
||||
const h = Math.min(left, 1 / 240);
|
||||
@@ -381,7 +391,7 @@ export class LiquidBlob {
|
||||
// Long while it flies up, squashed as it comes down and lands.
|
||||
const stretch = 1 + Math.min(0.18, Math.max(-0.15, jump[1] * STRETCH + Math.min(0, jump[0]) * 1.2));
|
||||
// Each "womb" of the bass line swells the whole drop.
|
||||
const size = 1 + pump[0] + THROB * look.throb * f.bass;
|
||||
const size = Math.max(0.88, Math.min(1.18, 1 + pump[0] + THROB * look.throb * f.bass * (1 - f.gap)));
|
||||
// Spun fast it flattens out a little, like dough, and sloshes.
|
||||
const whirl = Math.min(1, this.spin.speed / WHIRL);
|
||||
const side = (size / Math.sqrt(stretch)) * (1 + 0.08 * whirl);
|
||||
|
||||
@@ -141,7 +141,7 @@ export class Caustics {
|
||||
gl.uniform1f(u.uHue, look.hue);
|
||||
gl.uniform1f(u.uIri, look.iri);
|
||||
gl.uniform1f(u.uHeat, look.heat);
|
||||
gl.uniform1f(u.uAmount, c.amount * look.caustics);
|
||||
gl.uniform1f(u.uAmount, c.amount * look.caustics * 0.22);
|
||||
gl.disable(gl.DEPTH_TEST);
|
||||
gl.depthMask(false);
|
||||
gl.bindVertexArray(this.vao);
|
||||
|
||||
@@ -35,7 +35,7 @@ export class Ferro {
|
||||
private charge = new Float32Array(SPIKES);
|
||||
private seed = new Float32Array(SPIKES);
|
||||
private clock = 0;
|
||||
private lastDrop = 0;
|
||||
private dropArmed = true;
|
||||
/// Where on the crown the next kick lands, for tracks with scatter.
|
||||
private turn = Math.random();
|
||||
/// The tallest spike, so the shader can skip them all when there are none.
|
||||
@@ -58,7 +58,7 @@ export class Ferro {
|
||||
this.height.fill(0);
|
||||
this.speed.fill(0);
|
||||
this.charge.fill(0);
|
||||
this.lastDrop = 0;
|
||||
this.dropArmed = true;
|
||||
this.tallest = 0;
|
||||
for (let i = 0; i < SPIKES; i++) this.data[i * 4 + 3] = 0;
|
||||
}
|
||||
@@ -70,13 +70,15 @@ export class Ferro {
|
||||
const n = SPIKES;
|
||||
this.clock += dt;
|
||||
if (on && f.kickHit >= KICK_MIN && f.gap < 0.5) this.kick(f, look, reach, toObject);
|
||||
if (on && f.drop > DROP_BURST && this.lastDrop <= DROP_BURST) {
|
||||
if (f.drop < 0.2) this.dropArmed = true;
|
||||
if (on && f.drop > DROP_BURST && this.dropArmed) {
|
||||
this.dropArmed = false;
|
||||
for (let i = 0; i < n; i++) {
|
||||
this.charge[i] = 1;
|
||||
this.speed[i] += 2.5 * reach;
|
||||
}
|
||||
}
|
||||
this.lastDrop = f.drop;
|
||||
|
||||
if (on && f.snareHit >= look.jolt) {
|
||||
for (let i = 0; i < n; i++) this.speed[i] += f.snareHit * reach * (0.5 + 3 * this.charge[i]);
|
||||
}
|
||||
|
||||
@@ -265,22 +265,25 @@ void main() {
|
||||
// Looking through the surface bends the artwork like water does.
|
||||
vec2 bend = n.xy * (0.22 + 0.1 * uDrop) * uMorph;
|
||||
vec2 uv = 0.5 + vec2(vObj.x, -vObj.y) * 0.48 - vec2(bend.x, -bend.y) * 0.5;
|
||||
vec2 split = n.xy * (0.008 + 0.025 * uKick + 0.015 * uSnare + 0.01 * uGrowl * uBass) * uMorph;
|
||||
vec2 split = n.xy * (0.001 + 0.003 * uKick + 0.002 * uSnare) * uMorph;
|
||||
// Turned sideways the projection smears; blur it into flowing colour instead.
|
||||
float blur = 4.0 * pow(1.0 - abs(vObj.z), 3.0) * uMorph;
|
||||
vec3 art = vec3(texture(uArt, uv + split, blur).r, texture(uArt, uv, blur).g, texture(uArt, uv - split, blur).b);
|
||||
art = mix(art, smoothstep(0.0, 1.0, art), 0.6 * uDrop);
|
||||
float luminance = dot(art, vec3(0.2126, 0.7152, 0.0722));
|
||||
vec3 trace = mix(vec3(luminance), art, 0.22);
|
||||
// Preserve the flat artwork during entry; blackened metal takes over as it morphs.
|
||||
art = mix(art, vec3(0.035, 0.038, 0.042) + trace * 0.22, uMorph);
|
||||
|
||||
vec3 light = normalize(vec3(-0.5, 0.7, 0.6));
|
||||
float spec = pow(max(dot(n, normalize(light + v)), 0.0), mix(40.0, 140.0, uMetal)) * mix(0.35, 0.8, uMetal);
|
||||
// The hue sways around the accent but never leaves it, so the drop stays in the
|
||||
// card's colours.
|
||||
vec3 rim = hueRotate(uAccent, uHue + uIri * (fres + 0.5 * sin(uTime * 0.3)));
|
||||
vec3 rim = hueRotate(mix(vec3(0.72), uAccent, 0.16), uHue + uIri * (fres + 0.5 * sin(uTime * 0.3)));
|
||||
rim = mix(rim, vec3(1.0, 0.35, 0.1), uHeat * 0.6);
|
||||
// Oil-slick fringes: each channel cycles at its own phase across the edge, worn
|
||||
// over the accent, more for iridescent tracks.
|
||||
vec3 slick = 0.5 + 0.5 * cos(6.2831853 * (fres * 1.6 + vec3(0.0, 0.33, 0.67) + uTime * 0.05));
|
||||
rim = mix(rim, rim * 0.4 + slick * length(rim) * 0.75, 0.25 + 0.5 * uIri);
|
||||
rim = mix(rim, rim * 0.4 + slick * length(rim) * 0.75, 0.035 + 0.08 * uIri);
|
||||
// The fresnel per channel, red reaching in further than blue: a lens fringe.
|
||||
float nv = 1.0 - max(dot(n, v), 0.0);
|
||||
vec3 fresC = vec3(nv * nv * nv * mix(1.0, nv, -0.35), fres, nv * nv * nv * mix(1.0, nv, 0.35));
|
||||
@@ -290,13 +293,13 @@ void main() {
|
||||
// Out on a spike the liquid turns to ink, like ferrofluid: the artwork darkens and
|
||||
// the gloss takes over.
|
||||
vec3 col = art * mix(1.0, 0.45, fres * uMorph) * (1.0 - 0.6 * vSpike);
|
||||
col += rim * fresC * (0.65 + 0.9 * uKick + 0.5 * uDrop + 0.5 * uBass) * uMorph;
|
||||
col += mix(rim, vec3(1.0), 0.5) * edge * (0.3 + 1.2 * uKick + 0.8 * uDrop) * uMorph;
|
||||
col += rim * fresC * (0.3 + 0.35 * uKick + 0.15 * uDrop + 0.12 * uBass) * uMorph;
|
||||
col += mix(rim, vec3(1.0), 0.5) * edge * (0.15 + 0.45 * uKick + 0.25 * uDrop) * uMorph;
|
||||
col += spec * (1.0 + 0.8 * vSpike) * uMorph;
|
||||
// Gloss: a dark room lit by the accent from above, and one soft window. They stay
|
||||
// put while the surface turns under them, which is what makes a spin read.
|
||||
vec3 r = reflect(-v, n);
|
||||
vec3 env = mix(vec3(0.015), uAccent * 0.5, pow(smoothstep(-0.4, 1.0, r.y), 2.0));
|
||||
vec3 env = mix(vec3(0.015), mix(vec3(0.5), uAccent * 0.5, 0.12), pow(smoothstep(-0.4, 1.0, r.y), 2.0));
|
||||
env += 0.5 * smoothstep(0.92, 0.985, dot(r, normalize(vec3(-0.5, 0.7, 0.5))));
|
||||
col += env * (0.1 + 0.5 * fres + 0.5 * vSpike) * uMorph;
|
||||
// The icon sits on a disc of frosted glass, painted onto the surface so it turns
|
||||
|
||||
@@ -48,14 +48,14 @@ export interface Look {
|
||||
}
|
||||
|
||||
const BASE: Look = {
|
||||
flow: 1, gain: 1, spike: 0.5, step: 0, throb: 1, bounce: 1, stiff: 90, twist: 0.15, ripple: 1, shock: 1, iri: 0.1, metal: 0.3, heat: 0, hue: 0,
|
||||
flow: 1, gain: 1, spike: 0.5, step: 0, throb: 1, bounce: 0.35, stiff: 160, twist: 0.15, ripple: 1, shock: 1, iri: 0.1, metal: 0.3, heat: 0, hue: 0,
|
||||
ferro: 0, crown: 6, scatter: 0.4, reach: 0.22, hold: 2, jolt: 0.45, caustics: 1,
|
||||
};
|
||||
|
||||
const TAGS: Record<string, (l: Look) => void> = {
|
||||
// Riddim: the spikes stand in step and the crown repeats every bar.
|
||||
riddim: (l) => Object.assign(l, {
|
||||
step: 0.5, spike: 0.6, throb: 1.4, bounce: 1.3, stiff: 120, metal: 0.9, shock: 1.4, ripple: 1.2,
|
||||
step: 0.7, spike: 0.6, throb: 1.4, bounce: 0.4, stiff: 200, metal: 0.9, shock: 1.4, ripple: 1.2,
|
||||
ferro: 1, crown: 4, scatter: 0, reach: 0.26, hold: 2, jolt: 0.4,
|
||||
}),
|
||||
tearout: (l) => Object.assign(l, { spike: 1.3, bounce: 1.4, gain: l.gain * 1.15, heat: 0.7, ferro: 1, crown: 6, scatter: 0.5, reach: 0.3, jolt: 0.25 }),
|
||||
@@ -78,6 +78,7 @@ export function lookFor(t: Track): Look {
|
||||
// Untagged dubstep tempo counts too: most riddim carries no riddim tag.
|
||||
if (tags.includes("140")) l.ferro = 1;
|
||||
for (const tag of tags) TAGS[tag]?.(l);
|
||||
l.bounce = Math.min(l.bounce, 0.45);
|
||||
l.hue = keyHue(t.key);
|
||||
return l;
|
||||
}
|
||||
|
||||
@@ -283,7 +283,7 @@ export class Card {
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
const data = this.track.waveform || [];
|
||||
const max = Math.max(1, ...data);
|
||||
const accent = getComputedStyle(canvas).getPropertyValue("--card-accent").trim() || "#ff4d6d";
|
||||
const accent = getComputedStyle(canvas).getPropertyValue("--accent").trim() || "#e9e8e3";
|
||||
const base = Math.round(h * (1 - MIRROR)) - 1;
|
||||
const below = h - base - 2;
|
||||
const level = (i: number) => (data.length ? data[Math.floor((i / bars) * data.length)] / max : 0.15);
|
||||
|
||||
@@ -50,8 +50,8 @@ async function compute(src: string): Promise<string | null> {
|
||||
|
||||
const BACKDROP = 64; // px square; it is shown blurred, so this is plenty
|
||||
const BLUR = 2; // px each side, per box blur pass at that size
|
||||
const DIM = 0.45;
|
||||
const SATURATE = 1.3;
|
||||
const DIM = 0.16;
|
||||
const SATURATE = 0.15;
|
||||
|
||||
/// The card's background from the artist image, as a data URL: cropped square,
|
||||
/// blurred and darkened here, once, as `.bg`'s CSS filter would do on every frame.
|
||||
|
||||
@@ -1319,7 +1319,7 @@ function signedIn(user: User, pl8list?: Pl8Status): void {
|
||||
startEl.classList.remove("welcome");
|
||||
accountBtn.textContent = guest ? "sign in" : (user.name || user.handle)[0]?.toUpperCase() ?? "?";
|
||||
accountBtn.classList.toggle("guest", guest);
|
||||
accountBtn.style.backgroundColor = guest ? "var(--glass-strong)" : accentForName(user.handle);
|
||||
accountBtn.style.backgroundColor = guest ? "var(--glass-strong)" : "var(--accent)";
|
||||
accountBtn.title = guest ? "listening as a guest: sign in, or forget me" : `@${user.handle}`;
|
||||
$("account-handle").textContent = guest ? "listening as a guest" : `@${user.handle}`;
|
||||
$("delete-handle").textContent = user.handle;
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
@font-face { font-family: Barlow; src: url('/fonts/Barlow-Regular.ttf') format('truetype'); font-weight: 400; font-display: swap; }
|
||||
@font-face { font-family: 'Barlow Condensed'; src: url('/fonts/BarlowCondensed-Bold.ttf') format('truetype'); font-weight: 700 900; font-display: swap; }
|
||||
|
||||
/* Design tokens ---------------------------------------------------------- */
|
||||
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
--bg: #000;
|
||||
--bg: #090a0b;
|
||||
--fg: #f2f2f2;
|
||||
--muted: #b5b5b8;
|
||||
--faint: #96969a;
|
||||
--accent: #ff4d6d;
|
||||
--panel: rgba(18, 18, 20, 0.94);
|
||||
--accent: #e9e8e3;
|
||||
--danger: #ff8b8b;
|
||||
--panel: #141517;
|
||||
--line: #ffffff1f;
|
||||
--line-strong: #333;
|
||||
--scrim: #000b;
|
||||
--glass: rgba(255, 255, 255, 0.1);
|
||||
--glass-strong: rgba(255, 255, 255, 0.2);
|
||||
--radius-s: 8px;
|
||||
--radius-m: 14px;
|
||||
--radius-l: 18px;
|
||||
--radius-s: 4px;
|
||||
--radius-m: 6px;
|
||||
--radius-l: 6px;
|
||||
--shadow: 0 16px 40px #000a;
|
||||
--font: system-ui, -apple-system, "Segoe UI", sans-serif;
|
||||
--font-display: "Arial Narrow", "Roboto Condensed", "Liberation Sans Narrow", sans-serif;
|
||||
--font: Barlow, system-ui, sans-serif;
|
||||
--font-display: "Barlow Condensed", "Arial Narrow", sans-serif;
|
||||
--ease-out: cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
--fast: 0.15s;
|
||||
}
|
||||
@@ -46,7 +50,7 @@ button {
|
||||
cursor: pointer;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||
border-radius: 999px;
|
||||
border-radius: 5px;
|
||||
padding: 0.35em 0.9em;
|
||||
}
|
||||
|
||||
@@ -90,7 +94,7 @@ body.zen :is(#discovery-options, #active-filters) { display: none; }
|
||||
display: flex;
|
||||
gap: 0.2rem;
|
||||
padding: 0.2rem;
|
||||
border-radius: 999px;
|
||||
border-radius: 5px;
|
||||
background: var(--glass);
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
@@ -147,7 +151,7 @@ body.zen :is(#discovery-options, #active-filters) { display: none; }
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
background: linear-gradient(90deg, var(--fg) var(--v), #ffffff30 var(--v)) center / 100% 4px no-repeat;
|
||||
border-radius: 999px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
#volume-slider::-webkit-slider-thumb {
|
||||
@@ -247,7 +251,7 @@ body.zen :is(#discovery-options, #active-filters) { display: none; }
|
||||
flex: 1 1 10rem;
|
||||
min-width: 0;
|
||||
padding: 0.55em 0.9em;
|
||||
border-radius: 999px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid var(--line-strong);
|
||||
background: #111;
|
||||
}
|
||||
@@ -258,7 +262,7 @@ body.zen :is(#discovery-options, #active-filters) { display: none; }
|
||||
.modes input { position: absolute; opacity: 0; pointer-events: none; }
|
||||
.modes label:has(input) {
|
||||
padding: 0.3em 0.9em;
|
||||
border-radius: 999px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #ffffff40;
|
||||
font-size: 0.85rem;
|
||||
transition: background var(--fast);
|
||||
@@ -274,7 +278,7 @@ body.zen :is(#discovery-options, #active-filters) { display: none; }
|
||||
width: 2.6rem;
|
||||
height: 1.5rem;
|
||||
margin: 0;
|
||||
border-radius: 999px;
|
||||
border-radius: 5px;
|
||||
background: #ffffff26;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
@@ -435,7 +439,7 @@ button.danger:not(.link) { background: var(--accent); border: none; color: #fff;
|
||||
position: absolute;
|
||||
inset: -40px;
|
||||
background: #111 center / cover no-repeat;
|
||||
filter: blur(40px) brightness(0.45) saturate(1.3);
|
||||
filter: blur(40px) brightness(0.16) saturate(0.15);
|
||||
}
|
||||
/* Already blurred and darkened (color.ts): a CSS blur over the whole screen would
|
||||
be worked out again whenever anything on top of it moves. */
|
||||
@@ -446,7 +450,7 @@ button.danger:not(.link) { background: var(--accent); border: none; color: #fff;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
background: radial-gradient(55% 40% at 50% 40%, color-mix(in oklab, var(--card-accent) 55%, transparent), transparent 70%);
|
||||
opacity: calc(0.18 + var(--energy) * 0.55);
|
||||
opacity: calc(0.03 + var(--energy) * 0.07);
|
||||
will-change: opacity;
|
||||
}
|
||||
|
||||
@@ -502,11 +506,11 @@ button.danger:not(.link) { background: var(--accent); border: none; color: #fff;
|
||||
.card.liquid .art::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: -65%;
|
||||
inset: -25%;
|
||||
background: radial-gradient(closest-side, color-mix(in oklab, var(--card-accent) 83%, transparent), transparent);
|
||||
/* Pulses through transform and opacity only, so it never has to be painted again. */
|
||||
transform: scale(calc(0.87 + var(--energy) * 0.13));
|
||||
opacity: calc(0.34 + var(--energy) * 0.66);
|
||||
opacity: calc(0.06 + var(--energy) * 0.12);
|
||||
will-change: transform, opacity;
|
||||
pointer-events: none;
|
||||
}
|
||||
@@ -607,27 +611,27 @@ button.danger:not(.link) { background: var(--accent); border: none; color: #fff;
|
||||
gap: 0.3em;
|
||||
font-size: 0.75rem;
|
||||
padding: 0.2em 0.75em 0.2em 0.55em;
|
||||
border-radius: 999px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #ffffff40;
|
||||
background: transparent;
|
||||
transition: background var(--fast), border-color var(--fast);
|
||||
}
|
||||
.pill svg { width: 0.95em; height: 0.95em; }
|
||||
.pill:hover { background: var(--glass); }
|
||||
.follow.on { background: color-mix(in oklab, var(--card-accent) 25%, transparent); border-color: transparent; }
|
||||
.follow.on { background: var(--accent); color: #111; border-color: transparent; }
|
||||
|
||||
.chips { display: flex; gap: 0.4rem; flex-wrap: wrap; justify-content: center; }
|
||||
.chips span {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.1em 0.6em;
|
||||
border-radius: 999px;
|
||||
border-radius: 5px;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
.chips .free { background: #1f7a4d; }
|
||||
.chips .new { background: var(--fg); color: #000; font-weight: 700; }
|
||||
.chips span:first-child { background: color-mix(in oklab, var(--card-accent) 35%, transparent); }
|
||||
.chips span:first-child { background: #252729; }
|
||||
.chips .preview { background: #6a4b16; }
|
||||
.chips .tag { background: transparent; box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--card-accent) 60%, transparent); }
|
||||
.chips .tag { background: transparent; box-shadow: inset 0 0 0 1px var(--line); }
|
||||
|
||||
.desc {
|
||||
margin: 0;
|
||||
@@ -787,7 +791,7 @@ body.no-wave-comments :is(.wave-comments, .wc-bubble), body.zen :is(.wave-commen
|
||||
margin-right: auto;
|
||||
padding: 0.05em 0.6em;
|
||||
border: 1px solid var(--line-strong);
|
||||
border-radius: 999px;
|
||||
border-radius: 5px;
|
||||
background: none;
|
||||
color: var(--fg);
|
||||
font-size: inherit;
|
||||
@@ -885,7 +889,7 @@ body.no-wave-comments :is(.wave-comments, .wc-bubble), body.zen :is(.wave-commen
|
||||
bottom: calc(max(1rem, env(safe-area-inset-bottom)) + 3.4rem);
|
||||
transform: translate(-50%, 1rem);
|
||||
padding: 0.6em 1.1em;
|
||||
border-radius: 999px;
|
||||
border-radius: 5px;
|
||||
background: #222e;
|
||||
border: 1px solid #ffffff22;
|
||||
font-size: 0.9rem;
|
||||
@@ -1006,7 +1010,7 @@ body.zen #up-next { display: none; }
|
||||
}
|
||||
.start-welcome a { color: inherit; }
|
||||
/* A guest's account button says what it is for. */
|
||||
.avatar-btn.guest { width: auto; padding: 0 0.9rem; border-radius: 999px; font-weight: 600; font-size: 0.85rem; color: var(--fg); }
|
||||
.avatar-btn.guest { width: auto; padding: 0 0.9rem; border-radius: 5px; font-weight: 600; font-size: 0.85rem; color: var(--fg); }
|
||||
|
||||
/* Radio sheet: a bottom sheet on phones, a centred box elsewhere. */
|
||||
#station {
|
||||
@@ -1140,10 +1144,7 @@ button.primary:disabled { opacity: 0.35; cursor: default; }
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 1rem;
|
||||
background:
|
||||
radial-gradient(60rem 40rem at 20% 10%, #ff4d6d33, transparent 60%),
|
||||
radial-gradient(50rem 40rem at 90% 90%, #4d6dff2e, transparent 60%),
|
||||
#050507;
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
.login-box {
|
||||
@@ -1224,7 +1225,7 @@ button.primary:disabled { opacity: 0.35; cursor: default; }
|
||||
width: 2.6rem;
|
||||
height: 0.3rem;
|
||||
margin: 0.55rem auto 0;
|
||||
border-radius: 999px;
|
||||
border-radius: 5px;
|
||||
background: #fff5;
|
||||
}
|
||||
.content { width: calc(100vw - 2rem); margin-bottom: 6.5rem; gap: 0.6rem; }
|
||||
@@ -1268,7 +1269,7 @@ body.zen .content { gap: 0.3rem; }
|
||||
.bar-end {
|
||||
gap: 0.15rem;
|
||||
padding: 0.25rem;
|
||||
border-radius: 999px;
|
||||
border-radius: 5px;
|
||||
background: var(--glass);
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
@@ -1331,3 +1332,39 @@ body.zen .content { gap: 0.3rem; }
|
||||
.wc-in { animation: none; }
|
||||
.wc-bubble::before { display: none; }
|
||||
}
|
||||
|
||||
/* Monochrome soundsystem surfaces. Keep the controls still around the sculpture. */
|
||||
.logo, .start-logo, .login-box h1 { font-family: var(--font-display); font-weight: 700; letter-spacing: 0.025em; }
|
||||
.title { font-family: var(--font-display); font-weight: 700; line-height: 1.05; letter-spacing: 0.015em; }
|
||||
.time, .counts, .chips, .station-kicker, .up-next-label { font-family: ui-monospace, monospace; font-variant-numeric: tabular-nums; }
|
||||
#mode-tabs, .bar-end, #up-next { background: var(--panel); border: 1px solid var(--line); backdrop-filter: none; }
|
||||
button { min-height: 2.75rem; }
|
||||
button:hover { border-color: #737578; }
|
||||
button:active { filter: brightness(0.85); }
|
||||
button:disabled { cursor: default; opacity: 0.45; }
|
||||
.icon-btn { min-width: 2.75rem; min-height: 2.75rem; }
|
||||
.chips span { border-radius: 3px; background: #202225; color: var(--fg); border: 1px solid var(--line); }
|
||||
.chips span.free, .chips span.new { background: #252729; color: var(--fg); }
|
||||
.act .ring { background: #191b1d; border: 1px solid var(--line); backdrop-filter: none; }
|
||||
.art { box-shadow: 0 12px 35px #0009, 0 0 0 1px #ffffff24; scale: 1; }
|
||||
/* Fade the canvas spill before the title or navigation, including the caustics. */
|
||||
.liquid-canvas { mask-image: linear-gradient(transparent 2%, #000 18% 72%, transparent 88%); }
|
||||
.card.liquid .art { margin-bottom: 2.8rem; }
|
||||
.sheet, .station-box { box-shadow: 0 20px 80px #000b; }
|
||||
.track-row { padding-block: 0.7rem; }
|
||||
.login-box button[type=submit], .help-box button { color: #111; }
|
||||
.link.danger, #login-error { color: var(--danger); }
|
||||
button.danger:not(.link) { background: var(--danger); color: #111; }
|
||||
#login input { border-radius: var(--radius-s); }
|
||||
@media (max-width: 640px) {
|
||||
#mode-tabs button { padding-inline: 0.7em; }
|
||||
.content { margin-top: 3rem; }
|
||||
.art { width: min(14rem, 29dvh, 58vw); }
|
||||
.card.liquid .art { margin-bottom: 1.8rem; }
|
||||
.actions { gap: 0.5rem; }
|
||||
}
|
||||
@media (max-width: 640px) and (max-height: 700px) {
|
||||
.art { width: min(10rem, 22dvh, 48vw); }
|
||||
.card.liquid .art { margin-bottom: 1.2rem; }
|
||||
.content { gap: 0.25rem; }
|
||||
}
|
||||