diff --git a/public/index.html b/public/index.html
index 9000c44..eb61934 100644
--- a/public/index.html
+++ b/public/index.html
@@ -462,6 +462,25 @@
transition: opacity 0.2s;
}
.modal button:hover { opacity: 0.85; }
+ .modal-actions {
+ display: flex;
+ gap: 10px;
+ margin-top: 8px;
+ }
+ .modal-actions button {
+ width: auto;
+ flex: 1;
+ }
+ .modal-actions .btn-secondary {
+ background: transparent;
+ color: var(--text);
+ border: 1px solid var(--border);
+ }
+ .modal-actions .btn-secondary:hover {
+ border-color: var(--accent);
+ color: var(--accent);
+ opacity: 1;
+ }
/* ── Tabs ───────────────────────────────────────────────── */
.tabs {
@@ -544,7 +563,7 @@
border-radius: 12px;
padding: 14px 16px;
transition: border-color 0.2s, transform 0.15s;
- cursor: pointer;
+ cursor: default;
position: relative;
overflow: hidden;
}
@@ -562,6 +581,40 @@
font-size: 0.85rem;
font-weight: 700;
}
+ .option-actions {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 8px;
+ margin: 10px 0 2px;
+ flex-wrap: wrap;
+ }
+ .option-vote-btn {
+ border: 1px solid rgba(0,212,255,0.32);
+ background: rgba(0,212,255,0.10);
+ color: #dff9ff;
+ border-radius: 999px;
+ padding: 7px 12px;
+ font-size: 0.74rem;
+ font-weight: 800;
+ letter-spacing: 0.2px;
+ cursor: pointer;
+ transition: transform 0.15s, border-color 0.2s, background 0.2s;
+ }
+ .option-vote-btn:hover {
+ border-color: rgba(0,212,255,0.55);
+ background: rgba(0,212,255,0.16);
+ transform: translateY(-1px);
+ }
+ .option-vote-btn.voted {
+ border-color: rgba(52,211,153,0.35);
+ background: rgba(52,211,153,0.12);
+ color: #d8fff0;
+ }
+ .option-vote-btn.voted:hover {
+ border-color: rgba(52,211,153,0.6);
+ background: rgba(52,211,153,0.18);
+ }
.option-top {
display: flex;
@@ -1199,22 +1252,7 @@
.option-card { min-height: 72px; padding: 14px 16px; }
.option-name { font-size: 0.92rem; }
.option-desc { font-size: 0.76rem; }
-
- /* Add arrow hint to cards */
- .option-card::after {
- content: '›';
- position: absolute;
- right: 14px;
- top: 50%;
- transform: translateY(-50%);
- color: var(--text-muted);
- font-size: 1.2rem;
- opacity: 0.4;
- }
- .option-card.voted::after { display: none; }
-
- /* Touch feedback */
- .option-card:active { transform: scale(0.98); opacity: 0.9; }
+ .option-actions { justify-content: flex-start; }
}
/* ── Desktop (≥641px) ──────────────────────────────────── */
@@ -1293,6 +1331,19 @@
+
+
+
+
+
Confirm Vote
+
Are you sure you want to vote for this option?
+
+
+
+
+
+
+
📍 Cabo Bachelor Party — Vote
@@ -1425,6 +1476,8 @@
let ws = null;
let activeTab = 'hotel';
let priceRefreshTimer = null;
+ let pendingVoteOptionId = null;
+ let pendingVoteRemove = false;
// ── Init ───────────────────────────────────────────────────
function init() {
@@ -2011,6 +2064,9 @@
applyVoterName(name);
document.getElementById('nameModal').classList.add('hidden');
render();
+ if (pendingVoteOptionId) {
+ openVoteConfirm(pendingVoteOptionId, pendingVoteRemove);
+ }
}
function applyVoterName(name) {
@@ -2024,6 +2080,62 @@
document.getElementById('voterNameInput').focus();
}
+ function openVoteConfirm(optionId, remove = false) {
+ if (activeTab === 'results') return;
+ if (!state.voterName) {
+ pendingVoteOptionId = optionId;
+ pendingVoteRemove = remove;
+ document.getElementById('nameModal').classList.remove('hidden');
+ document.getElementById('voterNameInput').focus();
+ return;
+ }
+ if (!state.pollsOpen) {
+ showToast('Polls are currently closed', 'error');
+ return;
+ }
+
+ const opt = state.options.find(o => o.id === optionId);
+ if (!opt) return;
+
+ const alreadyVoted = getVoteEntries(opt).some(v => v.name === state.voterName);
+ pendingVoteOptionId = optionId;
+ pendingVoteRemove = remove || alreadyVoted;
+
+ const confirmTitle = document.getElementById('voteConfirmTitle');
+ const confirmText = document.getElementById('voteConfirmText');
+ const confirmBtn = document.getElementById('voteConfirmActionBtn');
+ const isRemoveAction = pendingVoteRemove;
+
+ if (confirmTitle) confirmTitle.textContent = isRemoveAction ? 'Remove Vote' : 'Confirm Vote';
+ if (confirmText) {
+ confirmText.textContent = isRemoveAction
+ ? `You already voted for ${opt.name}. Confirm to remove your vote.`
+ : `Confirm your vote for ${opt.name}.`;
+ }
+ if (confirmBtn) confirmBtn.textContent = isRemoveAction ? 'Remove Vote' : 'Vote';
+ document.getElementById('voteConfirmModal').classList.remove('hidden');
+ }
+
+ function closeVoteConfirm() {
+ pendingVoteOptionId = null;
+ pendingVoteRemove = false;
+ document.getElementById('voteConfirmModal').classList.add('hidden');
+ }
+
+ function confirmVote() {
+ if (!pendingVoteOptionId) return;
+ const optionId = pendingVoteOptionId;
+ const remove = pendingVoteRemove;
+ const opt = state.options.find(o => o.id === optionId);
+ if (!opt) {
+ closeVoteConfirm();
+ return;
+ }
+ wsSend({ type: 'vote', optionId, voterName: state.voterName, remove });
+ showToast(remove ? `Removed vote for ${opt.name}` : `Voted for ${opt.name}!`);
+ closeVoteConfirm();
+ }
+
// ── Tabs ───────────────────────────────────────────────────
function renderTabs() {
const bar = document.getElementById('tabsBar');
@@ -2174,7 +2286,7 @@
: (opt.url ? `🔗 ${opt.url.replace(/^https?:\/\//, '').split('/')[0]}` : '');
return `
-
+
${opt.name}
${voteEntries.length} vote${voteEntries.length !== 1 ? 's' : ''}
@@ -2183,6 +2295,16 @@
${linkPills}
${renderOptionFacts(opt)}
${renderPriceTrend(opt)}
+
+
+
@@ -2227,24 +2349,7 @@
// ── Voting ────────────────────────────────────────────────
function toggleVote(optionId) {
- if (activeTab === 'results') return; // no voting on results tab
- if (!state.voterName) {
- document.getElementById('nameModal').classList.remove('hidden');
- document.getElementById('voterNameInput').focus();
- return;
- }
- if (!state.pollsOpen) {
- showToast('Polls are currently closed', 'error');
- return;
- }
-
- const opt = state.options.find(o => o.id === optionId);
- if (!opt) return;
-
- const alreadyVoted = getVoteEntries(opt).some(v => v.name === state.voterName);
-
- wsSend({ type: 'vote', optionId, voterName: state.voterName, remove: alreadyVoted });
- showToast(alreadyVoted ? `Removed vote for ${opt.name}` : `Voted for ${opt.name}!`);
+ openVoteConfirm(optionId);
}
// ── Add option ────────────────────────────────────────────