Add share link button for room invites
- Add 🔗 button next to room code copy button
- Copies full URL with ?room=XXXX parameter
- On page load, pre-fills room code from URL param
- Works with both logged-in users and guests
- Cleans up URL after extracting room code
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
0b0873350c
commit
0c8d2b4a9c
@ -33,6 +33,20 @@ class GolfGame {
|
|||||||
this.initElements();
|
this.initElements();
|
||||||
this.initAudio();
|
this.initAudio();
|
||||||
this.bindEvents();
|
this.bindEvents();
|
||||||
|
this.checkUrlParams();
|
||||||
|
}
|
||||||
|
|
||||||
|
checkUrlParams() {
|
||||||
|
// Handle ?room=XXXX share links
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const roomCode = params.get('room');
|
||||||
|
if (roomCode) {
|
||||||
|
this.roomCodeInput.value = roomCode.toUpperCase();
|
||||||
|
// Focus name input so user can quickly enter name and join
|
||||||
|
this.playerNameInput.focus();
|
||||||
|
// Clean up URL without reloading
|
||||||
|
window.history.replaceState({}, '', window.location.pathname);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
initAudio() {
|
initAudio() {
|
||||||
@ -140,6 +154,7 @@ class GolfGame {
|
|||||||
// Waiting room elements
|
// Waiting room elements
|
||||||
this.displayRoomCode = document.getElementById('display-room-code');
|
this.displayRoomCode = document.getElementById('display-room-code');
|
||||||
this.copyRoomCodeBtn = document.getElementById('copy-room-code');
|
this.copyRoomCodeBtn = document.getElementById('copy-room-code');
|
||||||
|
this.shareRoomLinkBtn = document.getElementById('share-room-link');
|
||||||
this.playersList = document.getElementById('players-list');
|
this.playersList = document.getElementById('players-list');
|
||||||
this.hostSettings = document.getElementById('host-settings');
|
this.hostSettings = document.getElementById('host-settings');
|
||||||
this.waitingMessage = document.getElementById('waiting-message');
|
this.waitingMessage = document.getElementById('waiting-message');
|
||||||
@ -234,6 +249,12 @@ class GolfGame {
|
|||||||
this.copyRoomCode();
|
this.copyRoomCode();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Share room link
|
||||||
|
this.shareRoomLinkBtn.addEventListener('click', () => {
|
||||||
|
this.playSound('click');
|
||||||
|
this.shareRoomLink();
|
||||||
|
});
|
||||||
|
|
||||||
// Enter key handlers
|
// Enter key handlers
|
||||||
this.playerNameInput.addEventListener('keypress', (e) => {
|
this.playerNameInput.addEventListener('keypress', (e) => {
|
||||||
if (e.key === 'Enter') this.createRoomBtn.click();
|
if (e.key === 'Enter') this.createRoomBtn.click();
|
||||||
@ -511,22 +532,47 @@ class GolfGame {
|
|||||||
|
|
||||||
copyRoomCode() {
|
copyRoomCode() {
|
||||||
if (!this.roomCode) return;
|
if (!this.roomCode) return;
|
||||||
|
this.copyToClipboard(this.roomCode, this.copyRoomCodeBtn);
|
||||||
|
}
|
||||||
|
|
||||||
navigator.clipboard.writeText(this.roomCode).then(() => {
|
shareRoomLink() {
|
||||||
// Show brief visual feedback
|
if (!this.roomCode) return;
|
||||||
const originalText = this.copyRoomCodeBtn.textContent;
|
|
||||||
this.copyRoomCodeBtn.textContent = '✓';
|
// Build shareable URL with room code
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
url.search = ''; // Clear existing params
|
||||||
|
url.hash = ''; // Clear hash
|
||||||
|
url.searchParams.set('room', this.roomCode);
|
||||||
|
const shareUrl = url.toString();
|
||||||
|
|
||||||
|
this.copyToClipboard(shareUrl, this.shareRoomLinkBtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
copyToClipboard(text, feedbackBtn) {
|
||||||
|
// Use execCommand which is more reliable across contexts
|
||||||
|
const textarea = document.createElement('textarea');
|
||||||
|
textarea.value = text;
|
||||||
|
textarea.style.position = 'fixed';
|
||||||
|
textarea.style.left = '-9999px';
|
||||||
|
document.body.appendChild(textarea);
|
||||||
|
textarea.select();
|
||||||
|
|
||||||
|
let success = false;
|
||||||
|
try {
|
||||||
|
success = document.execCommand('copy');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Copy failed:', err);
|
||||||
|
}
|
||||||
|
document.body.removeChild(textarea);
|
||||||
|
|
||||||
|
// Show visual feedback
|
||||||
|
if (success && feedbackBtn) {
|
||||||
|
const originalText = feedbackBtn.textContent;
|
||||||
|
feedbackBtn.textContent = '✓';
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.copyRoomCodeBtn.textContent = originalText;
|
feedbackBtn.textContent = originalText;
|
||||||
}, 1500);
|
}, 1500);
|
||||||
}).catch(err => {
|
}
|
||||||
console.error('Failed to copy room code:', err);
|
|
||||||
// Fallback: select the text for manual copy
|
|
||||||
const range = document.createRange();
|
|
||||||
range.selectNode(this.displayRoomCode);
|
|
||||||
window.getSelection().removeAllRanges();
|
|
||||||
window.getSelection().addRange(range);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
startGame() {
|
startGame() {
|
||||||
|
|||||||
@ -53,7 +53,10 @@
|
|||||||
<div class="room-code-banner">
|
<div class="room-code-banner">
|
||||||
<span class="room-code-label">ROOM CODE</span>
|
<span class="room-code-label">ROOM CODE</span>
|
||||||
<span class="room-code-value" id="display-room-code"></span>
|
<span class="room-code-value" id="display-room-code"></span>
|
||||||
<button class="room-code-copy" id="copy-room-code" title="Copy to clipboard">📋</button>
|
<div class="room-code-buttons">
|
||||||
|
<button class="room-code-copy" id="copy-room-code" title="Copy code">📋</button>
|
||||||
|
<button class="room-code-copy" id="share-room-link" title="Copy link">🌐</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="waiting-layout">
|
<div class="waiting-layout">
|
||||||
|
|||||||
@ -275,50 +275,72 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Room Code Banner - positioned top-left to avoid auth bar overlap */
|
/* Room Code Banner - styled as a hanging ribbon/bookmark */
|
||||||
.room-code-banner {
|
.room-code-banner {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 20px;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
background: linear-gradient(135deg, rgba(244, 164, 96, 0.9) 0%, rgba(230, 140, 70, 0.95) 100%);
|
background: linear-gradient(180deg, #d4845a 0%, #c4723f 50%, #b8663a 100%);
|
||||||
padding: 10px 15px;
|
padding: 12px 16px 20px;
|
||||||
border-radius: 0 0 12px 0;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 4px;
|
||||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
|
box-shadow: 2px 4px 12px rgba(0, 0, 0, 0.3);
|
||||||
|
/* Ribbon forked end (snake tongue style) */
|
||||||
|
clip-path: polygon(0 0, 100% 0, 100% 100%, 50% calc(100% - 12px), 0 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-code-banner::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 4px;
|
||||||
|
background: linear-gradient(180deg, rgba(255,255,255,0.3) 0%, transparent 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.room-code-label {
|
.room-code-label {
|
||||||
font-size: 0.65rem;
|
font-size: 0.55rem;
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.1em;
|
letter-spacing: 0.25em;
|
||||||
color: rgba(26, 71, 42, 0.8);
|
color: rgba(255, 255, 255, 0.85);
|
||||||
|
text-align: center;
|
||||||
|
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.room-code-value {
|
.room-code-value {
|
||||||
font-size: 1.5rem;
|
font-size: 1.6rem;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
font-family: 'Courier New', monospace;
|
font-family: 'Courier New', monospace;
|
||||||
letter-spacing: 0.2em;
|
letter-spacing: 0.2em;
|
||||||
color: #1a472a;
|
color: #fff;
|
||||||
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.3);
|
text-shadow: 0 2px 2px rgba(0, 0, 0, 0.3);
|
||||||
|
padding: 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-code-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.room-code-copy {
|
.room-code-copy {
|
||||||
background: rgba(26, 71, 42, 0.2);
|
background: rgba(255, 255, 255, 0.85);
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 6px;
|
border-radius: 4px;
|
||||||
padding: 6px 8px;
|
padding: 4px 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 1rem;
|
font-size: 0.9rem;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
.room-code-copy:hover {
|
.room-code-copy:hover {
|
||||||
background: rgba(26, 71, 42, 0.3);
|
background: rgba(255, 255, 255, 0.35);
|
||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -326,10 +348,6 @@ body {
|
|||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
}
|
}
|
||||||
|
|
||||||
.room-code-copy.copied {
|
|
||||||
background: rgba(26, 71, 42, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 3rem;
|
font-size: 3rem;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user