website v3

This commit is contained in:
zoop 2026-09-08 19:08:35 -04:00
parent 9a0c184515
commit 1f6b5aef3d
No known key found for this signature in database
GPG Key ID: 15C0D336C9E08F3F
3 changed files with 212 additions and 49 deletions

View File

@ -4,7 +4,33 @@
---
<section class="section" id="np-section" hidden>
<div class="music">
<button
class="music__trigger"
type="button"
data-music-open
aria-haspopup="dialog"
data-umami-event="music-open"
hidden
>
<span class="music__note" aria-hidden="true">&#9835;</span>
<span class="music__label" id="music-label">now playing</span>
<span class="music__arw" aria-hidden="true">&#8594;</span>
</button>
<dialog class="music-wall" data-music-dialog aria-label="music">
<div class="music-wall__bar">
<span class="music-wall__title">music</span>
<button
class="music-wall__close"
type="button"
data-music-close
aria-label="close"
autofocus>&#10005;</button>
</div>
<div class="music-wall__body">
<div class="music-block" id="np-section" hidden>
<h2 class="label">now playing</h2>
<div class="np" id="np">
<div class="np__body">
@ -38,21 +64,137 @@
<div class="np__prog" id="np-prog" hidden><div class="np__prog-fill" id="np-prog-fill"></div></div>
</div>
</div>
</section>
</div>
<section class="section top" id="top-section" hidden>
<div class="music-block top" id="top-section" hidden>
<h2 class="label">top tracks <span class="label__sub">last 4 weeks</span></h2>
<div class="mosaic" id="mosaic"></div>
<div class="mosaic__caps" id="mosaic-caps"></div>
</section>
</div>
</div>
</dialog>
</div>
<style is:global>
.music {
margin-top: var(--gap);
}
.music__trigger {
display: inline-flex;
align-items: center;
gap: 0.55rem;
padding: 0.35rem 0;
background: none;
border: 0;
cursor: pointer;
font-family: var(--mono);
font-size: 0.78rem;
letter-spacing: 0.02em;
color: var(--muted);
transition: color 0.18s ease;
}
.music__trigger:hover {
color: var(--accent-2);
}
.music__trigger:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 4px;
border-radius: 3px;
}
.music__note {
color: var(--accent);
font-size: 0.9rem;
}
.music__trigger--live .music__note {
animation: music-pulse 1.6s ease-in-out infinite;
}
@keyframes music-pulse {
50% {
opacity: 0.35;
}
}
.music__label {
max-width: 22ch;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.music__arw {
color: var(--faint);
transition: transform 0.22s cubic-bezier(0.16, 1, 0.3, 1);
}
.music__trigger:hover .music__arw {
transform: translateX(0.25em);
}
.music-wall {
margin: auto;
width: min(24rem, calc(100vw - 1.5rem));
max-height: min(82vh, 40rem);
padding: 0;
border: 1px solid var(--line);
border-radius: 8px;
background: var(--paper-2);
color: var(--ink);
box-shadow: 0 24px 60px -20px rgba(0, 0, 0, 0.75);
overflow: hidden;
}
.music-wall[open] {
animation: music-in 0.28s cubic-bezier(0.17, 0.89, 0.32, 1.28);
}
@keyframes music-in {
from {
opacity: 0;
transform: translateY(8px) scale(0.96);
}
}
.music-wall::backdrop {
background: rgba(6, 5, 4, 0.68);
backdrop-filter: blur(3px);
}
.music-wall__bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.6rem 0.85rem;
border-bottom: 1px solid var(--line);
}
.music-wall__title {
font-family: var(--mono);
font-size: 0.72rem;
letter-spacing: 0.1em;
color: var(--faint);
}
.music-wall__close {
background: none;
border: 0;
color: var(--muted);
cursor: pointer;
font-size: 0.85rem;
line-height: 1;
padding: 0.3rem;
}
.music-wall__close:hover {
color: var(--accent);
}
.music-wall__body {
padding: 1rem;
overflow-y: auto;
max-height: calc(min(82vh, 40rem) - 2.6rem);
}
.music-block + .music-block {
margin-top: 1.4rem;
}
.music-block .label {
margin-bottom: 0.7rem;
}
.np {
width: 100%;
max-width: 22rem;
border: 1px solid var(--faint);
background: #151412;
}
@ -161,7 +303,6 @@
gap: 3px;
aspect-ratio: 4 / 3;
width: 100%;
max-width: 268px;
}
.mosaic__cell {
position: relative;
@ -205,7 +346,7 @@
opacity: 0;
transition: opacity 0.14s ease;
pointer-events: none;
white-space: nowrap;
white-space: normal;
}
.mosaic__rank {
color: var(--faint);
@ -257,16 +398,23 @@
}
}
@media (max-width: 560px) {
.np {
max-width: none;
}
.mosaic__cap {
white-space: normal;
@media (prefers-reduced-motion: reduce) {
.music__trigger--live .music__note {
animation: none;
}
}
</style>
<script>
import "../scripts/music.ts";
const dialog = document.querySelector<HTMLDialogElement>("[data-music-dialog]");
const open = document.querySelector<HTMLButtonElement>("[data-music-open]");
const close = document.querySelector<HTMLButtonElement>("[data-music-close]");
open?.addEventListener("click", () => dialog?.showModal());
close?.addEventListener("click", () => dialog?.close());
dialog?.addEventListener("click", (e) => {
if (e.target === dialog) dialog.close();
});
</script>

View File

@ -12,8 +12,6 @@ import { site, elsewhere } from "../data/site";
<p class="bio">{site.bio}</p>
</section>
<Music />
<section class="section">
<h2 class="label">elsewhere</h2>
<nav class="pagenav" aria-label="more">
@ -35,4 +33,5 @@ import { site, elsewhere } from "../data/site";
<Webrings />
<Buttons />
<Music />
</Base>

View File

@ -142,6 +142,22 @@ async function load(): Promise<void> {
if (!data || !data.configured) return;
if (data.nowPlaying) renderNowPlaying(data.nowPlaying as NP);
if (Array.isArray(data.top)) renderTop(data.top as Track[]);
const trigger = document.querySelector<HTMLButtonElement>(".music__trigger");
if (trigger && (data.nowPlaying || (Array.isArray(data.top) && data.top.length))) {
trigger.hidden = false;
const label = $("music-label");
const np = data.nowPlaying as NP | null;
if (label && np) {
if (np.isPlaying) {
trigger.classList.add("music__trigger--live");
label.textContent = np.title;
} else if (np.playedAt) {
label.textContent = "last played";
}
}
}
}
load();