aboutsummaryrefslogtreecommitdiff
path: root/working/music-svg-app-design/music-svg-directions.html
blob: 07b0456e44e1328c37091871e5b645faace0fc46 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Music SVG application — six directions</title>
<style>
:root{--ground:#151311;--panel:#100f0f;--raise:#1a1917;--well:#090a0b;--cream:#f3e7c5;--steel:#969385;--dim:#6f7478;--gold:#e2a038;--goldhi:#ffbe54;--amber:#f0b552;--green:#7fe0a0;--red:#e2543f;--line:#393226;--mono:"BerkeleyMono Nerd Font","Berkeley Mono",monospace}*{box-sizing:border-box}body{margin:0;background:radial-gradient(ellipse at 50% -20%,#302818 0,transparent 55%),var(--ground);color:var(--cream);font:14px/1.35 var(--mono)}header{padding:20px max(20px,calc((100vw - 1320px)/2));border-bottom:1px solid var(--line);background:#100f0fdd;position:sticky;top:0;z-index:4;backdrop-filter:blur(12px)}.eyebrow{color:var(--steel);font-size:10px;letter-spacing:.22em;text-transform:uppercase}.head{display:flex;gap:20px;align-items:end;justify-content:space-between;flex-wrap:wrap}.head h1{font-size:22px;color:var(--goldhi);margin:3px 0}.head p{max-width:640px;margin:0;color:var(--steel)}.tabs{display:flex;gap:6px;flex-wrap:wrap}.tabs button,.button{font:inherit;color:var(--steel);background:var(--raise);border:1px solid var(--line);border-radius:5px;padding:7px 9px;cursor:pointer}.tabs button:hover,.tabs button.on,.button:hover{color:var(--panel);background:var(--gold);border-color:var(--goldhi)}main{max-width:1320px;margin:auto;padding:28px 20px 60px}.note{color:var(--steel);margin:0 0 14px}.concept{display:none;min-height:650px}.concept.on{display:block}.face{min-height:640px;padding:20px;border:1px solid #5d471d;border-radius:16px;background:linear-gradient(135deg,#211b12,#0b0b0b 45%,#19150f);box-shadow:inset 0 1px #ffffff13,0 18px 55px #0008;position:relative;overflow:hidden}.face:before,.face:after{content:"";position:absolute;left:16px;right:16px;height:2px;background:linear-gradient(90deg,transparent,var(--gold),var(--goldhi),var(--gold),transparent);opacity:.9}.face:before{top:11px}.face:after{bottom:11px}.label{color:var(--gold);font-size:10px;letter-spacing:.18em;text-transform:uppercase}.well{background:linear-gradient(#060707,#121313);border:1px solid #342b1b;border-radius:8px;box-shadow:inset 0 2px 12px #000;padding:13px}.screen{color:var(--green);text-shadow:0 0 8px #7fe0a077}.title{font:600 clamp(20px,3vw,38px) Georgia,serif;color:var(--cream)}.sub{color:var(--steel)}.gold{color:var(--goldhi)}.lamp{display:inline-block;width:9px;height:9px;border-radius:100%;background:#312818;border:1px solid #6b5223}.lamp.on{background:var(--goldhi);box-shadow:0 0 12px #ffbe54}.lamp.live{background:var(--green);box-shadow:0 0 12px #7fe0a0}.lamp.warn{background:var(--red);box-shadow:0 0 12px #e2543f}.transport{display:flex;gap:8px;align-items:center;justify-content:center}.transport button{width:46px;height:39px;border-radius:6px;border:1px solid #685025;background:linear-gradient(#3a2e1c,#17130d);color:var(--goldhi);font:18px var(--mono);cursor:pointer}.transport button.play{background:linear-gradient(#f0c46c,#8f671f);color:#181109}.transport button:active{transform:translateY(1px);filter:brightness(1.2)}.queue{display:grid;gap:4px}.track{display:grid;grid-template-columns:30px 1fr auto;gap:10px;align-items:center;padding:7px 8px;border-bottom:1px solid #ffffff0b;cursor:pointer}.track:hover,.track.current{background:#e2a03818}.track.current{border-left:3px solid var(--goldhi)}.track .num{color:var(--gold)}.track .meta{color:var(--steel);font-size:11px}.progress{height:7px;background:#28221a;border:1px solid #554321;border-radius:8px;overflow:hidden}.progress i{display:block;height:100%;width:42%;background:linear-gradient(90deg,#8f671f,var(--goldhi),#fff0af);box-shadow:0 0 8px var(--goldhi)}.presets{display:flex;gap:6px;flex-wrap:wrap}.preset{padding:5px 8px;border:1px solid #49391e;border-radius:20px;background:#17130e;color:var(--steel);cursor:pointer}.preset.on{color:var(--panel);background:var(--goldhi)}.knob{width:84px;height:84px;border:8px solid #5c4319;border-radius:50%;background:radial-gradient(circle at 38% 30%,#ffe0a0,#b77b27 45%,#3d2b13 68%);position:relative;box-shadow:0 3px 8px #000}.knob:after{content:"";position:absolute;width:3px;height:27px;background:#1b1308;top:8px;left:38px;border-radius:2px;transform:rotate(32deg);transform-origin:50% 34px}.gauge{width:230px;height:126px;overflow:hidden;position:relative}.gauge svg{width:230px;height:230px;position:absolute;bottom:-104px}.gauge b{position:absolute;bottom:7px;left:0;right:0;text-align:center;color:var(--goldhi);font-size:24px}.svg{width:100%;height:100%}.caption{margin-top:13px;color:var(--steel);font-size:12px}.pinstripe{display:grid;grid-template-columns:minmax(240px,1.1fr) minmax(360px,1.8fr) minmax(240px,1fr);gap:16px;align-items:stretch}.odometer{font-size:clamp(27px,5vw,58px);letter-spacing:.12em;color:#17120a;background:var(--cream);border:3px solid var(--gold);padding:10px 14px;border-radius:6px;box-shadow:inset 0 -5px #b9a988}.dashboard{display:grid;grid-template-columns:1fr minmax(340px,1.45fr) 1fr;gap:18px;align-items:center}.dash-center{text-align:center}.dial{width:min(100%,310px);aspect-ratio:1;margin:auto;border:10px solid #30343a;border-radius:50%;background:radial-gradient(circle,#171611 0 52%,transparent 53%),repeating-conic-gradient(from 225deg,#d59a36 0 1deg,transparent 1deg 7deg);position:relative}.dial:after{content:"";position:absolute;left:50%;bottom:50%;width:3px;height:36%;background:var(--red);transform-origin:bottom;transform:rotate(42deg);box-shadow:0 0 8px #e2543f}.dial .dialtext{position:absolute;inset:0;display:grid;place-content:center;text-align:center;color:var(--goldhi)}.chronometer{display:grid;grid-template-columns:minmax(260px,1fr) minmax(280px,1fr);gap:22px}.chrono{border-radius:50%;aspect-ratio:1;max-width:440px;margin:auto;border:12px double var(--gold);background:repeating-radial-gradient(circle,#15110c 0 5px,#1f1910 6px 7px);position:relative;display:grid;place-content:center;text-align:center}.chrono:before{content:"";position:absolute;inset:13px;border:1px solid #c28b2c;border-radius:50%;background:repeating-conic-gradient(#d69b38 0 1deg,transparent 1deg 6deg)}.chrono>*{position:relative}.time{font-size:clamp(34px,6vw,66px);letter-spacing:.05em;color:var(--cream)}.rack{display:grid;grid-template-columns:190px 1fr 210px;gap:14px}.vu{display:flex;gap:7px;align-items:end;height:110px}.vu i{width:12px;background:linear-gradient(var(--red) 0 18%,var(--goldhi) 18% 50%,var(--green) 50%);border-radius:2px;box-shadow:0 0 6px #7fe0a066}.vu i:nth-child(1){height:28%}.vu i:nth-child(2){height:55%}.vu i:nth-child(3){height:38%}.vu i:nth-child(4){height:76%}.vu i:nth-child(5){height:92%}.vu i:nth-child(6){height:64%}.vu i:nth-child(7){height:48%}.calendar{display:grid;grid-template-columns:1fr 1.35fr 1fr;gap:16px}.apertures{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}.aperture{padding:11px 5px;text-align:center;background:#080706;border:2px solid var(--gold);border-radius:5px;color:var(--cream);font-size:20px;box-shadow:inset 0 3px 10px #000}.radio{display:grid;grid-template-columns:1.1fr 1.5fr;gap:18px}.radar{aspect-ratio:1;max-width:370px;margin:auto;border:2px solid #4b742d;border-radius:50%;background:repeating-radial-gradient(circle,#102012 0 1px,transparent 2px 58px),linear-gradient(135deg,#07100a,#122d16);position:relative;overflow:hidden}.radar:before{content:"";position:absolute;inset:50% 0 0 50%;background:conic-gradient(from 15deg,#7fe0a088,transparent 55deg);transform-origin:0 0;animation:sweep 3s linear infinite}.radar:after{content:"•  104.9\A\A    ◉ \A\A        •";white-space:pre;position:absolute;inset:30px;color:var(--green);font-size:20px;text-shadow:0 0 8px var(--green)}@keyframes sweep{to{transform:rotate(360deg)}}@media(max-width:900px){.pinstripe,.dashboard,.rack,.calendar,.chronometer,.radio{grid-template-columns:1fr}.face{min-height:auto}.gauge{margin:auto}}
</style>
</head>
<body>
<header><div class="head"><div><div class="eyebrow">Music SVG application · six functional directions</div><h1>Which instrument do you want to live with?</h1><p>Every direction drives the same player state: transport, queue selection, radio preset, playback mode, and volume all respond.</p></div><nav class="tabs" id="tabs"></nav></div></header>
<main><p class="note" id="note"></p><div id="stage"></div></main>
<script>
const state={playing:true,track:0,volume:68,preset:'Groove Salad',modes:{repeat:false,random:false,consume:false}};
const tracks=[['Groove Salad','SomaFM · 256k','◉'],['Blue in Green','Miles Davis · 5:37','♪'],['Dawn Chorus','Tycho · 4:21','♪'],['Drone Zone','SomaFM · 128k','◉'],['Night Drive','Chromatics · 4:06','♪']];
const names=['Gold-pinstripe tuner','Touring-car dashboard','Chronometer record deck','Studio rack','Perpetual-calendar salon','Radio navigator'];
const notes=['One machined faceplate: the clearest bridge to Waybar direction 1. A tape-counter queue and a brass volume knob make library work feel deliberate.','A driving instrument cluster: progress becomes speed, queue position becomes odometer, and state becomes a small set of unmistakable tell-tales.','A timepiece that treats playback as elapsed time. Best for focused listening: transport is controlled from the bezel and the queue becomes a program card.','A tactile late-70s receiver: VU meters make audio activity visible, while a rack layout gives playlist and library controls equal status.','A restrained horological receiver: album/stream art sits beside calendar-like status apertures. Best if the music panel should feel like a dressed object, not a machine.','A green-phosphor tuner/navigation station: radio discovery takes center stage, with stations as signals and playlists as routes.'];
function controls(){return `<div class="transport"><button data-action="prev">‹‹</button><button class="play" data-action="play">${state.playing?'Ⅱ':'▶'}</button><button data-action="next">››</button><button data-action="seek">↶</button><button data-action="library">☰</button></div>`}
function queue(){return `<div class="queue">${tracks.map((t,i)=>`<div class="track ${i===state.track?'current':''}" data-track="${i}"><span class="num">${String(i+1).padStart(2,'0')}</span><span>${t[2]} ${t[0]}<br><span class="meta">${t[1]}</span></span><span class="lamp ${i===state.track?'live':''}"></span></div>`).join('')}</div>`}
function modes(){return `<div class="presets"><span class="preset ${state.modes.repeat?'on':''}" data-mode="repeat">repeat</span><span class="preset ${state.modes.random?'on':''}" data-mode="random">random</span><span class="preset ${state.modes.consume?'on':''}" data-mode="consume">consume</span></div>`}
function radioPresets(){return `<div class="presets">${['Groove Salad','Drone Zone','FIP','NTS 1'].map(x=>`<span class="preset ${x===state.preset?'on':''}" data-preset="${x}">${x}</span>`).join('')}</div>`}
function now(){let t=tracks[state.track];return `<div class="label">Now playing <span class="lamp ${state.playing?'live':''}"></span></div><div class="title">${t[0]}</div><div class="sub">${t[1]} · ${state.playing?'playing':'paused'}</div><div class="progress"><i style="width:${state.playing?42:13}%"></i></div>`}
function pinstripe(){return `<section class="concept pinstripe on"><div class="face"><div class="pinstripe"><div class="well"><div class="label">Program counter</div><div class="odometer">0${state.track+1}·24</div><p class="caption">Playlist position is a physical counter. The control bank below opens load, save, edit, and delete flows.</p>${modes()}</div><div class="well">${now()}<div style="height:12px"></div>${controls()}<div style="height:16px"></div><div class="label">Live queue</div>${queue()}</div><div class="well" style="display:grid;place-items:center;align-content:center;gap:12px"><div class="label">Volume</div><div class="knob" data-action="volume"></div><b class="gold">${state.volume}%</b><div class="label">Radio presets</div>${radioPresets()}</div></div><p class="caption">Direction 1 — gold pinstripe tuner console. Use as the base language if the player should visibly belong beside the Waybar odometer console.</p></div></section>`}
function dashboard(){return `<section class="concept dashboard"><div class="face"><div class="dashboard"><div class="well"><div class="label">Source / modes</div>${radioPresets()}<div style="height:18px"></div>${modes()}<div style="height:18px"></div><div class="label">Queue</div>${queue()}</div><div class="dash-center"><div class="dial"><div class="dialtext"><div class="label">elapsed</div><div style="font-size:42px">02:18</div><div class="gold">${state.track+1} / ${tracks.length}</div></div></div><div style="height:12px"></div>${controls()}<p class="caption">Needle position means playback progress. Lamps mean real state: green live, amber engaged, red fault or missing media.</p></div><div class="well"><div class="label">Signal and volume</div><div class="gauge"><svg viewBox="0 0 230 230"><path d="M28 172 A95 95 0 0 1 202 172" fill="none" stroke="#5d451e" stroke-width="16"/><path d="M28 172 A95 95 0 0 1 164 83" fill="none" stroke="#e2a038" stroke-width="10"/><line x1="115" y1="172" x2="153" y2="95" stroke="#ffbe54" stroke-width="4"/></svg><b>${state.volume}<small>%</small></b></div><div class="label">Tell-tales</div><p><span class="lamp live"></span> playing &nbsp; <span class="lamp on"></span> repeat &nbsp; <span class="lamp"></span> queue saved</p></div></div><p class="caption">Direction 2 — touring-car dashboard. Strongest one-glance state model; it turns progress, volume, queue position, and errors into instruments rather than labels.</p></div></section>`}
function chronometer(){return `<section class="concept chronometer"><div class="face"><div class="chronometer"><div class="chrono"><div class="label">${state.playing?'running':'held'} chronometer</div><div class="time">02:18</div><div class="sub">of 05:37</div><div style="margin-top:18px">${controls()}</div></div><div class="well">${now()}<div style="height:16px"></div><div class="label">Program card</div>${queue()}<div style="height:15px"></div><div class="label">Playback modes</div>${modes()}<div style="height:15px"></div><div class="label">Station bank</div>${radioPresets()}</div></div><p class="caption">Direction 3 — chronometer record deck. Playback time is the hero; a physical bezel is a compelling seek surface in the SVG port.</p></div></section>`}
function rack(){return `<section class="concept rack"><div class="face"><div class="rack"><div class="well"><div class="label">Input / source</div>${radioPresets()}<div style="height:15px"></div><div class="label">Program modes</div>${modes()}<div style="height:20px"></div><div class="label">Master</div><div class="knob" data-action="volume"></div><p class="gold">${state.volume}%</p></div><div class="well">${now()}<div style="height:18px"></div><div class="label">Stereo program</div><div class="vu">${Array.from({length:7},()=>'<i></i>').join('')}<span style="width:18px"></span>${Array.from({length:7},()=>'<i></i>').join('')}</div><div class="screen" style="margin:12px 0;padding:8px">L  −8.1 dB&nbsp;&nbsp; R −7.8 dB&nbsp;&nbsp; ${state.playing?'SIGNAL':'HOLD'}</div>${controls()}</div><div class="well"><div class="label">Tape library / queue</div>${queue()}</div></div><p class="caption">Direction 4 — studio rack and tape machine. The VU meters are alive only while a track is playing; flashing or red meter states become a useful failure vocabulary.</p></div></section>`}
function calendar(){return `<section class="concept calendar"><div class="face"><div class="calendar"><div class="well"><div class="label">Library cabinet</div>${queue()}<div style="height:12px"></div><div class="label">File operations</div><div class="presets"><span class="preset">load</span><span class="preset">save</span><span class="preset">edit</span><span class="preset">delete</span></div></div><div class="well"><div class="label">Music perpetual calendar</div><div class="apertures"><div class="aperture">JAZZ</div><div class="aperture">SIDE A</div><div class="aperture">LIVE</div></div><div style="height:22px"></div>${now()}<div style="height:18px"></div>${controls()}<div style="height:18px"></div>${modes()}</div><div class="well"><div class="label">Station complications</div>${radioPresets()}<div style="height:18px"></div><div class="label">Volume winding</div><div class="knob" data-action="volume"></div><p class="gold">${state.volume}%</p></div></div><p class="caption">Direction 5 — perpetual-calendar dress. Gold-framed apertures make source, mode, and queue identity feel calm and ceremonial; reserve flashing for actual attention, never decoration.</p></div></section>`}
function navigator(){return `<section class="concept radio"><div class="face"><div class="radio"><div><div class="radar"></div><div class="caption" style="text-align:center">The sweep means an active radio discovery request. A fixed blip means a saved station; a pulsing blip means buffering or reconnecting.</div></div><div class="well"><div class="label">Tuner navigator</div>${now()}<div style="height:16px"></div>${controls()}<div style="height:16px"></div><div class="label">Stations</div>${radioPresets()}<div style="height:16px"></div><div class="label">Route / playlist</div>${queue()}<div style="height:16px"></div>${modes()}</div></div><p class="caption">Direction 6 — mission-control radio navigator. Best for elevating radio search, tag browsing, and saved stations to first-class interaction rather than hiding them behind a command row.</p></div></section>`}
const renders=[pinstripe,dashboard,chronometer,rack,calendar,navigator];let active=Math.max(0,Math.min(renders.length-1,Number(new URLSearchParams(location.search).get('direction'))||0));
function render(){document.querySelector('#tabs').innerHTML=names.map((n,i)=>`<button class="${i===active?'on':''}" data-tab="${i}">${i+1}. ${n}</button>`).join('');document.querySelector('#stage').innerHTML=renders[active]();document.querySelector('#stage .concept').classList.add('on');document.querySelector('#note').textContent=notes[active];bind()}
function bind(){document.querySelectorAll('[data-tab]').forEach(e=>e.onclick=()=>{active=+e.dataset.tab;render()});document.querySelectorAll('[data-track]').forEach(e=>e.onclick=()=>{state.track=+e.dataset.track;render()});document.querySelectorAll('[data-preset]').forEach(e=>e.onclick=()=>{state.preset=e.dataset.preset;state.track=0;render()});document.querySelectorAll('[data-mode]').forEach(e=>e.onclick=()=>{state.modes[e.dataset.mode]=!state.modes[e.dataset.mode];render()});document.querySelectorAll('[data-action]').forEach(e=>e.onclick=()=>{let a=e.dataset.action;if(a==='play')state.playing=!state.playing;if(a==='next')state.track=(state.track+1)%tracks.length;if(a==='prev')state.track=(state.track+tracks.length-1)%tracks.length;if(a==='volume')state.volume=state.volume>82?35:state.volume+8;render()})}render();
</script>
</body>
</html>