SpotifyRoulette/src/main/resources/public/game.html

292 lines
14 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Spotify Roulette Spiel</title>
<!-- Font -->
<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=Inter:wght@400;600;700&display=swap" rel="stylesheet">
<style>
:root{
--bg:#121212; --elev:#181818; --border:#282828; --text:#fff; --muted:#b3b3b3;
--accent:#1db954; --accent-press:#169e47; --glow:rgba(29,185,84,.25);
--radius:16px; --shadow:0 10px 30px rgba(0,0,0,.35);
}
*{box-sizing:border-box}
html,body{height:100%}
body{
margin:0; font-family:Inter,system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial;
color:var(--text);
background:
radial-gradient(1200px 600px at 20% -10%, rgba(29,185,84,.15), transparent 60%),
radial-gradient(1000px 500px at 100% -20%, rgba(29,185,84,.12), transparent 70%),
var(--bg);
}
.container{max-width:1100px; margin:0 auto; padding:24px}
/* Header */
header{
display:flex; align-items:center; justify-content:space-between; gap:16px;
padding-bottom:12px; border-bottom:1px solid var(--border);
}
.brand{display:flex; align-items:center; gap:12px}
.logo{width:36px; height:36px; border-radius:50%; background:var(--accent); color:#121212;
display:grid; place-content:center; box-shadow:0 0 0 6px var(--glow)}
h1{margin:0; font-size:20px; letter-spacing:.2px}
.pill{display:inline-flex; align-items:center; gap:8px; padding:6px 10px; border-radius:999px;
border:1px solid var(--border); background:#101010; color:var(--muted); font-weight:600}
.pill b{color:#fff; letter-spacing:.08em}
.row{display:flex; gap:10px; align-items:center; flex-wrap:wrap}
.btn{border:0; padding:10px 14px; border-radius:999px; font-weight:700; cursor:pointer;
transition:transform .06s, filter .15s, background .2s}
.btn:active{transform:translateY(1px)}
.btn-primary{background:var(--accent); color:#0a0a0a}
.btn-primary:hover{filter:brightness(1.05)}
.btn-primary:active{background:var(--accent-press)}
.btn-ghost{background:transparent; color:var(--text); border:1px solid var(--border)}
.btn-ghost:hover{border-color:var(--muted)}
.muted{color:var(--muted)}
/* Grid */
.grid{display:grid; grid-template-columns:1.1fr .9fr; gap:24px; margin-top:20px}
@media (max-width:980px){.grid{grid-template-columns:1fr}}
/* Cards */
.card{background:var(--elev); border:1px solid var(--border); border-radius:var(--radius); box-shadow:var(--shadow)}
.card-hd{padding:18px 18px 8px; display:flex; align-items:center; justify-content:space-between}
.card-bd{padding:16px 18px 20px}
/* Now Playing */
.now{display:grid; grid-template-columns:96px 1fr; gap:16px; align-items:center}
.cover{width:96px; height:96px; border-radius:10px; background:#0d0d0d; border:1px solid var(--border);
overflow:hidden; display:grid; place-content:center}
.cover img{width:100%; height:100%; object-fit:cover}
.track-title{font-size:18px; font-weight:700; margin:0 0 2px}
.artists{color:var(--muted); margin:0 0 6px}
#songEmbed iframe{border-radius:10px; width:100%; height:80px; border:0}
/* Round area (Kreis) */
#roundArea[hidden]{display:none}
#options{
position:relative; width:100%; max-width:520px; aspect-ratio:1; margin:14px 0 6px;
border-radius:50%;
}
.player-option{
position:absolute;
left:50%;
top:50%;
transform-origin:center;
background:#0f0f0f; color:var(--text);
border:1px solid var(--border);
border-radius:999px; padding:8px 12px;
font-weight:700;
white-space:nowrap;
transition:filter .15s, transform .06s, background .2s, box-shadow .2s;
box-shadow:0 0 0 0 var(--glow);
}
.player-option:hover{filter:brightness(1.1)}
.player-option:active{transform:scale(.98)}
.player-option:disabled{opacity:.7}
.player-option.correct{background:var(--accent); color:#0a0a0a; box-shadow:0 0 0 6px var(--glow)}
/* Players + Scoreboard */
#playersList{list-style:none; padding:0; margin:0; display:grid;
grid-template-columns:repeat(auto-fill, minmax(200px,1fr)); gap:12px}
#playersList > *{display:flex; align-items:center; gap:10px; padding:10px;
border:1px solid var(--border); border-radius:12px; background:#101010}
#scoreboard{list-style:none; padding:0; margin:0}
#scoreboard li{display:flex; justify-content:space-between; padding:10px 4px; border-bottom:1px solid var(--border)}
/* Aside / Device select */
aside{background:transparent}
.section-title{font-weight:700; margin:0 0 10px}
#songList{list-style:none; padding:0; margin:0 0 14px; display:flex; flex-direction:column; gap:8px}
#songList li{padding:10px 12px; border:1px solid var(--border); border-radius:10px; background:#101010}
#deviceSelectArea label{color:var(--muted); font-size:13px}
#deviceSelect{
width:100%; margin-top:8px;
background:#0f0f0f; color:#fff; border:1px solid var(--border); border-radius:12px; padding:10px 12px; outline:none;
}
#deviceSelect:focus{border-color:var(--accent); box-shadow:0 0 0 3px var(--glow)}
/* Toast (Clipboard-Notice) */
.toast{position:fixed; left:50%; bottom:22px; transform:translateX(-50%);
background:#101010; color:#fff; border:1px solid var(--border);
padding:10px 14px; border-radius:999px; box-shadow:var(--shadow); z-index:50; display:none}
.toast.show{display:block}
/* Slide-out Drawer */
.drawer{position:fixed; top:0; right:0; height:100%; width:340px; background:#0f0f0f;
border-left:1px solid var(--border); transform:translateX(100%); transition:transform .25s ease;
z-index:60; display:flex; flex-direction:column}
.drawer.open{transform:translateX(0)}
.drawer header{padding:16px; border-bottom:1px solid var(--border); display:flex; justify-content:space-between; align-items:center}
.drawer .list{padding:12px 16px; overflow:auto}
.drawer .track{display:flex; align-items:center; gap:10px; padding:8px 6px; border-radius:10px}
.drawer .track:hover{background:#141414}
.drawer .tcover{width:40px; height:40px; border-radius:6px; background:#1a1a1a; border:1px solid var(--border)}
.drawer .meta{display:flex; flex-direction:column}
.drawer .meta b{font-size:13px}
.drawer .meta span{font-size:12px; color:var(--muted)}
/* === Pie-Slices === */
#options svg.options-svg { width:100%; height:auto; display:block; border-radius:50%; }
.wedge { fill:#181818; stroke: #ffffff; stroke-width:4; cursor:pointer; transition:filter .15s, fill .2s; }
.wedge:hover { filter:brightness(1.08); }
.wedge.selected { fill: #0d351c; }
/* .wedge.correct { fill:#1db954; } */
.wedge.correct { fill: var(--accent) !important; } /* grün */
.wedge.wrong { fill: #e22134 !important; } /* rot */
.wedge.disabled { pointer-events:none; opacity:.85; }
.wedge-label { fill:#fff; font-weight:700; font-size:35px; pointer-events:none; }
/* === Winner Overlay === */
.win-overlay{position:fixed; inset:0; display:none; align-items:center; justify-content:center;
background: radial-gradient(1000px 600px at 50% -10%, rgba(29,185,84,.18), transparent 60%), rgba(0,0,0,.72);
z-index:999}
.win-modal{width:min(720px,92vw); background:#121212; border:1px solid var(--border);
border-radius:24px; padding:28px 26px; box-shadow:0 24px 80px rgba(0,0,0,.6); text-align:center}
.win-trophy{width:88px; height:88px; margin:0 auto 10px; color:var(--accent); filter:drop-shadow(0 0 16px rgba(29,185,84,.35))}
.win-title{font-size:clamp(22px,4.5vw,28px); color:var(--muted); margin:6px 0 4px}
.win-name{font-size:clamp(32px,6vw,48px); font-weight:900; letter-spacing:.2px; margin:0 0 10px}
.win-points{color:#cfcfcf; margin:0 0 16px}
.win-actions{display:flex; gap:10px; justify-content:center; flex-wrap:wrap; margin-top:10px}
.confetti{position:absolute; top:-10vh; width:10px; height:16px; opacity:.9; animation:fall 3.2s linear infinite}
@keyframes fall{0%{transform:translateY(-10vh) rotate(0deg)}100%{transform:translateY(110vh) rotate(360deg)}}
/* Overlay-Scoreboard */
.win-leaderboard{ margin-top:14px; text-align:left }
.win-lead-title{ font-weight:800; color:#cfcfcf; margin:6px 0 8px }
.win-board{
list-style:none; padding:0; margin:0;
display:flex; flex-direction:column; gap:8px;
max-height:40vh; overflow:auto;
}
.win-board li{
display:flex; align-items:center; justify-content:space-between;
background:#0f0f0f; border:1px solid var(--border);
border-radius:12px; padding:10px 12px;
}
.win-board .left{ display:flex; align-items:center; gap:10px }
.win-board .rank{ width:28px; text-align:center; font-weight:900; color:#9f9f9f }
.win-board .name{ font-weight:700 }
.win-board .pts{ color:#cfcfcf; font-weight:700 }
.win-board li.winner{
border-color:var(--accent);
box-shadow:0 0 0 4px var(--glow);
}
</style>
</head>
<body>
<div class="container">
<header>
<div class="brand">
<!--TO DO eigenes Logo-->
<div class="logo" aria-hidden="true">
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M12 2 2 7l10 5 10-5-10-5Zm10 7-10 5v9l10-5V9ZM2 9v9l10 5v-9L2 9Z"/>
</svg>
</div>
<h1>Spotify Roulette</h1>
</div>
<div class="row">
<span class="pill">Spiel-Code: <b id="gameId"></b></span>
<button id="toggleSongList" class="btn btn-ghost" title="Zuletzt gehörte Songs">Songliste</button>
</div>
</header>
<div class="grid">
<!-- LEFT: Now Playing + Runde -->
<section class="card">
<div class="card-hd">
<h2 style="margin:0">Now Playing</h2>
<div class="row">
<button id="startRound" class="btn btn-ghost">Runde starten</button>
</div>
</div>
<div class="card-bd">
<section id="roundArea" hidden>
<div id="songEmbed" style="margin-bottom:12px"></div>
<div id="options"></div>
<div id="result" class="muted" style="margin-top:10px"></div>
<button id="nextRound" class="btn btn-primary" hidden>Nächster Song</button>
</section>
</div>
</section>
<!-- RIGHT: Teilnehmer + Scoreboard -->
<section class="card">
<div class="card-hd">
<h2 style="margin:0">Teilnehmer</h2>
</div>
<div class="card-bd">
<ul id="playersList"></ul>
</div>
<div class="card-hd"><h2 style="margin:0">Scoreboard</h2></div>
<div class="card-bd">
<ul id="scoreboard"></ul>
</div>
</section>
</div>
<!-- ASIDE: Geladene Songs + Wiedergabegerät -->
<div id="deviceSelectArea"></div>
</aside>
</div>
<!-- Drawer -->
<aside class="drawer" id="songDrawer" aria-label="Zuletzt gehörte Songs">
<header>
<h3 style="margin:0">Zuletzt gehört</h3>
<button class="btn btn-ghost" id="closeDrawer">Schließen</button>
</header>
<div class="list" id="songListArea"></div>
</aside>
<!-- Toast -->
<div id="toast" class="toast" role="status" aria-live="polite"></div>
<!-- Winner Overlay -->
<div id="winnerOverlay" class="win-overlay" role="dialog" aria-modal="true" aria-labelledby="winName">
<div class="win-modal">
<div class="win-trophy" aria-hidden="true">
<svg viewBox="0 0 24 24" width="88" height="88" fill="currentColor">
<path d="M19 3h-3V2a1 1 0 0 0-1-1H9a1 1 0 0 0-1 1v1H5a1 1 0 0 0-1 1v2a5 5 0 0 0 5 5h.1A5.98 5.98 0 0 0 11 14.9V17H8a1 1 0 1 0 0 2h8a1 1 0 1 0 0-2h-3v-2.1A5.98 5.98 0 0 0 15.9 11H16a5 5 0 0 0 5-5V4a1 1 0 0 0-1-1ZM6 6V5h2v2a3 3 0 0 1-3-1Zm14 0a3 3 0 0 1-3 1V5h3v1Z"/>
</svg>
</div>
<p class="win-title">Gewonnen!</p>
<h2 id="winName" class="win-name"></h2>
<p id="winPoints" class="win-points"></p>
<!-- Leaderboard im Overlay -->
<div class="win-leaderboard">
<div class="win-lead-title">Leaderboard</div>
<ol id="winBoard" class="win-board"></ol>
</div>
<div class="win-actions">
<!-- <button id="winNext" class="btn btn-primary">Neue Runde</button> -->
<button id="winClose" class="btn btn-ghost">Schließen</button>
<button id="winShare" class="btn btn-ghost">Teilen</button>
</div>
</div>
</div>
<!-- Module -->
<script type="module" src="/js/utils.js"></script>
<script type="module" src="/js/start-round.js"></script>
<script type="module" src="/js/device-select.js"></script> <!-- füllt #deviceSelectArea -->
<script type="module" src="/js/game.js"></script>
</body>
</html>