jukebox/index.html
Eric Villnow a796176a17 init
2025-10-03 21:29:17 -05:00

418 lines
12 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Touchscreen Jukebox</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<link rel="stylesheet" href="https://unpkg.com/kioskboard@2.3.0/dist/kioskboard-2.3.0.min.css" />
<style>
body, html {
height: 100%;
overflow: hidden; /* Prevent normal scrolling */
}
body {
transform: scale(0.8);
transform-origin: top left;
width: 125%; /* 1920 * 0.7 */
height: 125%;
}
.top-bar, .bottom-bar {
height: 120px; /* ~3x normal nav */
}
.main-body {
height: calc(100% - 240px); /* subtract top+bottom bar */
display: flex;
overflow-x: auto;
overflow-y: hidden;
scroll-snap-type: x mandatory;
}
.jukebox-card {
flex: 0 0 40%;
margin: 5rem 1rem;
scroll-snap-align: start;
display: flex;
scroll-margin-left:1rem;
flex-direction: column;
}
.jukebox-card .card-body{
flex:1;
display:flex;
flex-direction:column;
overflow:hidden;
}
.artist-card {
flex: 0 0 50%;
margin: 5rem 1rem;
scroll-snap-align: start;
background:none;
scroll-margin-left:1rem;
border:none;
}
.song-list {
flex:1;
overflow-y: auto;
}
.bottom-bar .nav {
justify-content: center;
}
.bottom-bar .nav-link {
text-align: center;
color: white;
border: 1px solid white;
border-radius:0px;
}
.bottom-bar .nav-link i {
display: block;
font-size: 1.5rem;
}
.bg-dark{
background-color:#191919 !important;
}
.card{
border-radius:0px;
}
.main-body{
background: #000dff;
background:
linear-gradient(350deg, rgba(0, 13, 255, .5) 0%, rgba(255, 0, 157, .5) 80%),
linear-gradient(to right, rgba(0,0,0,0) 40vh, rgba(0,0,0,1) 75vh),
url("<?php echo $artist['images'][0]['url']; ?>") no-repeat;
background-size: contain;
background-blend-mode: multiply, destination-in, normal; /* control blending */
}
.credits{
border: 2px solid magenta;
border-radius: 50%;
font-size: 3em;
width:80px;
text-align: center;
font-weight: light;
}
.card.bg-dark,
.modal-content.bg-dark{
background-color: rgba(25, 25, 25, 0.8) !important;
}
.card > .card-header,
.modal-content > .modal-header{
background: #191919 !important;
}
li.bg-dark{
background:none!important;
}
.fs-1{
font-size: 5em !important;
}
.btn{
border-radius:0;
background: rgba(0,0,0,.8);
}
.modal-content{
border-radius:0;
}
.modal > .btn{
background: blue !important;
}
input.form-control{
border-radius:0;
}
/* make sure the container hides overflow */
#titlebox {
overflow: hidden;
position: relative;
white-space: nowrap;
}
/* when the text needs scrolling */
#titlebox.scrolling .text {
display: inline-block; /* creates spacing for the loop */
animation: marquee 10s linear infinite;
}
/* pause at the start before moving */
@keyframes marquee {
0% { transform: translateX(0); }
10% { transform: translateX(0); } /* pause for 10% of duration */
100% { transform: translateX(-100%); }
}
</style>
</head>
<body class="bg-dark text-light">
<!-- Top Bar -->
<div class="top-bar bg-dark d-flex justify-content-between align-items-center px-4">
<!-- Now Playing -->
<div class="d-flex align-items-center m-0">
<img src="https://placehold.co/120" id="nowPlayingArt" width="120px" class="me-3" alt="Now Playing">
<div>
<div class="fw-bold text-primary">Now Playing</div>
<div class="fw-bold" id="nowPlayingTrack"></div>
<div class="text-light fw-light" id="nowPlayingArtist"></div>
</div>
</div>
<div>
<input type="text" id="search" class="form-control text-light bg-dark js-kioskboard-input" data-kioskboard-type="keyboard" data-kioskboard-placement="bottom" data-kioskboard-specialcharacters="false">
</div>
<!-- Credits & Prices -->
<div class="text-end d-flex align-items-center">
<div class="credits fw-lighter me-3" id="creditDisplay">0</div>
<div class="vr me-3"></div>
<div>
<div class="text-light">2 for $1.00</div>
<div class="text-light">13 for $5.00</div>
</div>
</div>
</div>
<!-- Main Body -->
<div class="main-body">
<!-- Example Card -->
<!-- Add more cards horizontally -->
<div class="card jukebox-card bg-dark text-light">
<div class="card-header">Playlists (12)</div>
<div class="card-body d-flex align-items-center justify-content-center">
<p>Playlist content goes here</p>
</div>
</div>
</div>
<!-- Bottom Bar -->
<div class="bottom-bar bg-dark">
<ul class="nav nav-pills h-100">
<li class="nav-item">
<a class="nav-link" href="#"><i class="fa fa-home"></i>Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#"><i class="fa fa-search"></i>Search</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#"><i class="fa fa-list"></i>Playlists</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" data-bs-toggle="modal" data-bs-target="#exampleModal"><i class="fa fa-heart"></i>Favorites</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" onclick="loadArtist()"><i class="fa fa-star"></i>Top Tracks</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#"><i class="fa fa-ellipsis-h"></i>More+</a>
</li>
</ul>
</div>
<!-- Modal -->
<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-centered">
<div class="modal-content bg-dark">
<div class="modal-body d-flex">
<div class="flex-shrink-1 m-5">
<img id="trackModalArt" src="https://placehold.co/250" width="250px"><br><br><span class="fs-small"><small>&copy; 2011 COLUMBIA / LEGACY</small></span>
</div>
<div class="flex-grow-1 m-5 text-nowrap overflow-hidden">
<div id="titlebox" class="overflow-hidden text-nowrap">
<h2 class="fw-bold text" id="trackModalTitle"></h2>
</div>
<span id="trackModalArtist"></span> ><br>
<span class="fw-bold" id="trackModalAlbum"></span>
<hr>
<div class="d-flex mt-2">
<button class="btn flex-grow-1 w-50 btn-lg btn-primary" id="trackModalNextButton" onclick="queueNext(this,3)">Play Next<br><span class="badge rounded-pill text-bg-primary">3 Credits</span></button>
<button class="btn flex-grow-1 w-50 btn-lg btn-secondary" id="trackModalQueueButton" onclick="queueSong(this,2)">Queue Song<br><span class="badge rounded-pill text-bg-secondary">2 Credits</span></button>
</div>
<div id="msg">
</div>
</div>
</div>
<div class="modal-footer d-flex flex-column align-items-center">
<button type="button" class="btn btn-secondary text-uppercase" id="trackModalArtistButton">More from this artist ></button>
</div>
</div>
</div>
</div>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://unpkg.com/kioskboard@2.3.0/dist/kioskboard-2.3.0.min.js"></script>
<script src="./jsonws.js"></script>
<script src="./credits.js"></script>
<script src="./queue.js"></script>
<script src="./nowplaying.js"></script>
<script src="./cards.js"></script>
<script src="./templates.js"></script>
<script src="./viewManager.js"></script>
<script>
ViewManager.load(Templates.test);
//init WebSocket
const addr = "kiosk";
const client = new JsonRpcWsClient({
url: ("ws://" + addr + ':6680/mopidy/ws/'),
reconnect: true,
requestTimeout: 15000
});
var artistid = '6zFYqv1mOsgBRQbae3JJ9e';
var modalEl = document.getElementById("exampleModal");
var myModal = new bootstrap.Modal(modalEl);
function loadArtist(){
client
.call("core.library.browse",{uri:"spotify:artist:" + artistid})
.then(resultz => {
ViewManager.load(Templates.artist,resultz)
})
}
function loadTrack(el){
var uri = el.dataset.uri;
client.call("core.library.lookup",{"uris":[uri]}).then(trackRes =>{
var track = trackRes[uri][0];
console.dir("track: " + JSON.stringify(track));
const trackEl = document.getElementById("trackModalTitle");
const artistEl = document.getElementById("trackModalArtist");
const albumEl = document.getElementById("trackModalAlbum");
const buttonEl = document.getElementById("trackModalArtistButton");
if (trackEl) trackEl.textContent = track.name;
if (artistEl) artistEl.textContent = track.artists[0].name;
if (albumEl) artistEl.textContent = track.album.name;
client.call("core.library.get_images",{ uris: [track.album.uri] }).then(images => {
// Pick the first (largest?) image
const artUri = images[track.album.uri][0].uri;
const artEl = document.getElementById("trackModalArt");
if (artEl) artEl.src = artUri;
})
});
myModal.show();
}
client.on('notification', (notif) => {
console.log('JSON-RPC notification:', notif);
});
client.on('response', (r) => {
console.log('response event:', r);
});
client.on('message', (msg) => {
console.log('got plain JSON:', msg);
});
client.on('raw', (txt) => {
console.log('raw text:', txt);
});
client.on('error', (e) => console.error('ws error', e));
client.on('close', (ev) => console.log('closed', ev));
client.on('reconnect_scheduled', delay => console.log('reconnect in', delay, 'ms'));
// Start:
client.connect();
// Example: call after 2s (demonstrates queueing if not ready yet)
setTimeout(() => {
client.call('core.playback.get_time_position')
.then(pos => console.log('position', pos))
.catch(err => console.error('pos err', err));
}, 2000);
</script>
<script>
document.addEventListener("DOMContentLoaded", function () {
const modal = document.getElementById("exampleModal");
const box = document.getElementById("titlebox");
const text = box.querySelector(".text");
function applyScrolling() {
box.classList.remove("scrolling");
text.style.paddingRight = "0px";
if (text.scrollWidth > box.clientWidth) {
const overflow = text.scrollWidth - box.clientWidth;
text.style.paddingRight = overflow + "px"; // dynamic gap
box.classList.add("scrolling");
}
}
// Recalculate once modal is fully visible
modal.addEventListener("shown.bs.modal", applyScrolling);
// Optional: recalc on window resize
window.addEventListener("resize", function () {
if (modal.classList.contains("show")) {
applyScrolling();
}
});
});
var usKeyboard = [
{
"0": "Q",
"1": "W",
"2": "E",
"3": "R",
"4": "T",
"5": "Y",
"6": "U",
"7": "I",
"8": "O",
"9": "P"
},
{
"0": "A",
"1": "S",
"2": "D",
"3": "F",
"4": "G",
"5": "H",
"6": "J",
"7": "K",
"8": "L"
},
{
"0": "Z",
"1": "X",
"2": "C",
"3": "V",
"4": "B",
"5": "N",
"6": "M"
}
];
KioskBoard.run('.js-kioskboard-input', {
keysArrayOfObjects:usKeyboard,
theme:"dark",
cssAnimations: true,
keysEnterText: 'Search',
keysAllowSpacebar: true,
allowRealKeyboard: true,
allowMobileKeyboard: true,
// ...init options
});
</script>
</body>
</html>