diff --git a/modules/rmaps/components/folk-map-viewer.ts b/modules/rmaps/components/folk-map-viewer.ts index 6df9c44..80227e9 100644 --- a/modules/rmaps/components/folk-map-viewer.ts +++ b/modules/rmaps/components/folk-map-viewer.ts @@ -287,16 +287,108 @@ class FolkMapViewer extends HTMLElement { if (this.userName) return true; // Use EncryptID username if authenticated const identityName = getUsername(); - const name = identityName || prompt("Your display name for this room:"); - if (!name?.trim()) return false; - this.userName = name.trim(); - localStorage.setItem("rmaps_user", JSON.stringify({ - id: this.participantId, - name: this.userName, - emoji: this.userEmoji, - color: this.userColor, - })); - return true; + if (identityName) { + this.userName = identityName; + localStorage.setItem("rmaps_user", JSON.stringify({ + id: this.participantId, + name: this.userName, + emoji: this.userEmoji, + color: this.userColor, + })); + return true; + } + return false; + } + + private pendingRoomSlug = ""; + + private showJoinForm(slug: string) { + this.pendingRoomSlug = slug; + const saved = JSON.parse(localStorage.getItem("rmaps_user") || "null"); + const savedName = saved?.name || ""; + const savedEmoji = saved?.emoji || this.userEmoji; + + const overlay = document.createElement("div"); + overlay.id = "join-form-overlay"; + overlay.style.cssText = ` + position:fixed;inset:0;z-index:50;background:rgba(0,0,0,0.6); + display:flex;align-items:center;justify-content:center; + backdrop-filter:blur(4px); + `; + + const card = document.createElement("div"); + card.style.cssText = ` + background:var(--rs-bg-surface);border:1px solid var(--rs-border-strong); + border-radius:16px;padding:28px;width:340px;max-width:90vw; + box-shadow:0 16px 48px rgba(0,0,0,0.4);text-align:center; + font-family:system-ui,-apple-system,sans-serif; + `; + + card.innerHTML = ` +
${savedEmoji}
+
Join ${this.esc(slug)}
+
Choose your avatar and display name
+
+ ${EMOJIS.map(e => ``).join("")} +
+ + ${savedName ? `` : ""} + + + `; + + overlay.appendChild(card); + this.shadow.appendChild(overlay); + + let selectedEmoji = savedEmoji; + const preview = card.querySelector("#join-avatar-preview") as HTMLElement; + const nameInput = card.querySelector("#join-name-input") as HTMLInputElement; + + card.querySelectorAll(".join-emoji-opt").forEach(btn => { + btn.addEventListener("click", () => { + selectedEmoji = (btn as HTMLElement).dataset.e!; + preview.textContent = selectedEmoji; + card.querySelectorAll(".join-emoji-opt").forEach(b => { + const isSelected = (b as HTMLElement).dataset.e === selectedEmoji; + (b as HTMLElement).style.borderColor = isSelected ? "#4f46e5" : "transparent"; + (b as HTMLElement).style.background = isSelected ? "#4f46e520" : "var(--rs-bg-surface-sunken)"; + }); + }); + }); + + const doJoin = (name: string, emoji: string) => { + if (!name.trim()) { nameInput.style.borderColor = "#ef4444"; return; } + this.userName = name.trim(); + this.userEmoji = emoji; + localStorage.setItem("rmaps_user", JSON.stringify({ + id: this.participantId, name: this.userName, + emoji: this.userEmoji, color: this.userColor, + })); + overlay.remove(); + this.joinRoom(this.pendingRoomSlug); + }; + + card.querySelector("#join-quick-btn")?.addEventListener("click", () => doJoin(savedName, savedEmoji)); + card.querySelector("#join-submit-btn")?.addEventListener("click", () => doJoin(nameInput.value, selectedEmoji)); + nameInput.addEventListener("keydown", (e) => { if (e.key === "Enter") doJoin(nameInput.value, selectedEmoji); }); + card.querySelector("#join-cancel-btn")?.addEventListener("click", () => overlay.remove()); + overlay.addEventListener("click", (e) => { if (e.target === overlay) overlay.remove(); }); + + nameInput.focus(); } // ─── Demo mode ─────────────────────────────────────────────── @@ -1071,7 +1163,10 @@ class FolkMapViewer extends HTMLElement { // ─── Room mode: join / leave / create ──────────────────────── private joinRoom(slug: string) { - if (!this.ensureUserProfile()) return; + if (!this.ensureUserProfile()) { + this.showJoinForm(slug); + return; + } this.room = slug; this.view = "map"; this.render(); @@ -1110,10 +1205,57 @@ class FolkMapViewer extends HTMLElement { private createRoom() { if (!requireAuth("create map room")) return; - const name = prompt("Room name (slug):"); - if (!name?.trim()) return; - const slug = name.trim().toLowerCase().replace(/[^a-z0-9-]/g, "-"); - this.joinRoom(slug); + this.showCreateRoomForm(); + } + + private showCreateRoomForm() { + const overlay = document.createElement("div"); + overlay.id = "create-room-overlay"; + overlay.style.cssText = ` + position:fixed;inset:0;z-index:50;background:rgba(0,0,0,0.6); + display:flex;align-items:center;justify-content:center;backdrop-filter:blur(4px); + `; + const card = document.createElement("div"); + card.style.cssText = ` + background:var(--rs-bg-surface);border:1px solid var(--rs-border-strong); + border-radius:16px;padding:28px;width:340px;max-width:90vw; + box-shadow:0 16px 48px rgba(0,0,0,0.4);text-align:center; + font-family:system-ui,-apple-system,sans-serif; + `; + card.innerHTML = ` +
🌐
+
Create Room
+
Enter a name for your new map room
+ + + + `; + overlay.appendChild(card); + this.shadow.appendChild(overlay); + + const input = card.querySelector("#room-name-input") as HTMLInputElement; + const doCreate = () => { + const name = input.value.trim(); + if (!name) { input.style.borderColor = "#ef4444"; return; } + const slug = name.toLowerCase().replace(/[^a-z0-9-]/g, "-"); + overlay.remove(); + this.joinRoom(slug); + }; + card.querySelector("#room-create-btn")?.addEventListener("click", doCreate); + input.addEventListener("keydown", (e) => { if (e.key === "Enter") doCreate(); }); + card.querySelector("#room-cancel-btn")?.addEventListener("click", () => overlay.remove()); + overlay.addEventListener("click", (e) => { if (e.target === overlay) overlay.remove(); }); + input.focus(); } private leaveRoom() { @@ -1258,6 +1400,7 @@ class FolkMapViewer extends HTMLElement { }); saveRoomVisit(this.room, this.room); + try { localStorage.setItem("rmaps_last_room", this.room); } catch {} // Listen for SW-forwarded location request pushes if ("serviceWorker" in navigator) { @@ -1379,6 +1522,21 @@ class FolkMapViewer extends HTMLElement { emojiSpan.className = "marker-emoji"; emojiSpan.textContent = p.emoji; el.appendChild(emojiSpan); + + // Status dot overlay (bottom-right corner) + const statusDot = document.createElement("div"); + statusDot.className = "marker-status-dot"; + const dotColor = isStale ? "#6b7280" : ( + p.status === "online" ? "#22c55e" : p.status === "away" ? "#f59e0b" : + p.status === "ghost" ? "#64748b" : "#ef4444" + ); + statusDot.style.cssText = ` + position:absolute;bottom:-1px;right:-1px; + width:10px;height:10px;border-radius:50%; + background:${dotColor};border:2px solid ${markerBg}; + box-shadow:0 0 6px ${dotColor}; + `; + el.appendChild(statusDot); } // Staleness tooltip @@ -1477,6 +1635,13 @@ class FolkMapViewer extends HTMLElement { this.indoorView.updateParticipants(state.participants); } + // Update participant counts (header + mobile) + const pCount = String(Object.keys(state.participants).length); + const headerCount = this.shadow.getElementById("header-participant-count"); + if (headerCount) headerCount.textContent = pCount; + const mobileCount = this.shadow.getElementById("mobile-friends-count"); + if (mobileCount) mobileCount.textContent = pCount; + // Update participant list sidebar this.updateParticipantList(state); } @@ -1508,11 +1673,12 @@ class FolkMapViewer extends HTMLElement { const ageSec = Math.floor(ageMs / 1000); ageLabel = ageSec < 60 ? `${ageSec}s ago` : ageSec < 3600 ? `${Math.floor(ageSec / 60)}m ago` : "stale"; } + const glowColor = isStale ? "#6b7280" : statusColor; return ` -
-
- ${this.esc(p.emoji)} - +
+
+ ${this.esc(p.emoji)} +
${this.esc(p.name)}
@@ -1521,8 +1687,8 @@ class FolkMapViewer extends HTMLElement { ${distLabel ? ` \u2022 ${distLabel}` : ""}
- ${p.id !== this.participantId && p.location ? `` : ""} - ${p.id !== this.participantId ? `` : ""} + ${p.id !== this.participantId && p.location ? `` : ""} + ${p.id !== this.participantId ? `` : ""}
`; }).join(""); } @@ -1562,29 +1728,33 @@ class FolkMapViewer extends HTMLElement { const list = this.shadow.getElementById("participant-list"); const mobileList = this.shadow.getElementById("participant-list-mobile"); + const count = Object.keys(state.participants).length; const html = this.buildParticipantHTML(state); + const headerHTML = ` +
+ Friends (${count}) + +
+ `; const footerHTML = `
- - - + +
`; // Desktop sidebar if (list) { - list.innerHTML = html + footerHTML; + list.innerHTML = headerHTML + html + footerHTML; this.attachParticipantListeners(list); } // Mobile bottom sheet if (mobileList) { - mobileList.innerHTML = html; + mobileList.innerHTML = html + footerHTML; this.attachParticipantListeners(mobileList); - // Update sheet header count - const header = this.shadow.querySelector(".sheet-header span:first-child"); - const count = Object.keys(state.participants).length; - if (header) header.textContent = `Participants (${count})`; + const sheetCount = this.shadow.getElementById("sheet-participant-count"); + if (sheetCount) sheetCount.textContent = String(count); } } @@ -1622,6 +1792,16 @@ class FolkMapViewer extends HTMLElement { el.title = `${p.name} - ${p.status} (${ageLabel})`; const arrow = el.querySelector(".heading-arrow") as HTMLElement | null; if (arrow) arrow.style.borderBottomColor = isStale ? "#6b7280" : p.color; + // Update status dot + const statusDot = el.querySelector(".marker-status-dot") as HTMLElement | null; + if (statusDot) { + const dotColor = isStale ? "#6b7280" : ( + p.status === "online" ? "#22c55e" : p.status === "away" ? "#f59e0b" : + p.status === "ghost" ? "#64748b" : "#ef4444" + ); + statusDot.style.background = dotColor; + statusDot.style.boxShadow = `0 0 6px ${dotColor}`; + } } } @@ -1786,19 +1966,35 @@ class FolkMapViewer extends HTMLElement { private updateShareButton() { const btn = this.shadow.getElementById("share-location"); - if (!btn) return; - if (this.privacySettings.precision === "hidden") { - btn.textContent = "\u{1F47B} Hidden"; - btn.classList.remove("sharing"); - btn.classList.add("ghost"); - } else if (this.sharingLocation) { - btn.textContent = "\u{1F4CD} Stop Sharing"; - btn.classList.add("sharing"); - btn.classList.remove("ghost"); - } else { - btn.textContent = "\u{1F4CD} Share Location"; - btn.classList.remove("sharing"); - btn.classList.remove("ghost"); + if (btn) { + if (this.privacySettings.precision === "hidden") { + btn.textContent = "\u{1F47B} Hidden"; + btn.classList.remove("sharing"); + btn.classList.add("ghost"); + } else if (this.sharingLocation) { + btn.textContent = "\u{1F4CD} Stop Sharing"; + btn.classList.add("sharing"); + btn.classList.remove("ghost"); + } else { + btn.textContent = "\u{1F4CD} Share Location"; + btn.classList.remove("sharing"); + btn.classList.remove("ghost"); + } + } + + // Floating share button + const floatBtn = this.shadow.getElementById("map-share-float"); + if (floatBtn) { + if (this.privacySettings.precision === "hidden") { + floatBtn.innerHTML = "👻 Hidden"; + floatBtn.className = "map-share-float ghost"; + } else if (this.sharingLocation) { + floatBtn.innerHTML = "📍 Sharing"; + floatBtn.className = "map-share-float active"; + } else { + floatBtn.innerHTML = "📍 Share Location"; + floatBtn.className = "map-share-float"; + } } // Update permission indicator const permIndicator = this.shadow.getElementById("geo-perm-indicator"); @@ -1807,6 +2003,11 @@ class FolkMapViewer extends HTMLElement { permIndicator.style.background = colors[this.geoPermissionState] || "#64748b"; permIndicator.title = `Geolocation: ${this.geoPermissionState || "unknown"}`; } + // Update header share toggle + const headerToggle = this.shadow.getElementById("header-share-toggle"); + if (headerToggle) { + headerToggle.className = `map-header__share-toggle ${this.sharingLocation ? "active" : ""}`; + } // Also update mobile FAB this.updateMobileFab(); } @@ -2369,11 +2570,49 @@ class FolkMapViewer extends HTMLElement { :host { display: block; font-family: system-ui, -apple-system, sans-serif; color: var(--rs-text-primary); } * { box-sizing: border-box; } + /* Lobby nav */ .rapp-nav { display: flex; gap: 8px; margin-bottom: 16px; align-items: center; min-height: 36px; } .rapp-nav__back { padding: 4px 10px; border-radius: 6px; border: 1px solid var(--rs-border); background: transparent; color: var(--rs-text-secondary); cursor: pointer; font-size: 13px; } .rapp-nav__back:hover { color: var(--rs-text-primary); border-color: var(--rs-border-strong); } .rapp-nav__title { font-size: 15px; font-weight: 600; flex: 1; color: var(--rs-text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + /* Map room header — dark bar */ + .map-header { + display:flex;align-items:center;gap:8px;padding:8px 14px; + background:rgba(15,23,42,0.95);backdrop-filter:blur(8px); + border-radius:10px;margin-bottom:12px;min-height:44px; + border:1px solid rgba(255,255,255,0.08); + } + .map-header__left { display:flex;align-items:center;gap:6px;flex:1;min-width:0; } + .map-header__back { + background:none;border:none;color:#94a3b8;cursor:pointer; + font-size:16px;padding:4px 6px;border-radius:4px; + } + .map-header__back:hover { color:#e2e8f0; } + .map-header__logo { font-size:13px;font-weight:700;color:#e2e8f0;white-space:nowrap; } + .map-header__slug { font-size:12px;color:#64748b;font-family:monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap; } + .map-header__participants { + display:flex;align-items:center;gap:4px; + padding:4px 12px;border-radius:20px; + background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.12); + color:#e2e8f0;font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap; + } + .map-header__participants:hover { background:rgba(255,255,255,0.14); } + .map-header__right { display:flex;align-items:center;gap:4px; } + .map-header__icon-btn { + background:none;border:none;color:#94a3b8;cursor:pointer; + font-size:15px;padding:4px 6px;border-radius:4px; + } + .map-header__icon-btn:hover { color:#e2e8f0; } + .map-header__share-toggle { + width:32px;height:32px;border-radius:50%; + background:transparent;border:1px solid rgba(255,255,255,0.15); + color:#94a3b8;cursor:pointer;font-size:14px; + display:flex;align-items:center;justify-content:center;transition:all 0.2s; + } + .map-header__share-toggle.active { background:#10b981;border-color:#10b981;color:#fff; } + .map-header__share-toggle:hover { border-color:#10b981;color:#10b981; } + .status-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; } @@ -2448,8 +2687,21 @@ class FolkMapViewer extends HTMLElement { } .map-locate-fab:hover { border-color: #4285f4; color: #4285f4; } - /* Mobile FAB menu — hidden on desktop */ - .mobile-fab-container { display: none; } + /* Floating share-location button on map */ + .map-share-float { + position:absolute;bottom:20px;right:16px;z-index:6; + padding:10px 18px;border-radius:24px; + background:#fff;border:1px solid #e2e8f0; + color:#1e293b;font-weight:600;font-size:13px;cursor:pointer; + box-shadow:0 2px 10px rgba(0,0,0,0.15);transition:all 0.2s; + font-family:system-ui,-apple-system,sans-serif; + } + .map-share-float.active { background:#10b981;border-color:#10b981;color:#fff; } + .map-share-float.ghost { background:#8b5cf6;border-color:#8b5cf6;color:#fff; } + .map-share-float:hover { box-shadow:0 4px 16px rgba(0,0,0,0.2); } + + /* Mobile floating buttons — hidden on desktop */ + .mobile-float-btns { display: none; } .mobile-bottom-sheet { display: none; } @media (max-width: 768px) { @@ -2458,52 +2710,20 @@ class FolkMapViewer extends HTMLElement { .map-sidebar { display: none; } .controls { display: none; } #privacy-panel { display: none !important; } - .rapp-nav { position: absolute; top: 0; left: 0; right: 0; z-index: 7; background: var(--rs-bg-surface); border-bottom: 1px solid var(--rs-border); margin: 0; padding: 6px 12px; min-height: 48px; } + .map-header { position:absolute;top:0;left:0;right:0;z-index:7;margin:0;border-radius:0;border-bottom:1px solid rgba(255,255,255,0.08); } .map-locate-fab { bottom: 100px; } - /* Mobile FAB menu */ - .mobile-fab-container { - display: block; position: fixed; bottom: 24px; right: 16px; z-index: 8; + /* Mobile floating pill buttons */ + .mobile-float-btns { display:flex;position:fixed;bottom:16px;left:0;right:0;z-index:8;padding:0 16px;justify-content:space-between;pointer-events:none; } + .mobile-pill-btn { + padding:10px 16px;border-radius:24px;border:none; + background:rgba(15,23,42,0.92);color:#e2e8f0;font-weight:600; + font-size:13px;cursor:pointer;pointer-events:auto; + box-shadow:0 4px 16px rgba(0,0,0,0.3);backdrop-filter:blur(4px); + font-family:system-ui,-apple-system,sans-serif; } - .fab-main { - width: 52px; height: 52px; border-radius: 50%; - background: #4f46e5; border: none; color: #fff; cursor: pointer; - font-size: 22px; display: flex; align-items: center; justify-content: center; - box-shadow: 0 4px 16px rgba(79,70,229,0.5); transition: transform 0.2s; - } - .fab-main.open { transform: rotate(45deg); } - .fab-mini-list { - position: absolute; bottom: 62px; right: 2px; - display: flex; flex-direction: column-reverse; gap: 10px; - opacity: 0; pointer-events: none; transition: opacity 0.2s; - } - .fab-mini-list.open { opacity: 1; pointer-events: auto; } - .fab-mini { - display: flex; align-items: center; gap: 8px; flex-direction: row-reverse; - } - .fab-mini-btn { - width: 40px; height: 40px; border-radius: 50%; - border: 1px solid var(--rs-border); background: var(--rs-bg-surface); - color: var(--rs-text-primary); cursor: pointer; font-size: 16px; - display: flex; align-items: center; justify-content: center; - box-shadow: 0 2px 8px rgba(0,0,0,0.2); - transform: scale(0); transition: transform 0.15s; - } - .fab-mini-list.open .fab-mini-btn { transform: scale(1); } - .fab-mini-list.open .fab-mini:nth-child(1) .fab-mini-btn { transition-delay: 0s; } - .fab-mini-list.open .fab-mini:nth-child(2) .fab-mini-btn { transition-delay: 0.04s; } - .fab-mini-list.open .fab-mini:nth-child(3) .fab-mini-btn { transition-delay: 0.08s; } - .fab-mini-list.open .fab-mini:nth-child(4) .fab-mini-btn { transition-delay: 0.12s; } - .fab-mini-list.open .fab-mini:nth-child(5) .fab-mini-btn { transition-delay: 0.16s; } - .fab-mini-list.open .fab-mini:nth-child(6) .fab-mini-btn { transition-delay: 0.20s; } - .fab-mini-list.open .fab-mini:nth-child(7) .fab-mini-btn { transition-delay: 0.24s; } - .fab-mini-label { - font-size: 11px; background: var(--rs-bg-surface); color: var(--rs-text-secondary); - padding: 4px 8px; border-radius: 6px; white-space: nowrap; - box-shadow: 0 1px 4px rgba(0,0,0,0.15); border: 1px solid var(--rs-border); - } - .fab-mini-btn.sharing { border-color: #22c55e; color: #22c55e; } - .fab-mini-btn.ghost { border-color: #8b5cf6; color: #8b5cf6; } + .mobile-pill-btn:active { opacity:0.8; } + .map-share-float { bottom:70px; } /* Mobile bottom sheet */ .mobile-bottom-sheet { @@ -2611,6 +2831,37 @@ class FolkMapViewer extends HTMLElement { startTour() { this._tour.start(); } private renderLobby(): string { + const saved = JSON.parse(localStorage.getItem("rmaps_user") || "null"); + const lastRoom = localStorage.getItem("rmaps_last_room") || ""; + + const profileSection = ` +
+
Get Started
+
+
+ ${this.userEmoji} +
+
+ +
+
+ + ${lastRoom ? ` + + ` : ""} +
Profile is saved locally for quick rejoins
+
+ `; + const history = loadRoomHistory(); const historyCards = history.length > 0 ? `
Recent Rooms
@@ -2640,6 +2891,8 @@ class FolkMapViewer extends HTMLElement {
+ ${profileSection} + ${this.rooms.length > 0 ? `
Active Rooms
${this.rooms.map((r) => ` @@ -2661,12 +2914,24 @@ class FolkMapViewer extends HTMLElement { private renderMap(): string { return ` -
- ${this._history.canGoBack ? '' : ''} - \u{1F5FA} ${this.esc(this.room)} - - - +
+
+ ${this._history.canGoBack ? '' : ''} + + /${this.esc(this.room)} + +
+ +
+ + + + +
@@ -2705,54 +2970,30 @@ class FolkMapViewer extends HTMLElement { - -
-
-
- - ${this.privacySettings.ghostMode ? "Ghost" : this.sharingLocation ? "Stop" : "Share"} -
-
- - Privacy -
-
- - Drop Pin -
-
- - Share rMap -
-
- - Emoji -
-
- - Chat -
-
- - ${this.mapMode === "indoor" ? "Outdoor" : "Indoor"} -
-
- + +
+ +
+ + +
- Participants - tap to expand + Friends (0) +
- + `; } @@ -2761,6 +3002,60 @@ class FolkMapViewer extends HTMLElement { this.shadow.getElementById("btn-tour")?.addEventListener("click", () => this.startTour()); this.shadow.getElementById("create-room")?.addEventListener("click", () => this.createRoom()); + // Lobby profile section + const lobbyNameInput = this.shadow.getElementById("lobby-name-input") as HTMLInputElement; + if (lobbyNameInput) { + lobbyNameInput.addEventListener("change", () => { + const name = lobbyNameInput.value.trim(); + if (name) { + this.userName = name; + localStorage.setItem("rmaps_user", JSON.stringify({ + id: this.participantId, name: this.userName, + emoji: this.userEmoji, color: this.userColor, + })); + } + }); + } + const lobbyAvatar = this.shadow.getElementById("lobby-avatar-preview"); + const lobbyEmojiGrid = this.shadow.getElementById("lobby-emoji-grid"); + if (lobbyAvatar && lobbyEmojiGrid) { + lobbyAvatar.addEventListener("click", () => { + lobbyEmojiGrid.style.display = lobbyEmojiGrid.style.display === "none" ? "flex" : "none"; + }); + } + this.shadow.querySelectorAll("[data-lobby-emoji]").forEach(btn => { + btn.addEventListener("click", () => { + const emoji = (btn as HTMLElement).dataset.lobbyEmoji!; + this.userEmoji = emoji; + if (lobbyAvatar) lobbyAvatar.textContent = emoji; + this.shadow.querySelectorAll("[data-lobby-emoji]").forEach(b => { + const sel = (b as HTMLElement).dataset.lobbyEmoji === emoji; + (b as HTMLElement).style.borderColor = sel ? "#4f46e5" : "transparent"; + (b as HTMLElement).style.background = sel ? "#4f46e520" : "var(--rs-bg-surface-sunken)"; + }); + localStorage.setItem("rmaps_user", JSON.stringify({ + id: this.participantId, name: this.userName, + emoji: this.userEmoji, color: this.userColor, + })); + }); + }); + this.shadow.getElementById("lobby-rejoin-btn")?.addEventListener("click", () => { + const lastRoom = localStorage.getItem("rmaps_last_room"); + if (lastRoom) { + // Save name from input if provided + if (lobbyNameInput?.value.trim()) { + this.userName = lobbyNameInput.value.trim(); + localStorage.setItem("rmaps_user", JSON.stringify({ + id: this.participantId, name: this.userName, + emoji: this.userEmoji, color: this.userColor, + })); + } + this._history.push("lobby"); + this._history.push("map", { room: lastRoom }); + this.joinRoom(lastRoom); + } + }); + this.shadow.querySelectorAll("[data-room]").forEach((el) => { el.addEventListener("click", () => { const room = (el as HTMLElement).dataset.room!; @@ -2780,6 +3075,22 @@ class FolkMapViewer extends HTMLElement { this.toggleLocationSharing(); }); + // Header share toggle + this.shadow.getElementById("header-share-toggle")?.addEventListener("click", () => { + this.toggleLocationSharing(); + }); + + // Header participants toggle + this.shadow.getElementById("header-participants-toggle")?.addEventListener("click", () => { + // Desktop: toggle sidebar visibility, Mobile: toggle bottom sheet + const sidebar = this.shadow.querySelector(".map-sidebar") as HTMLElement; + if (sidebar && window.innerWidth > 768) { + sidebar.style.display = sidebar.style.display === "none" ? "" : "none"; + } + const sheet = this.shadow.getElementById("mobile-bottom-sheet"); + if (sheet) sheet.classList.toggle("expanded"); + }); + this.shadow.getElementById("drop-waypoint")?.addEventListener("click", () => { this.dropWaypoint(); }); @@ -2865,75 +3176,26 @@ class FolkMapViewer extends HTMLElement { this.fitToParticipants(); }); - // Mobile FAB menu - this.shadow.getElementById("fab-main")?.addEventListener("click", () => { - const main = this.shadow.getElementById("fab-main"); - const list = this.shadow.getElementById("fab-mini-list"); - if (main && list) { - const isOpen = list.classList.contains("open"); - list.classList.toggle("open"); - main.classList.toggle("open"); - // Close mobile privacy popup when closing FAB - if (isOpen) { - const popup = this.shadow.getElementById("mobile-privacy-popup"); - if (popup) popup.style.display = "none"; - } - } + // Mobile floating buttons + this.shadow.getElementById("mobile-friends-btn")?.addEventListener("click", () => { + const sheet = this.shadow.getElementById("mobile-bottom-sheet"); + if (sheet) sheet.classList.toggle("expanded"); }); - this.shadow.getElementById("fab-share-loc")?.addEventListener("click", () => { - this.toggleLocationSharing(); - this.updateMobileFab(); - }); - - this.shadow.getElementById("fab-privacy")?.addEventListener("click", () => { - const popup = this.shadow.getElementById("mobile-privacy-popup"); - if (popup) { - const isVisible = popup.style.display !== "none"; - popup.style.display = isVisible ? "none" : "block"; - if (!isVisible) this.renderPrivacyPanel(popup); - } - }); - - this.shadow.getElementById("fab-drop-pin")?.addEventListener("click", () => { - this.closeMobileFab(); - this.dropWaypoint(); - }); - - this.shadow.getElementById("fab-share-map")?.addEventListener("click", () => { - this.closeMobileFab(); + this.shadow.getElementById("mobile-qr-btn")?.addEventListener("click", () => { this.showShareModal = true; this.renderShareModal(); }); - this.shadow.getElementById("fab-emoji")?.addEventListener("click", () => { - this.showEmojiPicker = !this.showEmojiPicker; - this.updateEmojiButton(); + // Floating share-location button on map + this.shadow.getElementById("map-share-float")?.addEventListener("click", () => { + this.toggleLocationSharing(); }); - this.shadow.getElementById("fab-chat")?.addEventListener("click", () => { - this.closeMobileFab(); - // Toggle mobile bottom sheet to chat view - const sheet = this.shadow.getElementById("mobile-bottom-sheet"); - if (sheet) { - sheet.classList.add("expanded"); - const content = this.shadow.getElementById("participant-list-mobile"); - if (content && !content.querySelector("map-chat-panel")) { - this.mountChatPanel(content); - } - } - this.unreadCount = 0; - this.updateChatBadge(); - }); - - this.shadow.getElementById("fab-indoor")?.addEventListener("click", () => { - this.closeMobileFab(); - if (this.mapMode === "outdoor") { - const event = prompt("c3nav event code (e.g. 39c3):", "39c3"); - if (event?.trim()) this.switchToIndoor(event.trim()); - } else { - this.switchToOutdoor(); - } + // Sheet close button + this.shadow.getElementById("sheet-close-btn")?.addEventListener("click", () => { + const s = this.shadow.getElementById("mobile-bottom-sheet"); + if (s) s.classList.remove("expanded"); }); // Mobile bottom sheet @@ -2974,21 +3236,8 @@ class FolkMapViewer extends HTMLElement { }); } - private closeMobileFab() { - const main = this.shadow.getElementById("fab-main"); - const list = this.shadow.getElementById("fab-mini-list"); - if (main) main.classList.remove("open"); - if (list) list.classList.remove("open"); - const popup = this.shadow.getElementById("mobile-privacy-popup"); - if (popup) popup.style.display = "none"; - } - private updateMobileFab() { - const btn = this.shadow.getElementById("fab-share-loc"); - if (!btn) return; - btn.className = `fab-mini-btn ${this.sharingLocation ? "sharing" : ""} ${this.privacySettings.ghostMode ? "ghost" : ""}`; - const label = btn.parentElement?.querySelector(".fab-mini-label"); - if (label) label.textContent = this.privacySettings.ghostMode ? "Ghost" : this.sharingLocation ? "Stop" : "Share"; + // No-op — legacy FAB removed; share state updated via updateShareButton() } private goBack() { diff --git a/modules/rmaps/mod.ts b/modules/rmaps/mod.ts index 9e7b050..200d433 100644 --- a/modules/rmaps/mod.ts +++ b/modules/rmaps/mod.ts @@ -276,7 +276,7 @@ routes.get("/", (c) => { body: ``, scripts: ` - `, + `, styles: ``, })); }); @@ -295,7 +295,7 @@ routes.get("/:room", (c) => { body: ``, scripts: ` - `, + `, })); });