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 = `
+
-
-
${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}
+
+
+
+
+
+
+ ${EMOJIS.map(e => ``).join("")}
+
+ ${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 ? `