Andrea di Tuoro
Developer
Web Designer
Home
Portfolio
Code & Samples
Templates
Blog
Contatti
Instagram
@andrew_webdeveloper
Dark
Dark
Navigazione
Menu rapido
Home
Ingresso
Portfolio
Lavori
Code & Samples
Pagina attuale
Templates
Modelli
Blog
Articoli
Contatti
Scrivimi
Instagram
@andrew_webdeveloper
HTML
<div class="nocturne-player-shell"> <div class="nocturne-player is-stopped" data-experiment-player data-player-state="stopped"> <audio class="nocturne-player__audio" preload="none" playsinline crossorigin="anonymous" src="https://play.streamafrica.net/lofiradio" ></audio> <div class="nocturne-player__aurora"></div> <div class="nocturne-player__glow"></div> <div class="nocturne-player__dust"></div> <div class="nocturne-player__vignette"></div> <div class="nocturne-player__deck"> <div class="nocturne-player__header"> <div class="nocturne-player__brand"> <span class="nocturne-player__brand-dot"></span> <span class="nocturne-player__brand-station" data-player-station-name>Lo-fi Radio</span> </div> <div class="nocturne-player__status"> <span class="nocturne-player__status-led"></span> <span class="nocturne-player__status-text" data-player-status-text>Offline</span> </div> </div> <div class="nocturne-player__stations" role="list" aria-label="Stazione radio attiva"> <button type="button" class="nocturne-player__station is-active" data-station-button data-station-name="Lo-fi Radio" data-station-url="https://play.streamafrica.net/lofiradio" data-station-accent="#d8a7ff" aria-pressed="true" > <span class="nocturne-player__station-pill"></span> <span class="nocturne-player__station-text">Lo-fi</span> </button> </div> <div class="nocturne-player__platter-shadow"></div> <div class="nocturne-player__platter"> <div class="nocturne-player__platter-rim"></div> <div class="nocturne-player__record"> <div class="nocturne-player__record-rings"></div> <div class="nocturne-player__record-core"></div> <div class="nocturne-player__label"> <div class="nocturne-player__label-art"></div> </div> <div class="nocturne-player__reflection"></div> <div class="nocturne-player__specular"></div> </div> </div> <div class="nocturne-player__tonearm-base"> <div class="nocturne-player__tonearm-pivot"> <div class="nocturne-player__tonearm"> <div class="nocturne-player__tonearm-head"></div> <div class="nocturne-player__tonearm-needle"></div> </div> </div> </div> <div class="nocturne-player__meter" aria-hidden="true"> <span class="nocturne-player__bar"></span> <span class="nocturne-player__bar"></span> <span class="nocturne-player__bar"></span> <span class="nocturne-player__bar"></span> <span class="nocturne-player__bar"></span> <span class="nocturne-player__bar"></span> <span class="nocturne-player__bar"></span> <span class="nocturne-player__bar"></span> </div> <div class="nocturne-player__controls"> <div class="nocturne-player__volume-control"> <div class="nocturne-player__knob" data-volume-knob role="slider" tabindex="0" aria-label="Controllo volume" aria-valuemin="0" aria-valuemax="100" aria-valuenow="70" > <span class="nocturne-player__knob-line"></span> </div> <span class="nocturne-player__volume-readout" data-player-volume-value>70%</span> <input class="nocturne-player__volume-range" data-player-volume type="range" min="0" max="1" step="0.01" value="0.7" aria-label="Regola il volume" /> </div> <div class="nocturne-player__switch-control"> <button type="button" class="nocturne-player__switch" data-player-toggle aria-pressed="false" aria-label="Avvia la radio" > <span class="nocturne-player__switch-track"></span> <span class="nocturne-player__switch-thumb"></span> </button> <span class="nocturne-player__led"></span> <div class="nocturne-player__switch-labels"> <span class="nocturne-player__switch-label is-active" data-player-label-off>Off</span> <span class="nocturne-player__switch-label" data-player-label-on>On</span> </div> </div> </div> </div> </div> </div>
CSS
.nocturne-player-shell { --bg: #08070d; --bg-2: #120d1b; --panel: rgba(19, 15, 29, 0.94); --panel-soft: rgba(34, 24, 47, 0.92); --metal: #e9e3f4; --metal-dark: #7f738f; --accent: #d8a7ff; --accent-2: #72f6ff; --accent-3: #d8a7ff; --accent-rgb: 216, 167, 255; --text: #f7f2ff; --muted: rgba(247, 242, 255, 0.62); --line: rgba(255, 255, 255, 0.09); width: min(100%, 46rem); margin-inline: auto; } .nocturne-player { position: relative; width: 100%; aspect-ratio: 1 / 1; min-height: 22rem; overflow: hidden; border-radius: 2rem; isolation: isolate; background: radial-gradient(circle at 20% 15%, rgba(114, 246, 255, 0.12), transparent 22%), radial-gradient(circle at 84% 20%, rgba(216, 167, 255, 0.12), transparent 24%), radial-gradient(circle at 50% 100%, rgba(var(--accent-rgb), 0.12), transparent 40%), linear-gradient(180deg, #120d1d 0%, #08070d 100%); border: 1px solid rgba(255, 255, 255, 0.08); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06), 0 30px 100px -42px rgba(0, 0, 0, 0.88); animation: playerIntro 1200ms cubic-bezier(0.16, 1, 0.3, 1) both; } .nocturne-player *, .nocturne-player *::before, .nocturne-player *::after, .nocturne-player-shell *, .nocturne-player-shell *::before, .nocturne-player-shell *::after { box-sizing: border-box; } .nocturne-player__audio, .nocturne-player__aurora, .nocturne-player__glow, .nocturne-player__dust, .nocturne-player__vignette, .nocturne-player__deck, .nocturne-player__platter-shadow, .nocturne-player__platter, .nocturne-player__platter-rim, .nocturne-player__record, .nocturne-player__record-rings, .nocturne-player__record-core, .nocturne-player__label, .nocturne-player__reflection, .nocturne-player__specular, .nocturne-player__tonearm-base, .nocturne-player__tonearm-pivot, .nocturne-player__tonearm, .nocturne-player__tonearm-head, .nocturne-player__tonearm-needle, .nocturne-player__meter { position: absolute; } .nocturne-player__aurora, .nocturne-player__glow, .nocturne-player__dust, .nocturne-player__vignette { inset: 0; pointer-events: none; } .nocturne-player__aurora { background: radial-gradient(circle at 22% 26%, rgba(114, 246, 255, 0.24), transparent 24%), radial-gradient(circle at 80% 24%, rgba(216, 167, 255, 0.22), transparent 26%), radial-gradient(circle at 58% 82%, rgba(var(--accent-rgb), 0.2), transparent 30%); filter: blur(18px); opacity: 0.92; animation: auroraMove 14s ease-in-out infinite alternate; } .nocturne-player__glow { background: radial-gradient(circle at 32% 34%, rgba(var(--accent-rgb), 0.12), transparent 20%), radial-gradient(circle at 67% 66%, rgba(114, 246, 255, 0.08), transparent 20%); mix-blend-mode: screen; } .nocturne-player__dust { opacity: 0.65; background-image: radial-gradient(circle at 14% 20%, rgba(255, 255, 255, 0.5) 0 1px, transparent 1.8px), radial-gradient(circle at 70% 10%, rgba(255, 255, 255, 0.28) 0 1px, transparent 1.8px), radial-gradient(circle at 86% 74%, rgba(var(--accent-rgb), 0.34) 0 1px, transparent 1.8px), radial-gradient(circle at 26% 84%, rgba(114, 246, 255, 0.26) 0 1px, transparent 1.8px), radial-gradient(circle at 52% 58%, rgba(216, 167, 255, 0.18) 0 1px, transparent 1.8px); animation: dustFloat 18s linear infinite; } .nocturne-player__vignette { background: radial-gradient(circle, transparent 48%, rgba(0, 0, 0, 0.28) 100%); } .nocturne-player__deck { inset: 10%; border-radius: 2rem; background: linear-gradient(145deg, rgba(39, 28, 55, 0.96), rgba(14, 10, 21, 0.98)), linear-gradient(180deg, rgba(255, 255, 255, 0.05), transparent 30%); border: 1px solid rgba(255, 255, 255, 0.08); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08), inset 0 -18px 34px rgba(0, 0, 0, 0.36), 0 22px 60px rgba(0, 0, 0, 0.34); } .nocturne-player__header { position: absolute; left: 1.4rem; right: 1.4rem; top: 1.25rem; z-index: 5; display: flex; align-items: flex-start; justify-content: space-between; gap: 0.75rem; flex-wrap: wrap; } .nocturne-player__brand, .nocturne-player__status { display: inline-flex; align-items: center; gap: 0.5rem; min-width: 0; } .nocturne-player__brand { min-width: 0; max-width: 100%; flex: 1 1 12rem; } .nocturne-player__status { flex: 0 0 auto; justify-content: flex-end; } .nocturne-player__brand-dot, .nocturne-player__status-led { width: 0.55rem; aspect-ratio: 1; border-radius: 50%; flex: 0 0 auto; } .nocturne-player__brand-dot { background: linear-gradient(135deg, var(--accent-2), var(--accent-3)); box-shadow: 0 0 14px rgba(114, 246, 255, 0.3); } .nocturne-player__brand-text, .nocturne-player__status-text, .nocturne-player__brand-station { font-size: 0.72rem; line-height: 1; letter-spacing: 0.18em; text-transform: uppercase; } .nocturne-player__brand-text { color: rgba(247, 242, 255, 0.86); white-space: nowrap; } .nocturne-player__brand-separator { width: 0.22rem; height: 0.22rem; border-radius: 50%; background: rgba(255, 255, 255, 0.3); flex: 0 0 auto; } .nocturne-player__brand-station { color: rgba(var(--accent-rgb), 0.96); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; } .nocturne-player__status-text { color: var(--muted); } .nocturne-player__status-led { background: rgba(255, 255, 255, 0.18); box-shadow: 0 0 0 rgba(255, 255, 255, 0); transition: background 260ms ease, box-shadow 260ms ease, opacity 260ms ease; } .nocturne-player__stations { position: absolute; left: 1.35rem; right: 1.35rem; top: 3.5rem; z-index: 5; display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; } .nocturne-player__station { position: relative; appearance: none; border: 1px solid rgba(255, 255, 255, 0.08); background: linear-gradient(180deg, rgba(255, 255, 255, 0.09), rgba(255, 255, 255, 0.03)); color: rgba(247, 242, 255, 0.82); border-radius: 999px; min-height: 2rem; padding: 0.4rem 0.85rem 0.4rem 0.55rem; display: inline-flex; align-items: center; gap: 0.5rem; cursor: default; white-space: nowrap; max-width: 100%; min-width: 0; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08), 0 8px 20px rgba(0, 0, 0, 0.16); } .nocturne-player__station.is-active { border-color: rgba(var(--accent-rgb), 0.42); background: linear-gradient(180deg, rgba(var(--accent-rgb), 0.16), rgba(255, 255, 255, 0.04)); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08), 0 0 0 1px rgba(var(--accent-rgb), 0.16), 0 12px 24px rgba(0, 0, 0, 0.2); } .nocturne-player__station-pill { width: 0.55rem; aspect-ratio: 1; border-radius: 50%; flex: 0 0 auto; background: linear-gradient(135deg, rgba(var(--accent-rgb), 1), rgba(255, 255, 255, 0.7)); box-shadow: 0 0 10px rgba(var(--accent-rgb), 0.35); } .nocturne-player__station-text { font-size: 0.68rem; line-height: 1; letter-spacing: 0.12em; text-transform: uppercase; overflow: hidden; text-overflow: ellipsis; } .nocturne-player__platter-shadow { left: 16.5%; top: 20%; width: 60%; aspect-ratio: 1; border-radius: 50%; background: radial-gradient(circle, rgba(0, 0, 0, 0.48), transparent 68%); filter: blur(12px); } .nocturne-player__platter { left: 18%; top: 21.5%; width: 58%; aspect-ratio: 1; border-radius: 50%; background: linear-gradient(145deg, rgba(117, 103, 141, 0.45), rgba(14, 11, 19, 0.88)), radial-gradient(circle at 50% 40%, rgba(255, 255, 255, 0.12), transparent 62%); border: 1px solid rgba(255, 255, 255, 0.09); box-shadow: inset 0 2px 8px rgba(255, 255, 255, 0.08), inset 0 -12px 18px rgba(0, 0, 0, 0.4); } .nocturne-player__platter-rim { inset: 3.5%; border-radius: 50%; border: 1px solid rgba(255, 255, 255, 0.08); box-shadow: inset 0 0 0 0.2rem rgba(255, 255, 255, 0.02), inset 0 0 1.6rem rgba(255, 255, 255, 0.04); } .nocturne-player__record { inset: 7%; border-radius: 50%; overflow: hidden; background: radial-gradient(circle at 42% 34%, rgba(114, 246, 255, 0.08), transparent 24%), radial-gradient(circle at 58% 68%, rgba(216, 167, 255, 0.08), transparent 20%), radial-gradient(circle at 50% 50%, rgba(34, 28, 41, 0.98), rgba(7, 7, 9, 1) 72%); box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.04), 0 14px 24px rgba(0, 0, 0, 0.26); will-change: transform; } .nocturne-player__record::before { content: ""; position: absolute; inset: 1.8%; border-radius: 50%; border: 1px solid rgba(255, 255, 255, 0.035); } .nocturne-player__record-rings { inset: 0; border-radius: 50%; background: repeating-radial-gradient( circle, rgba(255, 255, 255, 0.042) 0 1px, rgba(255, 255, 255, 0) 1px 10px ); opacity: 0.84; } .nocturne-player__record-core { left: 50%; top: 50%; width: 4%; aspect-ratio: 1; transform: translate(-50%, -50%); border-radius: 50%; background: #d9d3e3; box-shadow: 0 0 0 0.22rem rgba(13, 10, 20, 0.95), 0 0 0 0.34rem rgba(255, 255, 255, 0.045); } .nocturne-player__label { left: 50%; top: 50%; width: 27%; aspect-ratio: 1; transform: translate(-50%, -50%); border-radius: 50%; background: radial-gradient(circle at 34% 32%, rgba(255, 255, 255, 0.18), transparent 24%), conic-gradient( from 30deg, rgba(var(--accent-rgb), 0.92), rgba(216, 167, 255, 0.9), rgba(114, 246, 255, 0.92), rgba(var(--accent-rgb), 0.92) ); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14), 0 0 0 0.34rem rgba(15, 11, 23, 0.88), 0 0 24px rgba(var(--accent-rgb), 0.14); overflow: hidden; } .nocturne-player__label-art { position: absolute; inset: 18%; border-radius: 50%; background: radial-gradient(circle at 50% 50%, rgba(18, 14, 27, 0.92), rgba(8, 7, 13, 1) 72%), linear-gradient(180deg, rgba(255,255,255,0.12), transparent); } .nocturne-player__label::after { content: ""; position: absolute; left: 50%; top: 50%; width: 18%; aspect-ratio: 1; transform: translate(-50%, -50%); border-radius: 50%; background: #efe9f6; box-shadow: 0 0 0 0.08rem rgba(0, 0, 0, 0.18); } .nocturne-player__reflection { inset: 7%; border-radius: 50%; background: conic-gradient( from 0deg, transparent 0 30deg, rgba(255, 255, 255, 0.18) 30deg 54deg, transparent 54deg 170deg, rgba(var(--accent-rgb), 0.08) 170deg 215deg, transparent 215deg 360deg ); mix-blend-mode: screen; } .nocturne-player__specular { inset: 11%; border-radius: 50%; background: radial-gradient(circle at 34% 28%, rgba(255,255,255,0.18), transparent 0 12%), radial-gradient(circle at 68% 76%, rgba(255,255,255,0.05), transparent 0 14%); mix-blend-mode: screen; pointer-events: none; } .nocturne-player__tonearm-base { right: 8.5%; top: 22%; width: 31%; height: 40%; } .nocturne-player__tonearm-base::before { content: ""; position: absolute; right: 3%; top: 9%; width: 22%; aspect-ratio: 1; border-radius: 50%; background: linear-gradient(145deg, #7f7490, #241b2f); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14), 0 8px 18px rgba(0, 0, 0, 0.24); } .nocturne-player__tonearm-base::after { content: ""; position: absolute; right: 8%; top: 14%; width: 10%; aspect-ratio: 1; border-radius: 50%; background: linear-gradient(145deg, #cfc7d8, #5e5569); } .nocturne-player__tonearm-pivot { right: 12%; top: 12%; width: 92%; height: 76%; transform-origin: 92% 10%; transform: rotate(-35deg); transition: transform 1050ms cubic-bezier(0.65, 0, 0.35, 1); } .nocturne-player__tonearm { right: 0; top: 0; width: 100%; height: 0.56rem; border-radius: 999px; background: linear-gradient(90deg, rgba(235, 230, 241, 0.98), rgba(141, 131, 156, 0.94)); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.3), 0 4px 10px rgba(0, 0, 0, 0.18); } .nocturne-player__tonearm-head { right: -0.5%; top: 50%; width: 15%; height: 1.2rem; transform: translateY(-50%); border-radius: 0.38rem; background: linear-gradient(180deg, #efe9f5, #83778f); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.24); } .nocturne-player__tonearm-needle { right: 4%; top: calc(50% + 0.46rem); width: 0.09rem; height: 1.7rem; background: linear-gradient(180deg, rgba(235, 231, 240, 0.94), rgba(161, 152, 174, 0.34)); transform-origin: top center; transform: rotate(10deg) scaleY(0.82); opacity: 0.45; transition: transform 1050ms cubic-bezier(0.65, 0, 0.35, 1), opacity 1050ms cubic-bezier(0.65, 0, 0.35, 1); } .nocturne-player__meter { right: 6%; bottom: 10%; width: 24%; height: 14%; display: flex; align-items: end; justify-content: space-between; gap: 0.28rem; padding: 0.95rem; border-radius: 1.15rem; background: linear-gradient(180deg, rgba(11, 9, 17, 0.94), rgba(5, 4, 9, 0.98)); border: 1px solid rgba(255, 255, 255, 0.07); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 12px 24px rgba(0, 0, 0, 0.18); } .nocturne-player__bar { position: relative; flex: 1 1 auto; align-self: end; min-width: 0.24rem; height: 12%; border-radius: 999px 999px 0 0; background: linear-gradient(180deg, rgba(255,255,255,0.94), rgba(var(--accent-rgb), 1) 52%, rgba(216,167,255,0.98)); box-shadow: 0 0 14px rgba(var(--accent-rgb), 0.22), 0 0 24px rgba(114, 246, 255, 0.08); transform-origin: bottom center; opacity: 0.24; transition: height 90ms linear, opacity 90ms linear, filter 90ms linear; } .nocturne-player__controls { position: absolute; left: 8%; right: 35%; bottom: 7.5%; z-index: 5; display: flex; align-items: flex-end; justify-content: space-between; gap: 1.4rem; min-width: 0; } .nocturne-player__volume-control { display: flex; align-items: center; gap: 0.85rem; min-width: 0; flex: 1 1 auto; } .nocturne-player__knob { position: relative; width: 2.75rem; aspect-ratio: 1; flex: 0 0 auto; transform: rotate(52deg); border-radius: 50%; background: radial-gradient(circle at 35% 30%, rgba(255,255,255,0.18), transparent 28%), linear-gradient(145deg, #7b7288, #281f32); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.16), 0 8px 16px rgba(0, 0, 0, 0.24); transition: transform 120ms ease, box-shadow 180ms ease; cursor: grab; touch-action: none; user-select: none; } .nocturne-player__knob:hover, .nocturne-player__knob:focus-visible { box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 10px 20px rgba(0, 0, 0, 0.26), 0 0 0 0.18rem rgba(var(--accent-rgb), 0.18); outline: none; } .nocturne-player__knob.is-dragging { cursor: grabbing; transition: none; } .nocturne-player__knob-line { position: absolute; left: 50%; top: 12%; width: 0.12rem; height: 0.82rem; transform: translateX(-50%); border-radius: 999px; background: rgba(255, 255, 255, 0.82); } .nocturne-player__volume-readout { min-width: 0; font-size: 0.72rem; line-height: 1; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase; color: rgba(var(--accent-rgb), 0.96); white-space: nowrap; } .nocturne-player__volume-range { display: none; } .nocturne-player__switch-control { display: flex; flex-direction: column; align-items: center; gap: 0.55rem; flex: 0 0 auto; min-width: 0; } .nocturne-player__switch-row { display: flex; align-items: center; gap: 0.7rem; } .nocturne-player__switch { position: relative; width: 4.2rem; height: 1.14rem; appearance: none; border: none; background: transparent; cursor: pointer; flex: 0 0 auto; } .nocturne-player__switch-track { position: absolute; inset: 0; border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 999px; background: linear-gradient(180deg, rgba(255, 255, 255, 0.12), rgba(255,255,255,0.03)); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05); } .nocturne-player__switch-thumb { position: absolute; left: 6%; top: 50%; width: 1.14rem; aspect-ratio: 1; transform: translateY(-50%) translateX(0); border-radius: 50%; background: radial-gradient(circle at 30% 30%, rgba(255,255,255,0.28), transparent 30%), linear-gradient(145deg, #efe9f4, #887d96); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.24), inset 0 1px 0 rgba(255,255,255,0.28); transition: transform 520ms cubic-bezier(0.65, 0, 0.35, 1), box-shadow 520ms ease; } .nocturne-player__led { width: 0.68rem; aspect-ratio: 1; border-radius: 50%; background: rgba(var(--accent-rgb), 0.72); opacity: 0.34; box-shadow: 0 0 8px rgba(var(--accent-rgb), 0.2); transition: opacity 520ms ease, box-shadow 520ms ease, background 520ms ease; flex: 0 0 auto; } .nocturne-player__switch-labels { width: 4.2rem; display: flex; align-items: center; justify-content: space-between; } .nocturne-player__switch-label { font-size: 0.54rem; line-height: 1; font-weight: 700; letter-spacing: 0.18em; text-transform: uppercase; color: rgba(255, 255, 255, 0.34); transition: color 260ms ease; } .nocturne-player__switch-label.is-active { color: rgba(var(--accent-rgb), 0.96); } .nocturne-player.is-playing .nocturne-player__tonearm-pivot { transform: rotate(-18deg); } .nocturne-player.is-playing .nocturne-player__tonearm-needle { transform: rotate(1deg) scaleY(1); opacity: 1; } .nocturne-player.is-playing .nocturne-player__switch-thumb { transform: translateY(-50%) translateX(2.02rem); } .nocturne-player.is-playing .nocturne-player__led, .nocturne-player.is-playing .nocturne-player__status-led { opacity: 1; background: rgba(var(--accent-rgb), 1); box-shadow: 0 0 14px rgba(var(--accent-rgb), 0.84), 0 0 28px rgba(114, 246, 255, 0.16); } .nocturne-player.is-playing .nocturne-player__record { animation: nocturneSpin 5.4s linear infinite; } .nocturne-player.is-playing .nocturne-player__reflection { animation: nocturneSpinReverse 8.8s linear infinite; } .nocturne-player.is-stopped .nocturne-player__bar { height: 12%; opacity: 0.2; filter: saturate(0.9); } .nocturne-player.is-playing [data-player-label-on] { color: rgba(var(--accent-rgb), 0.98); } .nocturne-player.is-playing [data-player-label-off] { color: rgba(255, 255, 255, 0.34); } .nocturne-player.is-stopped [data-player-label-on] { color: rgba(255, 255, 255, 0.34); } .nocturne-player.is-stopped [data-player-label-off] { color: rgba(var(--accent-rgb), 0.98); } @media (max-width: 680px) { .nocturne-player { border-radius: 1.5rem; } .nocturne-player__deck { inset: 5.5%; border-radius: 1.55rem; } .nocturne-player__header { left: 1rem; right: 1rem; top: 1rem; gap: 0.5rem 0.8rem; } .nocturne-player__stations { left: 1rem; right: 1rem; top: 4.1rem; gap: 0.45rem; } .nocturne-player__station { padding-inline: 0.75rem; } .nocturne-player__platter-shadow { left: 20%; top: 24%; width: 56%; } .nocturne-player__platter { left: 21.5%; top: 25%; width: 54%; } .nocturne-player__tonearm-base { right: 6%; top: 23.5%; width: 29%; height: 34%; } .nocturne-player__meter { right: 5%; bottom: 9%; width: 21%; height: 12%; padding: 0.62rem; border-radius: 1rem; gap: 0.18rem; } .nocturne-player__controls { left: 10%; right: 31%; bottom: 7%; gap: 0.9rem; align-items: center; } .nocturne-player__knob { width: 2.4rem; } .nocturne-player__volume-readout { font-size: 0.64rem; letter-spacing: 0.12em; } .nocturne-player__switch-control { align-items: flex-start; } .nocturne-player__switch { width: 3.7rem; } .nocturne-player__switch-labels { width: 3.7rem; } .nocturne-player.is-playing .nocturne-player__switch-thumb { transform: translateY(-50%) translateX(1.52rem); } } @media (max-width: 520px) { .nocturne-player { min-height: 26rem; aspect-ratio: auto; } .nocturne-player__brand-text, .nocturne-player__status-text, .nocturne-player__brand-station { font-size: 0.6rem; letter-spacing: 0.14em; } .nocturne-player__header { align-items: flex-start; } .nocturne-player__brand { flex: 1 1 100%; max-width: 100%; } .nocturne-player__status { flex: 1 1 100%; justify-content: flex-start; } .nocturne-player__stations { top: 4.4rem; } .nocturne-player__station { min-height: 1.85rem; padding: 0.42rem 0.72rem 0.42rem 0.52rem; max-width: calc(100% - 0.25rem); } .nocturne-player__station-text { font-size: 0.6rem; letter-spacing: 0.1em; } .nocturne-player__platter-shadow { left: 21.5%; top: 29%; width: 54%; } .nocturne-player__platter { left: 23%; top: 30%; width: 52%; } .nocturne-player__tonearm-base { right: 6%; top: 30%; width: 26%; height: 25%; } .nocturne-player__tonearm-pivot { right: 8%; top: 10%; width: 88%; height: 74%; } .nocturne-player__tonearm { height: 0.46rem; } .nocturne-player__tonearm-head { height: 0.95rem; } .nocturne-player__tonearm-needle { top: calc(50% + 0.36rem); height: 1.3rem; } .nocturne-player__meter { right: 5%; bottom: 7%; width: 18%; height: 8.8%; padding: 0.38rem; border-radius: 0.9rem; } .nocturne-player__bar { min-width: 0.14rem; } .nocturne-player__controls { left: 10%; right: 28%; bottom: 6.2%; flex-direction: column; align-items: flex-start; justify-content: flex-end; gap: 0.75rem; } .nocturne-player__volume-control { gap: 0.58rem; } .nocturne-player__knob { width: 2.15rem; } .nocturne-player__volume-readout { font-size: 0.58rem; min-width: 0; } .nocturne-player__switch-control { align-items: flex-start; gap: 0.42rem; } .nocturne-player__switch-row { gap: 0.52rem; } .nocturne-player__switch-label { font-size: 0.45rem; letter-spacing: 0.16em; } .nocturne-player__switch { width: 3.2rem; } .nocturne-player__switch-labels { width: 3.2rem; } .nocturne-player.is-playing .nocturne-player__switch-thumb { transform: translateY(-50%) translateX(1.14rem); } } @media (max-width: 420px) { .nocturne-player { min-height: 27rem; } .nocturne-player__header { left: 0.85rem; right: 0.85rem; top: 0.9rem; } .nocturne-player__stations { left: 0.85rem; right: 0.85rem; top: 4.2rem; gap: 0.4rem; } .nocturne-player__station { flex: 1 1 calc(50% - 0.2rem); justify-content: center; } .nocturne-player__platter-shadow { left: 23%; top: 31%; width: 52%; } .nocturne-player__platter { left: 24.5%; top: 32%; width: 50%; } .nocturne-player__tonearm-base { right: 7%; top: 31.5%; width: 23%; height: 22%; } .nocturne-player__meter { right: 5%; bottom: 5.8%; width: 16%; height: 7.4%; padding: 0.28rem; } .nocturne-player__controls { left: 10%; right: 26%; bottom: 5.3%; gap: 0.65rem; } .nocturne-player__knob { width: 2rem; } .nocturne-player__volume-readout { font-size: 0.54rem; } .nocturne-player__switch { width: 2.95rem; } .nocturne-player__switch-labels { width: 2.95rem; } .nocturne-player.is-playing .nocturne-player__switch-thumb { transform: translateY(-50%) translateX(0.98rem); } } @media (max-width: 360px) { .nocturne-player__station { flex: 1 1 100%; } .nocturne-player__platter-shadow { left: 24.5%; width: 50%; } .nocturne-player__platter { left: 26%; width: 48%; } .nocturne-player__controls { right: 25%; } .nocturne-player__meter { width: 15%; right: 5%; } } @media (prefers-reduced-motion: reduce) { .nocturne-player, .nocturne-player * { animation: none !important; transition-duration: 0.01ms !important; } } @keyframes playerIntro { 0% { opacity: 0; transform: scale(0.975); filter: brightness(0.84); } 60% { opacity: 1; transform: scale(1); filter: brightness(1.04); } 100% { opacity: 1; transform: scale(1); filter: brightness(1); } } @keyframes dustFloat { 0% { transform: translateY(0) translateX(0); } 50% { transform: translateY(3%) translateX(1%); } 100% { transform: translateY(6%) translateX(-1%); } } @keyframes auroraMove { 0% { transform: translate3d(-2%, -1%, 0) scale(1); } 50% { transform: translate3d(2%, 1%, 0) scale(1.04); } 100% { transform: translate3d(-1%, 2%, 0) scale(1.02); } } @keyframes nocturneSpin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } @keyframes nocturneSpinReverse { from { transform: rotate(0deg); } to { transform: rotate(-360deg); } }
JS
const player = document.querySelector("[data-experiment-player]"); const shell = document.querySelector(".nocturne-player-shell"); if (player && shell) { const audio = player.querySelector(".nocturne-player__audio"); const toggle = player.querySelector("[data-player-toggle]"); const labelOn = player.querySelector("[data-player-label-on]"); const labelOff = player.querySelector("[data-player-label-off]"); const bars = [...player.querySelectorAll(".nocturne-player__bar")]; const stationNameText = player.querySelector("[data-player-station-name]"); const statusText = player.querySelector("[data-player-status-text]"); const volumeSlider = player.querySelector("[data-player-volume]"); const volumeValue = player.querySelector("[data-player-volume-value]"); const volumeKnob = player.querySelector("[data-volume-knob]"); let audioContext = null; let analyser = null; let sourceNode = null; let frequencyData = null; let visualSeed = Array.from({ length: bars.length }, () => Math.random() * 0.22); let isDraggingKnob = false; const currentStation = { name: "Lo-fi Radio", url: "https://play.streamafrica.net/lofiradio", accent: "#d8a7ff" }; const clamp = (value, min, max) => Math.min(max, Math.max(min, value)); const setAccent = (hex) => { if (!hex) return; const value = hex.replace("#", ""); let r = 216; let g = 167; let b = 255; if (value.length === 3) { r = parseInt(value[0] + value[0], 16); g = parseInt(value[1] + value[1], 16); b = parseInt(value[2] + value[2], 16); } else if (value.length === 6) { r = parseInt(value.slice(0, 2), 16); g = parseInt(value.slice(2, 4), 16); b = parseInt(value.slice(4, 6), 16); } shell.style.setProperty("--accent", hex); shell.style.setProperty("--accent-rgb", `${r}, ${g}, ${b}`); }; const updateVolumeUI = (value) => { const normalized = clamp(Number(value) || 0, 0, 1); const percent = Math.round(normalized * 100); if (audio) { audio.volume = normalized; } if (volumeValue) { volumeValue.textContent = `${percent}%`; } if (volumeSlider) { volumeSlider.value = normalized; } if (volumeKnob) { const rotation = -130 + normalized * 260; volumeKnob.style.transform = `rotate(${rotation}deg)`; volumeKnob.setAttribute("aria-valuenow", String(percent)); } }; const volumeFromKnobPointer = (clientX, clientY) => { if (!volumeKnob) return 0.7; const rect = volumeKnob.getBoundingClientRect(); const centerX = rect.left + rect.width / 2; const centerY = rect.top + rect.height / 2; const dx = clientX - centerX; const dy = clientY - centerY; let degrees = (Math.atan2(dy, dx) * 180) / Math.PI; degrees += 90; if (degrees > 180) { degrees -= 360; } const clampedDegrees = clamp(degrees, -130, 130); return (clampedDegrees + 130) / 260; }; const syncState = (playing) => { player.classList.toggle("is-playing", playing); player.classList.toggle("is-stopped", !playing); player.dataset.playerState = playing ? "playing" : "stopped"; if (toggle) { toggle.setAttribute("aria-pressed", playing ? "true" : "false"); toggle.setAttribute( "aria-label", playing ? `Ferma ${currentStation.name}` : `Avvia ${currentStation.name}` ); } labelOn?.classList.toggle("is-active", playing); labelOff?.classList.toggle("is-active", !playing); if (statusText) { statusText.textContent = playing ? "Live" : "Offline"; } }; const createAudioGraph = async () => { if (!audio || analyser) return; const AudioContextClass = window.AudioContext || window.webkitAudioContext; if (!AudioContextClass) return; try { audioContext = new AudioContextClass(); sourceNode = audioContext.createMediaElementSource(audio); analyser = audioContext.createAnalyser(); analyser.fftSize = 128; analyser.smoothingTimeConstant = 0.84; frequencyData = new Uint8Array(analyser.frequencyBinCount); sourceNode.connect(analyser); analyser.connect(audioContext.destination); } catch (error) { analyser = null; frequencyData = null; } }; const updateBars = () => { if (!bars.length) return; if (player.dataset.playerState !== "playing") { bars.forEach((bar, index) => { const idle = 12 + Math.sin(performance.now() * 0.002 + index * 0.75) * 2; bar.style.height = `${Math.max(10, idle)}%`; bar.style.opacity = "0.2"; }); return; } if (analyser && frequencyData) { analyser.getByteFrequencyData(frequencyData); const bucketSize = Math.max(1, Math.floor(frequencyData.length / bars.length)); bars.forEach((bar, index) => { const start = index * bucketSize; const end = Math.min(frequencyData.length, start + bucketSize); let sum = 0; for (let i = start; i < end; i += 1) { sum += frequencyData[i]; } const avg = end > start ? sum / (end - start) : 0; const normalized = avg / 255; const eased = 0.18 + normalized * 0.82; const height = 14 + eased * 72; const opacity = 0.34 + eased * 0.66; visualSeed[index] += (eased - visualSeed[index]) * 0.22; bar.style.height = `${height}%`; bar.style.opacity = `${opacity}`; bar.style.filter = `saturate(${1 + visualSeed[index] * 0.7}) brightness(${1 + visualSeed[index] * 0.28})`; }); return; } bars.forEach((bar, index) => { const t = performance.now() * 0.0024; const wave = 0.4 + Math.sin(t * 1.25 + index * 0.7) * 0.18 + Math.sin(t * 2.1 + index * 1.15) * 0.16 + Math.sin(t * 3.4 + index * 0.45) * 0.08; const clampedWave = Math.max(0.08, Math.min(1, wave)); bar.style.height = `${14 + clampedWave * 70}%`; bar.style.opacity = `${0.3 + clampedWave * 0.7}`; bar.style.filter = `brightness(${1 + clampedWave * 0.2})`; }); }; const tick = () => { updateBars(); requestAnimationFrame(tick); }; const stopPlayback = () => { audio.pause(); syncState(false); }; const startPlayback = async () => { const streamUrl = currentStation.url; if (!streamUrl) { syncState(false); return; } if (audio.src !== streamUrl) { audio.src = streamUrl; audio.load(); } try { await createAudioGraph(); if (audioContext?.state === "suspended") { await audioContext.resume(); } await audio.play(); syncState(true); } catch (error) { syncState(false); } }; const togglePlayback = async () => { const isPlaying = player.dataset.playerState === "playing"; if (isPlaying) { stopPlayback(); return; } await startPlayback(); }; audio.addEventListener("playing", () => { syncState(true); }); audio.addEventListener("pause", () => { if (!audio.ended && player.dataset.playerState === "playing") { syncState(false); } }); audio.addEventListener("error", () => { syncState(false); }); toggle?.addEventListener("click", togglePlayback); volumeSlider?.addEventListener("input", (event) => { updateVolumeUI(event.target.value); }); if (volumeKnob) { const onPointerMove = (event) => { if (!isDraggingKnob) return; const value = volumeFromKnobPointer(event.clientX, event.clientY); updateVolumeUI(value); }; const stopDrag = () => { isDraggingKnob = false; volumeKnob.classList.remove("is-dragging"); window.removeEventListener("pointermove", onPointerMove); window.removeEventListener("pointerup", stopDrag); }; volumeKnob.addEventListener("pointerdown", (event) => { event.preventDefault(); isDraggingKnob = true; volumeKnob.classList.add("is-dragging"); updateVolumeUI(volumeFromKnobPointer(event.clientX, event.clientY)); window.addEventListener("pointermove", onPointerMove); window.addEventListener("pointerup", stopDrag); }); volumeKnob.addEventListener( "wheel", (event) => { event.preventDefault(); const current = Number(audio?.volume ?? 0.7); const next = current + (event.deltaY < 0 ? 0.03 : -0.03); updateVolumeUI(next); }, { passive: false } ); volumeKnob.addEventListener("keydown", (event) => { const current = Number(audio?.volume ?? 0.7); if (event.key === "ArrowRight" || event.key === "ArrowUp") { event.preventDefault(); updateVolumeUI(current + 0.05); } if (event.key === "ArrowLeft" || event.key === "ArrowDown") { event.preventDefault(); updateVolumeUI(current - 0.05); } if (event.key === "Home") { event.preventDefault(); updateVolumeUI(0); } if (event.key === "End") { event.preventDefault(); updateVolumeUI(1); } }); } setAccent(currentStation.accent); if (stationNameText) { stationNameText.textContent = currentStation.name; } syncState(false); if (audio) { audio.volume = 0.7; audio.src = currentStation.url; } updateVolumeUI(0.7); requestAnimationFrame(tick); }
Lo-Fi Radio