Theme
This commit is contained in:
@@ -86,7 +86,6 @@
|
|||||||
<label for="voting-timeout">Voting timeout (seconds)</label>
|
<label for="voting-timeout">Voting timeout (seconds)</label>
|
||||||
<div class="number-input-wrap number-with-unit">
|
<div class="number-input-wrap number-with-unit">
|
||||||
<input type="number" id="voting-timeout" name="votingTimeoutSec" min="0" max="3600" value="{{ .DefaultVotingTime }}">
|
<input type="number" id="voting-timeout" name="votingTimeoutSec" min="0" max="3600" value="{{ .DefaultVotingTime }}">
|
||||||
<span class="input-unit">sec</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -287,6 +287,7 @@
|
|||||||
height: 6rem;
|
height: 6rem;
|
||||||
border-radius: 0.4rem;
|
border-radius: 0.4rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
overflow: visible;
|
||||||
transition: transform 120ms ease;
|
transition: transform 120ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -145,6 +145,12 @@ input[type="number"]::-webkit-inner-spin-button {
|
|||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.participant-name.is-admin {
|
||||||
|
color: #2f58ff;
|
||||||
|
text-shadow: 0 0 0.2rem rgba(47, 88, 255, 0.32);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
.summary-table {
|
.summary-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
@@ -164,13 +170,41 @@ input[type="number"]::-webkit-inner-spin-button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.vote-card.impact {
|
.vote-card.impact {
|
||||||
animation: vote-impact 170ms ease;
|
animation: vote-impact 320ms cubic-bezier(0.2, 0.85, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes vote-impact {
|
@keyframes vote-impact {
|
||||||
0% { transform: scale(1); }
|
0% { transform: translateX(0) scale(1) rotate(0deg); }
|
||||||
40% { transform: scale(0.91); }
|
15% { transform: translateX(-0.18rem) scale(0.9) rotate(-2.2deg); }
|
||||||
100% { transform: scale(1); }
|
30% { transform: translateX(0.14rem) scale(1.06) rotate(1.7deg); }
|
||||||
|
45% { transform: translateX(-0.1rem) scale(0.98) rotate(-1.4deg); }
|
||||||
|
60% { transform: translateX(0.08rem) scale(1.04) rotate(1deg); }
|
||||||
|
100% { transform: translateX(0) scale(1) rotate(0deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.vote-particle {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
width: 0.32rem;
|
||||||
|
height: 0.32rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
pointer-events: none;
|
||||||
|
background: hsl(var(--hue, 120) 95% 66%);
|
||||||
|
box-shadow: 0 0 0.35rem hsl(var(--hue, 120) 95% 66%);
|
||||||
|
transform: translate(-50%, -50%) scale(1);
|
||||||
|
animation: vote-particle-burst 420ms ease-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes vote-particle-burst {
|
||||||
|
0% {
|
||||||
|
opacity: 0.95;
|
||||||
|
transform: translate(-50%, -50%) scale(1);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translate(calc(-50% + var(--tx, 0px)), calc(-50% + var(--ty, 0px))) scale(0.2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.terminal-window .title-bar {
|
.terminal-window .title-bar {
|
||||||
|
|||||||
@@ -1,56 +1,64 @@
|
|||||||
:root[data-ui-theme="modern"] {
|
:root[data-ui-theme="modern"] {
|
||||||
--font-main: 'Segoe UI', 'Inter', Arial, sans-serif;
|
--font-main: "Segoe UI Variable Text", "Segoe UI", "Inter", "SF Pro Text", "Helvetica Neue", Arial, sans-serif;
|
||||||
--desktop-bg: #e8edf4;
|
--desktop-bg: #282c34;
|
||||||
--desktop-pattern: linear-gradient(135deg, #eef3f8 0%, #dde7f2 100%);
|
--desktop-pattern:
|
||||||
--surface-window: #ffffff;
|
radial-gradient(circle at 14% 18%, rgba(97, 175, 239, 0.18) 0, rgba(97, 175, 239, 0) 46%),
|
||||||
--surface-control: #f1f4f8;
|
radial-gradient(circle at 88% 10%, rgba(198, 120, 221, 0.18) 0, rgba(198, 120, 221, 0) 40%),
|
||||||
--surface-input: #ffffff;
|
radial-gradient(circle at 54% 100%, rgba(86, 182, 194, 0.13) 0, rgba(86, 182, 194, 0) 52%),
|
||||||
--surface-status: #f8fafc;
|
linear-gradient(165deg, #2c313c 0%, #232731 100%);
|
||||||
--text-primary: #101828;
|
--surface-window: rgba(40, 44, 52, 0.92);
|
||||||
--title-bg: #175cd3;
|
--surface-control: #3a404b;
|
||||||
--title-text: #ffffff;
|
--surface-input: #21252b;
|
||||||
--border-outer: #c4ced9;
|
--surface-status: #2f343f;
|
||||||
--border-input: #b8c3d2;
|
--text-primary: #abb2bf;
|
||||||
--border-muted: #d5dde8;
|
--title-bg: linear-gradient(90deg, #353b46 0%, #2c313c 100%);
|
||||||
|
--title-text: #e6edf7;
|
||||||
|
--border-outer: #4b5263;
|
||||||
|
--border-input: #5c6370;
|
||||||
|
--border-muted: #3a404b;
|
||||||
--window-border-width: 1px;
|
--window-border-width: 1px;
|
||||||
--control-border-width: 1px;
|
--control-border-width: 1px;
|
||||||
--input-border-width: 1px;
|
--input-border-width: 1px;
|
||||||
--window-shadow: 0 12px 30px rgba(16, 24, 40, 0.14);
|
--window-shadow: 0 18px 45px rgba(14, 16, 20, 0.42);
|
||||||
--button-shadow: none;
|
--button-shadow: 0 4px 16px rgba(0, 0, 0, 0.22);
|
||||||
--button-shadow-active: none;
|
--button-shadow-active: inset 0 1px 0 rgba(0, 0, 0, 0.25);
|
||||||
--focus-ring: 0 0 0 2px rgba(23, 92, 211, 0.22);
|
--focus-ring: 0 0 0 2px rgba(97, 175, 239, 0.35);
|
||||||
--card-bg: #ffffff;
|
--card-bg: #21252b;
|
||||||
--card-text: #101828;
|
--card-text: #e6edf7;
|
||||||
--card-border: #b8c3d2;
|
--card-border: #5c6370;
|
||||||
--card-border-width: 1px;
|
--card-border-width: 1px;
|
||||||
--selected-outline: 2px solid #175cd3;
|
--selected-outline: 2px solid #61afef;
|
||||||
--modal-overlay: rgba(15, 23, 42, 0.35);
|
--modal-overlay: rgba(10, 12, 16, 0.62);
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[data-ui-theme="modern"][data-theme="dark"] {
|
:root[data-ui-theme="modern"][data-theme="dark"] {
|
||||||
--desktop-bg: #0b1220;
|
--desktop-bg: #21252b;
|
||||||
--desktop-pattern: linear-gradient(140deg, #111b2c 0%, #09101d 100%);
|
--desktop-pattern:
|
||||||
--surface-window: #111827;
|
radial-gradient(circle at 12% 16%, rgba(97, 175, 239, 0.16) 0, rgba(97, 175, 239, 0) 46%),
|
||||||
--surface-control: #1f2937;
|
radial-gradient(circle at 86% 8%, rgba(198, 120, 221, 0.16) 0, rgba(198, 120, 221, 0) 42%),
|
||||||
--surface-input: #0f172a;
|
linear-gradient(165deg, #252932 0%, #1d2027 100%);
|
||||||
--surface-status: #172033;
|
--surface-window: rgba(33, 37, 43, 0.95);
|
||||||
--text-primary: #e5edf7;
|
--surface-control: #343a45;
|
||||||
--title-bg: #1d4ed8;
|
--surface-input: #1d2127;
|
||||||
--title-text: #ffffff;
|
--surface-status: #2a2f38;
|
||||||
--border-outer: #334155;
|
--text-primary: #b9c2d0;
|
||||||
--border-input: #3f5169;
|
--title-bg: linear-gradient(90deg, #2f343f 0%, #272c35 100%);
|
||||||
--border-muted: #334155;
|
--title-text: #eef3fb;
|
||||||
--window-shadow: 0 14px 34px rgba(0, 0, 0, 0.45);
|
--border-outer: #5c6370;
|
||||||
--focus-ring: 0 0 0 2px rgba(96, 165, 250, 0.34);
|
--border-input: #6c7484;
|
||||||
--card-bg: #1e293b;
|
--border-muted: #404652;
|
||||||
--card-text: #e5edf7;
|
--window-shadow: 0 20px 52px rgba(8, 10, 13, 0.58);
|
||||||
--card-border: #334155;
|
--focus-ring: 0 0 0 2px rgba(97, 175, 239, 0.42);
|
||||||
|
--card-bg: #1d2127;
|
||||||
|
--card-text: #eef3fb;
|
||||||
|
--card-border: #6c7484;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[data-ui-theme="modern"] body {
|
:root[data-ui-theme="modern"] body {
|
||||||
background-color: var(--desktop-bg);
|
background-color: var(--desktop-bg);
|
||||||
background-image: var(--desktop-pattern);
|
background-image: var(--desktop-pattern);
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
|
background-attachment: fixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[data-ui-theme="modern"] .window,
|
:root[data-ui-theme="modern"] .window,
|
||||||
@@ -60,20 +68,161 @@
|
|||||||
:root[data-ui-theme="modern"] input,
|
:root[data-ui-theme="modern"] input,
|
||||||
:root[data-ui-theme="modern"] select,
|
:root[data-ui-theme="modern"] select,
|
||||||
:root[data-ui-theme="modern"] textarea {
|
:root[data-ui-theme="modern"] textarea {
|
||||||
|
border-radius: 0.62rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-ui-theme="modern"] .window {
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-ui-theme="modern"] .mobile-control-strip,
|
||||||
|
:root[data-ui-theme="modern"] .taskbar {
|
||||||
|
background: rgba(40, 44, 52, 0.82);
|
||||||
|
border-top-color: var(--border-input);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-ui-theme="modern"] .title-bar {
|
||||||
|
font-size: 0.96rem;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
border-bottom: 1px solid rgba(97, 175, 239, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-ui-theme="modern"] .title-bar-controls button {
|
||||||
border-radius: 0.45rem;
|
border-radius: 0.45rem;
|
||||||
|
border-color: var(--border-input);
|
||||||
|
background: #323843;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-ui-theme="modern"] .title-bar-controls button:hover,
|
||||||
|
:root[data-ui-theme="modern"] .btn:hover {
|
||||||
|
background: #434a57;
|
||||||
|
border-color: #7d8699;
|
||||||
|
color: #e6edf7;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-ui-theme="modern"] .btn {
|
||||||
|
transition: background 120ms ease, border-color 120ms ease, transform 120ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-ui-theme="modern"] .btn:active {
|
||||||
|
transform: translateY(1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-ui-theme="modern"] .btn-primary {
|
||||||
|
background: #61afef;
|
||||||
|
border-color: #61afef;
|
||||||
|
color: #10131a;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-ui-theme="modern"] .btn-primary:hover {
|
||||||
|
background: #79bcf3;
|
||||||
|
border-color: #79bcf3;
|
||||||
|
color: #0d1117;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-ui-theme="modern"] input::placeholder,
|
||||||
|
:root[data-ui-theme="modern"] textarea::placeholder {
|
||||||
|
color: #7d8699;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-ui-theme="modern"] .status-line,
|
||||||
|
:root[data-ui-theme="modern"] .summary-table,
|
||||||
|
:root[data-ui-theme="modern"] .participant-list {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-ui-theme="modern"] .summary-table th {
|
||||||
|
color: #61afef;
|
||||||
|
background: #272c35;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-ui-theme="modern"] .participant-item:hover {
|
||||||
|
background: rgba(97, 175, 239, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-ui-theme="modern"] .participant-name.is-admin {
|
||||||
|
color: #d19a66;
|
||||||
|
text-shadow: 0 0 0.25rem rgba(209, 154, 102, 0.28);
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[data-ui-theme="modern"] .preview-board,
|
:root[data-ui-theme="modern"] .preview-board,
|
||||||
:root[data-ui-theme="modern"] .voting-board {
|
:root[data-ui-theme="modern"] .voting-board {
|
||||||
background: linear-gradient(180deg, #0f6f54 0%, #0a5c45 100%);
|
background:
|
||||||
border: 1px solid var(--border-input);
|
linear-gradient(145deg, rgba(97, 175, 239, 0.07) 0%, rgba(33, 37, 43, 0) 34%),
|
||||||
|
linear-gradient(0deg, rgba(92, 99, 112, 0.08), rgba(92, 99, 112, 0.08)),
|
||||||
|
#20242b;
|
||||||
|
border: 1px solid #5c6370;
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(97, 175, 239, 0.15), inset 0 -20px 40px rgba(0, 0, 0, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[data-ui-theme="modern"][data-theme="dark"] .preview-board,
|
:root[data-ui-theme="modern"][data-theme="dark"] .preview-board,
|
||||||
:root[data-ui-theme="modern"][data-theme="dark"] .voting-board {
|
:root[data-ui-theme="modern"][data-theme="dark"] .voting-board {
|
||||||
background: linear-gradient(180deg, #0b3e33 0%, #082d25 100%);
|
background:
|
||||||
|
linear-gradient(145deg, rgba(97, 175, 239, 0.07) 0%, rgba(33, 37, 43, 0) 35%),
|
||||||
|
linear-gradient(0deg, rgba(108, 116, 132, 0.08), rgba(108, 116, 132, 0.08)),
|
||||||
|
#1b1f25;
|
||||||
|
border-color: #6c7484;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-ui-theme="modern"] .vote-card,
|
||||||
|
:root[data-ui-theme="modern"] .preview-card {
|
||||||
|
box-shadow: 0 5px 16px rgba(0, 0, 0, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-ui-theme="modern"] .vote-card:hover {
|
||||||
|
border-color: #61afef;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-ui-theme="modern"] .vote-card.is-selected {
|
||||||
|
box-shadow: 0 0 0 1px #61afef, 0 10px 22px rgba(97, 175, 239, 0.22);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-ui-theme="modern"] .hint-text {
|
||||||
|
color: #7d8699;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-ui-theme="modern"] .icon-btn img {
|
||||||
|
image-rendering: auto;
|
||||||
|
filter: saturate(0.9) brightness(0.9);
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[data-ui-theme="modern"] .preset-modal-overlay {
|
:root[data-ui-theme="modern"] .preset-modal-overlay {
|
||||||
background: var(--modal-overlay);
|
background: var(--modal-overlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root[data-ui-theme="modern"] .skeleton-line,
|
||||||
|
:root[data-ui-theme="modern"] .skeleton-board,
|
||||||
|
:root[data-ui-theme="modern"] .skeleton-table,
|
||||||
|
:root[data-ui-theme="modern"] .skeleton-list,
|
||||||
|
:root[data-ui-theme="modern"] .skeleton-controls {
|
||||||
|
background: linear-gradient(90deg, rgba(92, 99, 112, 0.22), rgba(122, 132, 148, 0.32), rgba(92, 99, 112, 0.22));
|
||||||
|
background-size: 220% 100%;
|
||||||
|
border: 1px solid #4b5263;
|
||||||
|
border-radius: 0.4rem;
|
||||||
|
animation: modern-skeleton-shimmer 1.2s ease infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes modern-skeleton-shimmer {
|
||||||
|
from { background-position: 0 0; }
|
||||||
|
to { background-position: -200% 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-ui-theme="modern"] .terminal-window .title-bar {
|
||||||
|
background: #21252b;
|
||||||
|
color: #98c379;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-ui-theme="modern"] .terminal-window .title-bar-controls button {
|
||||||
|
background: #2f343f;
|
||||||
|
color: #98c379;
|
||||||
|
border-color: #5c6370;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-ui-theme="modern"] .terminal-log-output {
|
||||||
|
background: #1b1f25;
|
||||||
|
color: #abb2bf;
|
||||||
|
border: 1px solid #4b5263;
|
||||||
|
font-family: "JetBrains Mono", "Cascadia Code", "SFMono-Regular", Consolas, "Liberation Mono", monospace;
|
||||||
|
}
|
||||||
|
|||||||
@@ -522,7 +522,7 @@ roomConfigForm.addEventListener('submit', async (event) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const target = `/room/${encodeURIComponent(data.roomId)}?participantId=${encodeURIComponent(data.creatorParticipantId)}&adminToken=${encodeURIComponent(data.adminToken)}`;
|
const target = `/room/${encodeURIComponent(data.roomId)}?participantId=${encodeURIComponent(data.creatorParticipantId)}&adminToken=${encodeURIComponent(data.adminToken)}&username=${encodeURIComponent(payload.creatorUsername)}`;
|
||||||
window.location.assign(target);
|
window.location.assign(target);
|
||||||
} catch (_err) {
|
} catch (_err) {
|
||||||
statusLine.textContent = 'Network error while creating room.';
|
statusLine.textContent = 'Network error while creating room.';
|
||||||
|
|||||||
@@ -33,12 +33,13 @@ const joinAdminTokenInput = document.getElementById('join-admin-token');
|
|||||||
const joinError = document.getElementById('join-error');
|
const joinError = document.getElementById('join-error');
|
||||||
let participantID = params.get('participantId') || '';
|
let participantID = params.get('participantId') || '';
|
||||||
let adminToken = params.get('adminToken') || '';
|
let adminToken = params.get('adminToken') || '';
|
||||||
|
const prefillUsername = params.get('username') || '';
|
||||||
let eventSource = null;
|
let eventSource = null;
|
||||||
let latestLinks = { participantLink: '', adminLink: '' };
|
let latestLinks = { participantLink: '', adminLink: '' };
|
||||||
let latestAdminLogs = [];
|
let latestAdminLogs = [];
|
||||||
|
|
||||||
const savedUsername = localStorage.getItem(USERNAME_KEY) || '';
|
const savedUsername = localStorage.getItem(USERNAME_KEY) || '';
|
||||||
joinUsernameInput.value = savedUsername;
|
joinUsernameInput.value = prefillUsername || savedUsername;
|
||||||
joinAdminTokenInput.value = adminToken;
|
joinAdminTokenInput.value = adminToken;
|
||||||
|
|
||||||
if (!window.CardUI || typeof window.CardUI.appendFace !== 'function') {
|
if (!window.CardUI || typeof window.CardUI.appendFace !== 'function') {
|
||||||
@@ -59,6 +60,8 @@ function setJoinError(message) {
|
|||||||
|
|
||||||
function updateURL() {
|
function updateURL() {
|
||||||
const next = new URL(window.location.href);
|
const next = new URL(window.location.href);
|
||||||
|
next.searchParams.delete('username');
|
||||||
|
|
||||||
if (participantID) {
|
if (participantID) {
|
||||||
next.searchParams.set('participantId', participantID);
|
next.searchParams.set('participantId', participantID);
|
||||||
} else {
|
} else {
|
||||||
@@ -103,6 +106,7 @@ async function joinRoom({ username, role, password, participantIdOverride }) {
|
|||||||
localStorage.setItem(USERNAME_KEY, data.username);
|
localStorage.setItem(USERNAME_KEY, data.username);
|
||||||
updateURL();
|
updateURL();
|
||||||
setJoinError('');
|
setJoinError('');
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderParticipants(participants, isRevealed) {
|
function renderParticipants(participants, isRevealed) {
|
||||||
@@ -114,12 +118,14 @@ function renderParticipants(participants, isRevealed) {
|
|||||||
item.className = 'participant-item';
|
item.className = 'participant-item';
|
||||||
|
|
||||||
const name = document.createElement('span');
|
const name = document.createElement('span');
|
||||||
|
name.className = 'participant-name';
|
||||||
let label = participant.username;
|
let label = participant.username;
|
||||||
if (participant.id === participantID) {
|
if (participant.id === participantID) {
|
||||||
label += ' (You)';
|
label += ' (You)';
|
||||||
}
|
}
|
||||||
if (participant.isAdmin) {
|
if (participant.isAdmin) {
|
||||||
label += ' [Admin]';
|
label += ' [Admin]';
|
||||||
|
name.classList.add('is-admin');
|
||||||
}
|
}
|
||||||
name.textContent = label;
|
name.textContent = label;
|
||||||
|
|
||||||
@@ -247,6 +253,7 @@ function renderCards(cards, participants, isRevealed) {
|
|||||||
card.classList.remove('impact');
|
card.classList.remove('impact');
|
||||||
void card.offsetWidth;
|
void card.offsetWidth;
|
||||||
card.classList.add('impact');
|
card.classList.add('impact');
|
||||||
|
spawnVoteParticles(card);
|
||||||
castVote(value);
|
castVote(value);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -254,6 +261,26 @@ function renderCards(cards, participants, isRevealed) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function spawnVoteParticles(card) {
|
||||||
|
const particleCount = 12;
|
||||||
|
for (let i = 0; i < particleCount; i += 1) {
|
||||||
|
const particle = document.createElement('span');
|
||||||
|
particle.className = 'vote-particle';
|
||||||
|
|
||||||
|
const angle = (Math.PI * 2 * i) / particleCount + (Math.random() * 0.4 - 0.2);
|
||||||
|
const distance = 18 + Math.random() * 26;
|
||||||
|
const dx = Math.cos(angle) * distance;
|
||||||
|
const dy = Math.sin(angle) * distance;
|
||||||
|
|
||||||
|
particle.style.setProperty('--tx', `${dx.toFixed(2)}px`);
|
||||||
|
particle.style.setProperty('--ty', `${dy.toFixed(2)}px`);
|
||||||
|
particle.style.setProperty('--hue', `${Math.floor(Math.random() * 80) + 90}`);
|
||||||
|
|
||||||
|
particle.addEventListener('animationend', () => particle.remove(), { once: true });
|
||||||
|
card.appendChild(particle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function formatLogTime(raw) {
|
function formatLogTime(raw) {
|
||||||
const parsed = new Date(raw);
|
const parsed = new Date(raw);
|
||||||
if (Number.isNaN(parsed.getTime())) {
|
if (Number.isNaN(parsed.getTime())) {
|
||||||
@@ -417,12 +444,17 @@ joinForm.addEventListener('submit', async (event) => {
|
|||||||
adminToken = joinAdminTokenInput.value.trim();
|
adminToken = joinAdminTokenInput.value.trim();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await joinRoom({
|
const result = await joinRoom({
|
||||||
username,
|
username,
|
||||||
role: joinRoleInput.value,
|
role: joinRoleInput.value,
|
||||||
password: joinPasswordInput.value,
|
password: joinPasswordInput.value,
|
||||||
participantIdOverride: participantID,
|
participantIdOverride: participantID,
|
||||||
});
|
});
|
||||||
|
if (result.isAdmin) {
|
||||||
|
const adminRoomURL = `/room/${encodeURIComponent(roomID)}?participantId=${encodeURIComponent(participantID)}&adminToken=${encodeURIComponent(adminToken)}`;
|
||||||
|
window.location.assign(adminRoomURL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
connectSSE();
|
connectSSE();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (participantID) {
|
if (participantID) {
|
||||||
@@ -433,6 +465,27 @@ joinForm.addEventListener('submit', async (event) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function tryAutoJoinExistingParticipant() {
|
||||||
|
if (!participantID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const username = joinUsernameInput.value.trim() || prefillUsername || 'host';
|
||||||
|
|
||||||
|
try {
|
||||||
|
await joinRoom({
|
||||||
|
username,
|
||||||
|
role: 'participant',
|
||||||
|
password: joinPasswordInput.value,
|
||||||
|
participantIdOverride: participantID,
|
||||||
|
});
|
||||||
|
connectSSE();
|
||||||
|
} catch (_err) {
|
||||||
|
participantID = '';
|
||||||
|
updateURL();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
window.addEventListener('pagehide', () => {
|
window.addEventListener('pagehide', () => {
|
||||||
if (!participantID) {
|
if (!participantID) {
|
||||||
return;
|
return;
|
||||||
@@ -441,3 +494,5 @@ window.addEventListener('pagehide', () => {
|
|||||||
const payload = JSON.stringify({ participantId: participantID });
|
const payload = JSON.stringify({ participantId: participantID });
|
||||||
navigator.sendBeacon(`/api/rooms/${encodeURIComponent(roomID)}/leave`, new Blob([payload], { type: 'application/json' }));
|
navigator.sendBeacon(`/api/rooms/${encodeURIComponent(roomID)}/leave`, new Blob([payload], { type: 'application/json' }));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
void tryAutoJoinExistingParticipant();
|
||||||
|
|||||||
Reference in New Issue
Block a user