Files
pl8list-rewind/web/index.html
T
nvrl b4470850bf Rebuild front end in TypeScript with a pager, crossfades and reactive visuals
The front end moves to web/ (Vite, TypeScript, no framework) and is built
into web/dist, which the server embeds. hls.js is bundled from npm and
loaded on demand instead of vendored.

The feed is a custom pager instead of CSS scroll snapping: drag and flick
with a spring that keeps the finger's speed, a rubber band at the ends,
one page per wheel or trackpad gesture, and only three cards in the DOM.

Playback uses two decks. The next track is buffered shortly after the
current one starts and tracks crossfade through a Web Audio graph; on iOS,
where that would break lock-screen playback, they switch with a cut. An
analyser drives a bass pulse of the artwork and its glow, and each card
takes its accent colour from the artist image. Styles start from design
tokens and respect reduced motion.

Docker gets a Node build stage; legal pages use their own small stylesheet.
2026-11-01 12:57:17 +01:00

188 lines
9.4 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>pl8list ∞</title>
<meta name="description" content="Endless scroll through pl8list, tuned to what you like.">
<meta name="theme-color" content="#000000">
<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">
<meta name="apple-mobile-web-app-title" content="pl8 ∞">
<link rel="manifest" href="/manifest.webmanifest">
<link rel="icon" href="/icons/icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/icons/apple-touch-icon.png">
<!-- Everything, hls.js included, is bundled and served by us: no visitor address
goes to a CDN or to pl8list. -->
<script type="module" src="/src/main.ts"></script>
</head>
<body>
<!-- Shared icons, referenced with <use href="#i-…">. -->
<svg class="sprite" aria-hidden="true">
<symbol id="i-heart" viewBox="0 0 24 24"><path fill="currentColor" d="M12 21s-7.5-4.6-10-9.3C.4 8.4 2.3 4.5 6 4.5c2.2 0 3.6 1.2 6 3.6 2.4-2.4 3.8-3.6 6-3.6 3.7 0 5.6 3.9 4 7.2C19.5 16.4 12 21 12 21z"/></symbol>
<symbol id="i-x" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" d="M6 6l12 12M18 6 6 18"/></symbol>
<symbol id="i-share" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" d="M12 3v12M7 8l5-5 5 5M5 13v6a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-6"/></symbol>
<symbol id="i-open" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" d="M7 17 17 7M9 7h8v8"/></symbol>
<symbol id="i-down" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" d="M12 4v16M6 14l6 6 6-6"/></symbol>
<symbol id="i-play" viewBox="0 0 24 24"><path fill="currentColor" d="M8 5.5v13a1 1 0 0 0 1.5.86l10.5-6.5a1 1 0 0 0 0-1.72L9.5 4.64A1 1 0 0 0 8 5.5z"/></symbol>
<symbol id="i-pause" viewBox="0 0 24 24"><path fill="currentColor" d="M7 4.5h3.5v15H7zM13.5 4.5H17v15h-3.5z"/></symbol>
</svg>
<header id="bar" hidden>
<span class="logo">pl8list <b>∞</b></span>
<span id="stats"></span>
<button id="filters-btn" type="button" aria-controls="filters" aria-expanded="false">filters</button>
<button id="likes-btn" type="button" aria-controls="likes" aria-expanded="false">likes</button>
<button id="help-btn" type="button" title="shortcuts (?)" aria-label="help">?</button>
<button id="account-btn" type="button" aria-controls="account" aria-expanded="false" title="account"></button>
</header>
<div id="login" hidden>
<div class="login-box">
<h1>pl8list <b>∞</b></h1>
<p class="tagline">endless scroll through pl8list, tuned to what you like</p>
<form id="email-form">
<label for="email">sign in with your pl8list email</label>
<input id="email" type="email" name="email" autocomplete="email" required placeholder="you@example.com">
<button type="submit">send code</button>
</form>
<form id="code-form" hidden>
<label for="code">pl8list sent a 6-digit code to <span id="sent-to"></span></label>
<input id="code" name="code" inputmode="numeric" autocomplete="one-time-code" pattern="[0-9]{6}"
maxlength="6" required placeholder="123456">
<label class="check">
<input type="checkbox" id="connect" name="connect">
<span>stay connected to pl8list on this device
<small>keeps your feed and library in sync, and saves what you like and follow here to
your pl8list library. Your pl8list login is stored encrypted with a key only this browser
holds. Turn it off any time under your account.</small></span>
</label>
<button type="submit">sign in</button>
<div class="login-links">
<button type="button" id="resend" class="link">resend code</button>
<button type="button" id="restart" class="link">use another email</button>
</div>
</form>
<button type="button" id="login-cancel" class="link" hidden>cancel, keep using the app</button>
<p id="login-error" role="alert"></p>
<p class="fineprint">
No account? <a href="https://pl8list.com/signup" target="_blank" rel="noopener">sign up on pl8list</a>.
This site is not affiliated with pl8list. Your email and code go to pl8list, which signs this
server in for you. Unless you choose to stay connected, we use that pl8list login once to read
your feed and library, then log it out; it is never stored. We keep your pl8list handle, the
artists you follow and what you play and like, to pick tracks for you.
</p>
<p class="fineprint legal-links">
<a href="/datenschutz">Datenschutz / privacy</a> · <a href="/impressum">Impressum</a>
</p>
</div>
</div>
<form id="filters" class="panel" hidden>
<label><input type="checkbox" name="free"> free only</label>
<label>bpm <input type="number" name="bpm_min" min="40" max="250" placeholder="min"></label>
<label>– <input type="number" name="bpm_max" min="40" max="250" placeholder="max"></label>
<button type="submit">apply</button>
<button type="button" id="filters-clear" class="link">clear</button>
</form>
<aside id="likes" class="panel" hidden>
<h2>likes</h2>
<ol id="likes-list"></ol>
</aside>
<aside id="account" class="panel" hidden>
<h2 id="account-handle"></h2>
<section id="sync" aria-labelledby="sync-title">
<h3 id="sync-title">pl8list sync</h3>
<p id="sync-status"></p>
<button type="button" id="sync-btn">resync with pl8list</button>
<button type="button" id="disconnect-btn" class="link" hidden>stop staying connected on this device</button>
</section>
<button type="button" id="logout-btn">sign out</button>
<button type="button" id="logout-all-btn" class="link">sign out on all devices</button>
<a href="/account/export" download>download my data</a>
<button type="button" id="delete-open" class="link danger">delete account…</button>
<form id="delete-form" hidden>
<label for="delete-confirm">This deletes your likes and listening history here, right away.
Your pl8list account is not touched. Type <b id="delete-handle"></b> to confirm.</label>
<input id="delete-confirm" name="confirm" autocomplete="off" required>
<button type="submit" class="danger">delete my account</button>
</form>
<p class="fineprint legal-links">
<a href="/datenschutz">Datenschutz / privacy</a> · <a href="/impressum">Impressum</a>
</p>
</aside>
<main id="feed"></main>
<p id="feed-status" role="status" hidden></p>
<div id="start" hidden>
<button type="button">tap anywhere to start</button>
</div>
<div id="help" hidden>
<div class="help-box">
<h2>controls</h2>
<dl class="touch-only">
<dt>tap</dt><dd>play / pause</dd>
<dt>double-tap</dt><dd>like</dd>
<dt>swipe up / down</dt><dd>next / previous</dd>
<dt>drag waveform</dt><dd>seek</dd>
</dl>
<dl class="pointer-only">
<dt>click · space</dt><dd>play / pause</dd>
<dt>double-click · l</dt><dd>like</dd>
<dt>↓ j · ↑ k · wheel</dt><dd>next / previous</dd>
<dt>← →</dt><dd>seek 10 s</dd>
<dt>f</dt><dd>follow artist</dd>
<dt>x</dt><dd>not for me</dd>
<dt>s</dt><dd>share</dd>
<dt>m</dt><dd>mute</dd>
<dt>?</dt><dd>this help</dd>
</dl>
<p>Lock-screen and headphone buttons work too.</p>
<button type="button" id="help-close">got it</button>
</div>
</div>
<div id="toast" role="status" aria-live="polite"></div>
<template id="card-tpl">
<section class="card">
<div class="bg"></div>
<div class="glow"></div>
<div class="content">
<div class="art">
<span class="initial"></span>
<span class="paused-badge"><svg><use href="#i-play"/></svg></span>
<span class="spinner"></span>
</div>
<h1 class="title"></h1>
<div class="byline">
<a class="artist" target="_blank" rel="noopener"></a>
<button type="button" class="follow" title="follow artist (f)">follow</button>
</div>
<div class="chips"></div>
<p class="desc"></p>
<div class="wave-wrap">
<canvas class="wave"></canvas>
<span class="wave-tip"></span>
</div>
<div class="time"><span class="cur">0:00</span><span class="dur"></span></div>
<div class="counts"></div>
</div>
<nav class="actions">
<button type="button" class="like" title="like (l)" aria-label="like"><svg><use href="#i-heart"/></svg></button>
<button type="button" class="dislike" title="not for me (x)" aria-label="not for me"><svg><use href="#i-x"/></svg></button>
<button type="button" class="share" title="share (s)" aria-label="share"><svg><use href="#i-share"/></svg></button>
<a class="open" target="_blank" rel="noopener" title="open on pl8list" aria-label="open on pl8list"><svg><use href="#i-open"/></svg></a>
<button type="button" class="next" title="next (j)" aria-label="next"><svg><use href="#i-down"/></svg></button>
</nav>
</section>
</template>
</body>
</html>