Files
Lucas WintherandClaude Opus 5.5 843b6d32bc chore: bootstrap agent-ready docs, rules and design-phase artefacts
Add AGENTS.md as the single source of truth (with CLAUDE.md and GEMINI.md
as pointers), permissions for Claude Code, Codex and Gemini, the docs tree
(architecture, decisions, TODO, permissions, specs, plans), PRODUCT.md and
DESIGN.md, plus the design-phase record: the inspiration log, all mockup
rounds with briefs and screenshots, and the owner's work history.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 06:47:38 +01:00

360 lines
29 KiB
HTML
Raw Permalink 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">
<title>Lucas Winther · as a person (P2 · The desk)</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Newsreader:ital,opsz,wght@0,6..72,400;0,6..72,600;1,6..72,400&family=IBM+Plex+Mono:wght@400;500&family=Reenie+Beanie&display=swap" rel="stylesheet">
<style>
:root{
--desk:#ddd0b8; --desk2:#d3c4a8; --blotter:#a9b597; --paper:#fbf7ee; --kraft:#c9a678; --ink:#26231e; --soft:#6b6457; --line:#cdbf9f;
--red:#b8452f; --tape:rgba(240,214,120,.62); --green:#3e5a47;
--serif:"Newsreader", Georgia, serif; --mono:"IBM Plex Mono", ui-monospace, monospace; --hand:"Reenie Beanie", cursive;
}
*{box-sizing:border-box}
body{margin:0;background:var(--desk);color:var(--ink);font-family:var(--serif);font-size:17px;line-height:1.5;
background-image:repeating-linear-gradient(90deg,transparent 0 160px,rgba(0,0,0,.018) 160px 162px)}
a{color:inherit}
.ph{text-decoration:underline dotted var(--soft);text-underline-offset:3px;cursor:help}
.mock-note{position:fixed;top:10px;right:10px;z-index:99;max-width:300px;background:#fff;border:1px solid #bbb;padding:8px 30px 8px 10px;font:12px/1.4 system-ui,sans-serif;color:#333}
.mock-note b{display:block}
.mock-note button{position:absolute;top:4px;right:6px;border:0;background:none;font-size:16px;cursor:pointer;color:#666}
.wrap{max-width:1160px;margin:0 auto;padding:0 28px 40px}
header{display:grid;grid-template-columns:1fr auto;align-items:end;gap:24px;padding:28px 0 18px}
header h1{margin:0;font-weight:400;font-size:34px;letter-spacing:-.015em;line-height:1.1}
header h1 i{color:var(--soft)}
header p{margin:6px 0 0;color:var(--soft);max-width:560px;font-size:16px}
nav.spaces{font-family:var(--mono);font-size:12.5px;display:flex;gap:16px}
nav.spaces a{text-decoration:none;color:var(--soft)} nav.spaces a[aria-current]{color:var(--ink);border-bottom:1px solid}
.trays{display:flex;gap:6px;align-items:flex-end;margin:0 0 -1px 18px;position:relative;z-index:2;flex-wrap:wrap}
.trays button{font:12.5px var(--mono);border:1px solid var(--line);border-bottom:0;background:var(--paper);padding:6px 12px 5px;cursor:pointer;color:var(--soft)}
.trays button[aria-pressed="true"]{color:var(--ink);padding-bottom:9px;background:var(--blotter);border-color:#8e9a7c}
.trays .tip{font:13px var(--mono);color:var(--soft);margin-left:auto;align-self:center;padding-bottom:6px}
.blotter{background:var(--blotter);border:1px solid #8e9a7c;padding:30px 26px 34px;position:relative;
background-image:radial-gradient(rgba(0,0,0,.04) 1px,transparent 1px);background-size:5px 5px}
.grid{display:grid;grid-template-columns:repeat(4,1fr);grid-auto-rows:auto;gap:26px 24px;align-items:start}
.zone{grid-column:1/-1;min-height:0;font-family:var(--hand);font-size:26px;color:#2f3d2e;line-height:1;margin:6px 0 -10px;display:flex;align-items:center;gap:12px}
.zone::after{content:"";flex:1;border-top:1px dashed rgba(47,61,46,.35)}
.item{all:unset;box-sizing:border-box;display:block;min-height:150px;cursor:pointer;position:relative;background:var(--paper);padding:16px 16px 14px;
transform:rotate(var(--r,0deg));transition:transform .2s ease, box-shadow .2s ease, opacity .2s;
box-shadow:0 1px 1px rgba(40,30,10,.18), 0 2px 6px rgba(40,30,10,.08)}
.item:hover,.item:focus-visible{transform:rotate(0) translateY(-6px);box-shadow:0 2px 2px rgba(40,30,10,.12), 0 14px 24px rgba(40,30,10,.18);z-index:3}
.item:focus-visible{outline:2px solid var(--ink);outline-offset:4px}
.item.dim{opacity:.25}
.item .k{font:11px var(--mono);letter-spacing:.06em;text-transform:uppercase;color:var(--soft);display:block;margin-bottom:6px}
.item h3{margin:0 0 6px;font-weight:600;font-size:19px;line-height:1.2}
.item p{margin:0;font-size:15px;color:#4a443a}
.item .more{display:none}
.item.open{grid-column:span 2;grid-row:span 2;transform:none;cursor:default;z-index:4;box-shadow:0 2px 2px rgba(40,30,10,.12), 0 18px 34px rgba(40,30,10,.2)}
.item.open .more{display:block}
.item.open .less{display:none}
.item .close{display:none;position:absolute;top:8px;right:10px;font:12px var(--mono);background:none;border:0;cursor:pointer;color:var(--soft)}
.item.open .close{display:block}
.tape{position:absolute;top:-10px;left:50%;width:70px;height:20px;margin-left:-35px;background:var(--tape);transform:rotate(-3deg)}
.note{background:#fbf3c9}
.lined{background:var(--paper) repeating-linear-gradient(transparent 0 25px,#d9e2ea 25px 26px);background-position:0 8px;line-height:26px}
.lined p{line-height:26px}
.polaroid{padding:10px 10px 12px;background:#fdfcf8}
.polaroid .ph-img{height:130px;background:var(--c,#7d8a74);display:flex;align-items:flex-end;padding:6px 8px;font:11px var(--mono);color:rgba(255,255,255,.85)}
.polaroid .cap{font-family:var(--hand);font-size:22px;line-height:1.1;margin-top:8px}
.item.open.polaroid .ph-img{height:300px}
.ticket{background:var(--kraft);padding:0;display:grid;grid-template-columns:1fr 56px;min-height:120px;
-webkit-mask:radial-gradient(circle at 0 50%,transparent 8px,#000 9px) left/51% 100% no-repeat, radial-gradient(circle at 100% 50%,transparent 8px,#000 9px) right/51% 100% no-repeat;
mask:radial-gradient(circle at 0 50%,transparent 8px,#000 9px) left/51% 100% no-repeat, radial-gradient(circle at 100% 50%,transparent 8px,#000 9px) right/51% 100% no-repeat}
.ticket .main{padding:14px 16px}
.ticket .stub{border-left:2px dashed rgba(38,35,30,.35);writing-mode:vertical-rl;display:flex;align-items:center;justify-content:center;font:11px var(--mono);letter-spacing:.1em}
.ticket h3{font-size:18px}
.ticket .k{color:#5b4a33}
.card{background:#fff;border-top:6px solid var(--red)}
.clip{background:#efe9dc;font-size:14px}
.clip h3{font-size:17px;font-style:italic;font-weight:400}
.receipt{background:#fff;font:12.5px/1.6 var(--mono);padding:14px 16px 20px;
-webkit-mask:conic-gradient(from -45deg at bottom,#0000,#000 1deg 89deg,#0000 90deg) 50%/12px 100%;mask:conic-gradient(from -45deg at bottom,#0000,#000 1deg 89deg,#0000 90deg) 50%/12px 100%}
.receipt .row{display:flex;justify-content:space-between}
.receipt hr{border:0;border-top:1px dashed #999;margin:6px 0}
/* the recorder */
.recorder{background:#30362f;color:#e9e3d2;padding:16px;grid-column:span 2;grid-row:span 1}
.recorder .k{color:#b8bea9}
.recorder .body{display:grid;grid-template-columns:150px 1fr;gap:16px;align-items:center}
.rec-win{height:84px;background:#1d201c;border:2px solid #4b5246;display:flex;align-items:center;justify-content:space-around}
.rec-win span{width:40px;height:40px;border-radius:50%;background:repeating-conic-gradient(#d6cfbd 0 20deg,#5b5f55 20deg 60deg);border:4px solid #2a2d27}
.rec-keys{display:flex;gap:5px;margin-top:8px}
.rec-keys i{flex:1;height:14px;background:#596055;border-bottom:3px solid #23271f}
.rec-keys i:first-child{background:var(--red)}
.recorder h3{color:#fff}
.recorder p{color:#cfd3c3}
.recorder.open{grid-column:1/-1;grid-row:span 1;cursor:default}
.playing .rec-win span{animation:spin 2.4s linear infinite}
@keyframes spin{to{transform:rotate(1turn)}}
@media (prefers-reduced-motion:reduce){.playing .rec-win span{animation:none}}
.player{display:none;margin-top:16px;border-top:1px solid #4b5246;padding-top:16px;grid-template-columns:1fr 1fr 1.1fr;gap:22px;cursor:auto}
.recorder.open .player{display:grid}
.player h4{font:11px var(--mono);letter-spacing:.08em;text-transform:uppercase;color:#b8bea9;margin:0 0 8px;font-weight:400}
.gigs{display:flex;flex-direction:column;gap:6px}
.gigs button,.songs button{all:unset;cursor:pointer;display:flex;justify-content:space-between;gap:10px;padding:6px 8px;font-size:15px;border:1px solid transparent}
.gigs button small,.songs button small{font:11.5px var(--mono);color:#b8bea9;padding-top:3px}
.gigs button:hover,.songs button:hover{border-color:#6c7462}
.gigs button[aria-current="true"],.songs button[aria-current="true"]{background:#e9e3d2;color:#26231e}
.gigs button[aria-current="true"] small,.songs button[aria-current="true"] small{color:#5c5547}
.gigs button:focus-visible,.songs button:focus-visible,.ctrl button:focus-visible{outline:2px solid #f0d678}
.songs{display:flex;flex-direction:column}
.ctrl{display:flex;align-items:center;gap:10px;margin-top:10px;font:12px var(--mono)}
.ctrl button{all:unset;cursor:pointer;border:1px solid #e9e3d2;width:34px;height:34px;display:grid;place-items:center;border-radius:50%}
.ctrl button.big{width:42px;height:42px;background:#e9e3d2;color:#26231e}
.prog{height:18px;position:relative;cursor:pointer;margin-top:8px}
.prog::before{content:"";position:absolute;left:0;right:0;top:8px;height:2px;background:#596055}
.prog i{position:absolute;left:0;top:8px;height:2px;background:#f0d678;width:0}
.bts{background:var(--paper);color:var(--ink);padding:14px 16px;position:relative}
.bts h5{margin:0;font-size:22px;font-weight:400}
.bts .tabs{display:flex;gap:12px;margin:8px 0 10px;font:12px var(--mono);flex-wrap:wrap}
.bts .tabs button{all:unset;cursor:pointer;color:var(--soft);border-bottom:1px solid transparent}
.bts .tabs button[aria-selected="true"]{color:var(--ink);border-color:var(--ink)}
.bts .tabs button:focus-visible{outline:2px solid var(--red)}
.bts dl{display:grid;grid-template-columns:auto 1fr;gap:3px 12px;margin:0;font-size:15px}
.bts dt{font:12.5px var(--mono);color:var(--soft);padding-top:2px}
.bts dd{margin:0}
.bts .pic{height:130px;background:repeating-linear-gradient(45deg,#c4cbb6 0 8px,#bac2ab 8px 16px);font:11.5px var(--mono);color:#4f5747;display:flex;align-items:flex-end;padding:8px}
.stubs{display:flex;gap:8px;flex-wrap:wrap}
.stubs button{font:12px var(--mono);background:var(--kraft);border:0;padding:8px 10px;cursor:pointer;text-align:left}
.stubs button[disabled]{background:#e4dccd;cursor:default}
.stubs button:hover:not([disabled]){background:#d8b98d}
.hand{font-family:var(--hand);font-size:22px;line-height:1.1;color:var(--red)}
footer{margin-top:26px;font:12px var(--mono);color:var(--soft);display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap}
@media (max-width:900px){.grid{grid-template-columns:repeat(2,1fr)} .player{grid-template-columns:1fr} header{grid-template-columns:1fr}}
@media (max-width:480px){.grid{grid-template-columns:1fr} .item.open,.recorder{grid-column:auto} .recorder .body{grid-template-columns:1fr}}
</style>
</head>
<body>
<div class="mock-note" role="note"><button aria-label="Dismiss note" onclick="this.parentNode.remove()">×</button><b>P2 · The desk</b>An ordered desk: notes, photos, ticket stubs and a tape recorder in labelled zones. Hover lifts a thing, click opens it in place. The recorder holds the live sets. Borrows kigo.studio's liveliness, kept tidy, plus mindofakira's "play, then explore".</div>
<div class="wrap">
<header>
<div>
<h1>Lucas Winther, <i>off the clock</i></h1>
<p>This is roughly what my desk looks like: a few things I've been thinking about, things I found, photos, and the recorder with our gigs on it. Pick anything up.</p>
</div>
<nav class="spaces" aria-label="Spaces"><a href="#">with AI, now</a><a href="#" aria-current="page">as a person</a><a href="#">by hand, before ↗</a></nav>
</header>
<div class="trays" role="group" aria-label="Show only">
<button aria-pressed="true" data-f="all">everything</button>
<button aria-pressed="false" data-f="musing">musings</button>
<button aria-pressed="false" data-f="found">findings</button>
<button aria-pressed="false" data-f="photo">photos</button>
<button aria-pressed="false" data-f="music">music</button>
<span class="tip">hover lifts · click opens · esc puts it back</span>
</div>
<main class="blotter">
<div class="grid" id="grid">
<div class="zone">the recorder &amp; gigs</div>
<div class="item recorder" data-t="music" tabindex="0" role="button" aria-expanded="false" id="rec">
<span class="k">tape recorder · <span class="ph" title="placeholder">Lav Sol</span>, live</span>
<div class="body">
<div><div class="rec-win"><span></span><span></span></div><div class="rec-keys"><i></i><i></i><i></i><i></i></div></div>
<div class="less"><h3>Our gigs, recorded on a phone</h3><p>Four nights, three bedroom projects. Open it to play a set, skip between songs and read what's behind each one.</p></div>
<div class="more"><h3 id="recTitle">Live at Café Retro</h3><p id="recSub"></p></div>
</div>
<div class="player" id="player">
<div>
<h4>Tapes</h4>
<div class="gigs" id="gigs"></div>
</div>
<div>
<h4>Setlist</h4>
<div class="songs" id="songs"></div>
<div class="prog" id="prog" aria-label="Seek"><i id="fill"></i></div>
<div class="ctrl"><button id="prev" aria-label="Previous">«</button><button class="big" id="pp" aria-label="Play">▶</button><button id="next" aria-label="Next">»</button><span id="time">0:00</span></div>
</div>
<div class="bts" id="bts"></div>
</div>
<button class="close" aria-label="Close">put back ×</button>
</div>
<button class="item ticket" data-t="music" data-gig="retro" style="--r:-1.5deg"><div class="main"><span class="k">ticket · 14.03.26</span><h3 class="ph" title="placeholder">Café Retro</h3><p>Five songs, 60 DKK at the door. Opens the recorder at this gig.</p></div><div class="stub">ADMIT ONE</div></button>
<button class="item ticket" data-t="music" data-gig="kaelder" style="--r:1.2deg"><div class="main"><span class="k">ticket · 27.10.25</span><h3 class="ph" title="placeholder">Studenterhuset, basement</h3><p>The loud one. Opens the recorder at this gig.</p></div><div class="stub">Nº 0041</div></button>
<div class="zone">musings</div>
<div class="item note" data-t="musing" style="--r:-1deg" tabindex="0" role="button" aria-expanded="false">
<span class="k">sticky note</span>
<h3 class="ph" title="placeholder">Songs I still haven't finished</h3>
<p class="less">1. the one about the ferry<br>2. "Halvvejs", second verse<br>3. …</p>
<div class="more"><p>1. the one about the ferry: it's done, I just don't like the bridge.<br>2. "Halvvejs", second verse. Signe says it's fine. It's not fine.<br>3. A song made only of things overheard on the 5A bus.<br>4. An instrumental for the rowing-game soundtrack I never made.</p><p class="hand">update: the ferry one is done-ish</p></div>
<button class="close" aria-label="Close">put back ×</button>
</div>
<div class="item lined" data-t="musing" style="--r:.8deg" tabindex="0" role="button" aria-expanded="false">
<span class="k">essay · Oct 2026</span>
<h3 class="ph" title="placeholder">On playing to eleven people</h3>
<p class="less">Small rooms are harder than big ones. You can see every face…</p>
<div class="more"><p>Small rooms are harder than big ones. You can see every face, and every face can see your left hand. At the October gig there were eleven people and a dog, and I counted them twice during the first song.</p><p>What helped: looking at Jonas instead of the room. What didn't: the third coffee. What I'd do again: tell the story behind the song before playing it. People listen differently when they know where a song came from. That's partly why the recorder above has notes on every track.</p></div>
<button class="close" aria-label="Close">put back ×</button>
</div>
<div class="item card" data-t="musing" style="--r:-.6deg" tabindex="0" role="button" aria-expanded="false">
<span class="k">index card</span>
<h3>Writing code vs. writing songs</h3>
<p class="less">Both are mostly deleting. One has a test suite.</p>
<div class="more"><p>Both are mostly deleting. One has a test suite.</p><p>Lately I've been working with AI tools all day (that's <a href="#">the other side of the site</a>). Songwriting is where I deliberately don't. It's the one place I want to be slow and a bit bad at something.</p></div>
<button class="close" aria-label="Close">put back ×</button>
</div>
<div class="item receipt" data-t="musing" style="--r:1.4deg" tabindex="0" role="button" aria-expanded="false">
<span class="k">receipt</span>
<div class="row"><span>strings ×2</span><span>178</span></div>
<div class="row"><span>room, 4 thu</span><span>360</span></div>
<div class="row"><span>capo (lost)</span><span>95</span></div>
<hr><div class="row"><b>band, Oct</b><b>633 DKK</b></div>
<div class="more"><hr><p style="font-size:12.5px">Tracked in the <a href="#">Monthly Expenses Tracker</a>, which I built for exactly this kind of thing. Third capo this year.</p></div>
<button class="close" aria-label="Close">put back ×</button>
</div>
<div class="zone">found &amp; kept</div>
<div class="item clip" data-t="found" style="--r:.6deg" tabindex="0" role="button" aria-expanded="false">
<span class="k">clipping · essay</span>
<h3 class="ph" title="placeholder">"The case for small software"</h3>
<p class="less">Read it twice. Applies to bands too.</p>
<div class="more"><p>Read it twice. The argument: things made for a few people can be made with more care. Applies to bands too. <a href="#">Read it →</a></p></div>
<button class="close" aria-label="Close">put back ×</button>
</div>
<div class="item card" data-t="found" style="--r:-1.2deg;border-top-color:var(--green)" tabindex="0" role="button" aria-expanded="false">
<span class="k">record</span>
<h3 class="ph" title="placeholder">Bill Evans, Sunday at the Village Vanguard</h3>
<p class="less">You can hear the glasses clink.</p>
<div class="more"><p>You can hear the glasses clink and someone laughing at the back. It's handcam, in 1961. This is the record that made me stop caring about clean recordings.</p></div>
<button class="close" aria-label="Close">put back ×</button>
</div>
<div class="item note" data-t="found" style="--r:.9deg;background:#f6e1d6" tabindex="0" role="button" aria-expanded="false">
<span class="k">link</span>
<h3 class="ph" title="placeholder">Every bus shelter in Denmark, photographed</h3>
<p class="less">Someone did it. All of them.</p>
<div class="more"><p>Someone did it. All of them. There's a map. I've spent an embarrassing amount of time on it. <a href="#">Have a look →</a></p></div>
<button class="close" aria-label="Close">put back ×</button>
</div>
<div class="item clip" data-t="found" style="--r:-.4deg" tabindex="0" role="button" aria-expanded="false">
<span class="k">tool</span>
<h3 class="ph" title="placeholder">A guitar tuner that doesn't nag</h3>
<p class="less">No account, no ads, no subscription.</p>
<div class="more"><p>No account, no ads, no subscription. Just a needle. <a href="#">Link →</a></p></div>
<button class="close" aria-label="Close">put back ×</button>
</div>
<div class="zone">photos</div>
<div class="item polaroid" data-t="photo" style="--r:-2deg" tabindex="0" role="button" aria-expanded="false"><span class="tape"></span>
<div class="ph-img" style="--c:#7b6a55">photo: rehearsal room, Thursday</div><div class="cap">the room, 90 kr/hour</div>
<div class="more"><p style="margin-top:8px;font-size:15px">Egg-box foam on one wall, a broken amp nobody will throw out. We've rehearsed here every Thursday since <span class="ph" title="placeholder">2023</span>.</p></div>
<button class="close" aria-label="Close">put back ×</button>
</div>
<div class="item polaroid" data-t="photo" style="--r:1.5deg" tabindex="0" role="button" aria-expanded="false"><span class="tape"></span>
<div class="ph-img" style="--c:#4e6158">photo: ferry deck, grey sea</div><div class="cap">where "Slow ferry" came from</div>
<div class="more"><p style="margin-top:8px;font-size:15px">Taken on the way to <span class="ph" title="placeholder">Bornholm</span>. I wrote the first verse on the back of the ticket.</p></div>
<button class="close" aria-label="Close">put back ×</button>
</div>
<div class="item polaroid" data-t="photo" style="--r:-.8deg" tabindex="0" role="button" aria-expanded="false"><span class="tape"></span>
<div class="ph-img" style="--c:#8f5b46">photo: setlist on the floor</div><div class="cap">gaffer tape &amp; hope</div>
<div class="more"><p style="margin-top:8px;font-size:15px">Café Retro, March. We skipped song four and nobody noticed.</p></div>
<button class="close" aria-label="Close">put back ×</button>
</div>
<div class="item polaroid" data-t="photo" style="--r:1deg" tabindex="0" role="button" aria-expanded="false"><span class="tape"></span>
<div class="ph-img" style="--c:#6b7b5a">photo: the oars, rowing game</div><div class="cap">old project, still in the hallway</div>
<div class="more"><p style="margin-top:8px;font-size:15px">Two real oars from the <a href="#">Mixed-Reality Rowing Game</a>. They live in the hallway now.</p></div>
<button class="close" aria-label="Close">put back ×</button>
</div>
</div>
</main>
<footer><span>Placeholders (dotted underline): band name, members, songs, venues, dates, essay and finding titles, photos.</span><span><a href="mailto:lucas@lucaswinther.info">lucas@lucaswinther.info</a> · <a href="#">GitHub</a> · <a href="#">blog</a></span></footer>
</div>
<script>
const gigs = [
{id:"retro", name:"Café Retro", date:"14 Mar 2026", note:"handcam from the bar", set:[["Grå morgen",252],["Kitchen light",208],["Slow ferry",301],["Halvvejs",196],["Paper boats",244]]},
{id:"kaelder", name:"Studenterhuset basement", date:"27 Oct 2025", note:"phone on a beer crate", set:[["Paper boats",231],["Grå morgen",270],["New one (no name)",188],["Slow ferry",322]]},
{id:"have", name:"Garden party at Ida's", date:"9 Aug 2025", note:"acoustic, wasps", set:[["Kitchen light",190],["Halvvejs",205],["Grå morgen",240]]},
{id:"kitchen", name:"Kitchen demos", date:"winter 2024", note:"one mic, fridge hum", set:[["Kitchen light",174],["Slow ferry",142]]},
];
const S = {
"Grå morgen":{who:[["Lucas","the riff, lead guitar"],["Signe","vocals"],["Mads","bass"],["Jonas","drums, brushes"]],story:"Started as a warm-up riff. Signe wrote words over it in one rehearsal.",lyric:"Grey morning, the coffee knows before I do.",pic:"photo: Signe, red stage light"},
"Kitchen light":{who:[["Lucas","acoustic, verse 2"],["Signe","vocals, Rhodes"],["Mads","bass"],["Jonas","shaker"]],story:"Written in a kitchen at 2am. Every live version is slower than the last.",lyric:"Leave the kitchen light on, I'll find my way.",pic:"photo: one lamp, a kitchen table"},
"Slow ferry":{who:[["Lucas","guitar, delay"],["Signe","keys"],["Mads","the long bass notes"],["Jonas","drums"]],story:"First verse written on the back of a ferry ticket. The one we let run long.",lyric:"Nothing to do but stand at the rail.",pic:"photo: Mads, eyes closed"},
"Halvvejs":{who:[["Lucas","guitar"],["Signe","vocals"],["Mads","bass"],["Jonas","drums"]],story:"Our only Danish song. The title means \"halfway\".",lyric:"Halvvejs hjem, halvvejs væk.",pic:"photo: the setlist on the floor"},
"Paper boats":{who:[["Lucas","guitar, harmony"],["Signe","vocals"],["Mads","bass"],["Jonas","count-in (too fast)"]],story:"Usually the closer.",lyric:"Fold it twice and let it go.",pic:"photo: blurry, happy crowd"},
};
const fmt=s=>Math.floor(s/60)+":"+String(Math.floor(s%60)).padStart(2,"0");
const grid=document.getElementById("grid"), rec=document.getElementById("rec");
function closeAll(except){ grid.querySelectorAll(".item.open").forEach(i=>{ if(i!==except){i.classList.remove("open"); i.setAttribute("aria-expanded","false"); if(i===rec && playing) setPlay(false);} }); }
grid.querySelectorAll(".item").forEach(it=>{
if(it.classList.contains("ticket")){ it.addEventListener("click",()=>{ openRec(it.dataset.gig); }); return; }
it.addEventListener("click",e=>{
if(e.target.closest(".close")){ if(it===rec && playing) setPlay(false); it.classList.remove("open"); it.setAttribute("aria-expanded","false"); e.stopPropagation(); return; }
if(it.classList.contains("open")) return;
if(it===rec){ openRec(cur?cur.id:"retro"); return; }
closeAll(it); it.classList.add("open"); it.setAttribute("aria-expanded","true");
});
it.addEventListener("keydown",e=>{ if((e.key==="Enter"||e.key===" ") && e.target===it){ e.preventDefault(); it.click(); } });
});
document.addEventListener("keydown",e=>{ if(e.key==="Escape") closeAll(); });
document.querySelectorAll(".trays button").forEach(b=>b.addEventListener("click",()=>{
document.querySelectorAll(".trays button").forEach(x=>x.setAttribute("aria-pressed",x===b));
grid.querySelectorAll(".item").forEach(i=>i.classList.toggle("dim", b.dataset.f!=="all" && i.dataset.t!==b.dataset.f));
}));
let cur=null, si=0, pos=0, playing=false, timer=null, tab="who";
function openRec(id, songIdx){
closeAll(rec); rec.classList.add("open"); rec.setAttribute("aria-expanded","true");
cur=gigs.find(g=>g.id===id); si=songIdx||0; pos=0;
document.getElementById("recTitle").textContent=cur.name;
document.getElementById("recSub").textContent=cur.date+" · "+cur.note+" · "+cur.set.length+" songs";
document.getElementById("gigs").innerHTML=gigs.map(g=>`<button data-id="${g.id}" aria-current="${g===cur}">${g.name}<small>${g.date.slice(-4)}</small></button>`).join("");
document.querySelectorAll("#gigs button").forEach(b=>b.onclick=e=>{e.stopPropagation(); openRec(b.dataset.id);});
renderSongs(); renderBts();
rec.scrollIntoView({block:"nearest"});
}
function renderSongs(){
document.getElementById("songs").innerHTML=cur.set.map((s,i)=>`<button data-i="${i}" aria-current="${i===si}">${s[0]}<small>${fmt(s[1])}</small></button>`).join("");
document.querySelectorAll("#songs button").forEach(b=>b.onclick=e=>{e.stopPropagation(); go(+b.dataset.i);});
update();
}
function go(i){ si=i; pos=0; renderSongs(); renderBts(); }
function renderBts(){
const name=cur.set[si][0], s=S[name];
const others=gigs.map(g=>({g, j:g.set.findIndex(x=>x[0]===name)})).filter(o=>o.j>-1);
let body="";
if(!s) body=`<p style="margin:0;font-size:15px;color:var(--soft)">No notes yet. We've only played it once and it doesn't have a name.</p>`;
else if(tab==="who") body=`<dl>${s.who.map(w=>`<dt>${w[0]}</dt><dd>${w[1]}</dd>`).join("")}</dl>`;
else if(tab==="story") body=`<p class="hand" style="margin:0 0 6px">"${s.lyric}"</p><p style="margin:0;font-size:15px">${s.story}</p>`;
else if(tab==="pic") body=`<div class="pic">${s.pic} · ${cur.date}</div>`;
else body=`<div class="stubs">${others.map(o=>`<button data-g="${o.g.id}" data-j="${o.j}" ${o.g===cur?"disabled":""}>${o.g.name}<br>${o.g.date} · ${fmt(o.g.set[o.j][1])}${o.g===cur?" · now":""}</button>`).join("")}</div>`;
const bts=document.getElementById("bts");
bts.innerHTML=`<h4 style="color:var(--soft)">Behind the song</h4><h5>${name}</h5>
<div class="tabs" role="tablist">${[["who","who plays what"],["story","lyric & origin"],["pic","photo"],["other",`same song, other nights (${others.length})`]].map(([k,l])=>`<button role="tab" data-k="${k}" aria-selected="${tab===k}">${l}</button>`).join("")}</div>${body}`;
bts.querySelectorAll(".tabs button").forEach(b=>b.onclick=e=>{e.stopPropagation(); tab=b.dataset.k; renderBts();});
bts.querySelectorAll(".stubs button").forEach(b=>b.onclick=e=>{e.stopPropagation(); openRec(b.dataset.g,+b.dataset.j);});
}
function setPlay(on){
playing=on; clearInterval(timer); rec.classList.toggle("playing",on);
const pp=document.getElementById("pp"); pp.textContent=on?"❚❚":"▶"; pp.setAttribute("aria-label",on?"Pause":"Play");
if(on) timer=setInterval(()=>{ pos++; if(pos>=cur.set[si][1]){ if(si<cur.set.length-1){go(si+1);} else {pos=cur.set[si][1]; setPlay(false);} } update(); },1000);
}
function update(){
const L=cur.set[si][1];
document.getElementById("fill").style.width=(pos/L*100)+"%";
document.getElementById("time").textContent=fmt(pos)+" / "+fmt(L);
}
document.getElementById("pp").onclick=e=>{e.stopPropagation(); setPlay(!playing);};
document.getElementById("prev").onclick=e=>{e.stopPropagation(); go(Math.max(0,si-1));};
document.getElementById("next").onclick=e=>{e.stopPropagation(); go(Math.min(cur.set.length-1,si+1));};
document.getElementById("prog").onclick=e=>{e.stopPropagation(); const r=e.currentTarget.getBoundingClientRect(); pos=Math.floor((e.clientX-r.left)/r.width*cur.set[si][1]); update();};
document.getElementById("player").addEventListener("click",e=>e.stopPropagation());
</script>
</body>
</html>