feat: add community discussion floating pill widget
Floating pill badge (bottom-right) with pulsing dot, "Join the Discussion" text, and CTA linking to GitHub Discussions #94. Shows 15s after initial data load. "Don't show again" persists dismissal to localStorage (wm-community-dismissed).
This commit is contained in:
@@ -37,6 +37,7 @@ import { buildMapUrl, debounce, loadFromStorage, parseMapUrlState, saveToStorage
|
||||
import { reverseGeocode } from '@/utils/reverse-geocode';
|
||||
import { CountryBriefPage } from '@/components/CountryBriefPage';
|
||||
import { maybeShowDownloadBanner } from '@/components/DownloadBanner';
|
||||
import { maybeShowCommunityWidget } from '@/components/CommunityWidget';
|
||||
import { CountryTimeline, type TimelineEvent } from '@/components/CountryTimeline';
|
||||
import { escapeHtml } from '@/utils/sanitize';
|
||||
import type { ParsedMapUrlState } from '@/utils';
|
||||
@@ -3200,6 +3201,7 @@ export class App {
|
||||
this.allNews = collectedNews;
|
||||
this.initialLoadComplete = true;
|
||||
maybeShowDownloadBanner();
|
||||
maybeShowCommunityWidget();
|
||||
// Temporal baseline: report news volume
|
||||
updateAndCheck([
|
||||
{ type: 'news', region: 'global', count: collectedNews.length },
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
const STORAGE_KEY = 'wm-community-dismissed';
|
||||
const SHOW_DELAY_MS = 15_000;
|
||||
let scheduled = false;
|
||||
|
||||
export function maybeShowCommunityWidget(): void {
|
||||
if (scheduled) return;
|
||||
if (localStorage.getItem(STORAGE_KEY)) return;
|
||||
|
||||
scheduled = true;
|
||||
setTimeout(() => {
|
||||
if (localStorage.getItem(STORAGE_KEY)) return;
|
||||
const el = build();
|
||||
document.body.appendChild(el);
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => el.classList.add('cw-show'));
|
||||
});
|
||||
}, SHOW_DELAY_MS);
|
||||
}
|
||||
|
||||
function dismiss(el: HTMLElement): void {
|
||||
localStorage.setItem(STORAGE_KEY, '1');
|
||||
el.classList.remove('cw-show');
|
||||
el.addEventListener('transitionend', () => el.remove(), { once: true });
|
||||
}
|
||||
|
||||
function close(el: HTMLElement): void {
|
||||
el.classList.remove('cw-show');
|
||||
el.addEventListener('transitionend', () => el.remove(), { once: true });
|
||||
}
|
||||
|
||||
function build(): HTMLElement {
|
||||
const wrap = document.createElement('div');
|
||||
wrap.className = 'cw-pill-wrap';
|
||||
|
||||
wrap.innerHTML = `
|
||||
<div class="cw-pill">
|
||||
<div class="cw-dot"></div>
|
||||
<span class="cw-text">Join the Discussion</span>
|
||||
<a class="cw-cta" href="https://github.com/koala73/worldmonitor/discussions/94" target="_blank" rel="noopener">Open</a>
|
||||
<button class="cw-close" aria-label="Close">×</button>
|
||||
</div>
|
||||
<button class="cw-dismiss">Don't show again</button>
|
||||
`;
|
||||
|
||||
wrap.querySelector('.cw-close')!.addEventListener('click', () => close(wrap));
|
||||
wrap.querySelector('.cw-dismiss')!.addEventListener('click', () => dismiss(wrap));
|
||||
|
||||
return wrap;
|
||||
}
|
||||
+93
-24
@@ -1,3 +1,6 @@
|
||||
@import './lang-switcher.css';
|
||||
@import './rtl-overrides.css';
|
||||
|
||||
/* ============================================================
|
||||
Theme Colors — overridden by [data-theme="light"] below
|
||||
============================================================ */
|
||||
@@ -49,8 +52,14 @@
|
||||
--map-grid: #0a2a20;
|
||||
--map-country: #0a2018;
|
||||
--map-stroke: #0f5040;
|
||||
|
||||
/* Font stack */
|
||||
--font-body: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
|
||||
}
|
||||
|
||||
[dir="rtl"] { --font-body: 'Geeza Pro', 'SF Arabic', 'Tahoma', system-ui, sans-serif; }
|
||||
:lang(zh-CN), :lang(zh) { --font-body: 'PingFang SC', 'Microsoft YaHei', 'Noto Sans SC', system-ui, sans-serif; }
|
||||
|
||||
/* ============================================================
|
||||
Semantic Colors — dark-mode defaults, light overrides below
|
||||
============================================================ */
|
||||
@@ -169,7 +178,7 @@ html {
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
|
||||
font-family: var(--font-body);
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
background: var(--bg);
|
||||
@@ -9200,29 +9209,6 @@ a.prediction-link:hover {
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
.intel-findings-context-menu {
|
||||
position: fixed;
|
||||
z-index: 10000;
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
|
||||
padding: 4px 0;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.context-menu-item {
|
||||
padding: 8px 14px;
|
||||
font-size: 12px;
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.context-menu-item:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
/* ===== Tech Events Panel ===== */
|
||||
.tech-events-panel {
|
||||
display: flex;
|
||||
@@ -12417,3 +12403,86 @@ body.has-critical-banner .panels-grid {
|
||||
.cb-timeline-section { display: none; }
|
||||
body > *:not(.country-brief-overlay) { display: none !important; }
|
||||
}
|
||||
|
||||
/* Community Widget — Floating Pill */
|
||||
.cw-pill-wrap {
|
||||
position: fixed;
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
z-index: 9000;
|
||||
opacity: 0;
|
||||
transform: translateY(10px) scale(0.95);
|
||||
transition: opacity 0.35s ease, transform 0.35s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
pointer-events: none;
|
||||
}
|
||||
.cw-pill-wrap.cw-show {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
pointer-events: auto;
|
||||
}
|
||||
.cw-pill {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
background: var(--panel-bg, #141414);
|
||||
border: 1px solid var(--border, #2a2a2a);
|
||||
border-radius: 28px;
|
||||
padding: 8px 8px 8px 16px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
.cw-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: hsl(229, 48%, 55%);
|
||||
flex-shrink: 0;
|
||||
animation: cw-pulse 2s ease-in-out infinite;
|
||||
}
|
||||
@keyframes cw-pulse {
|
||||
0%, 100% { opacity: 1; box-shadow: 0 0 0 0 hsla(229, 48%, 55%, 0.5); }
|
||||
50% { opacity: 0.6; box-shadow: 0 0 0 4px hsla(229, 48%, 55%, 0); }
|
||||
}
|
||||
.cw-text {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text, #e8e8e8);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.cw-cta {
|
||||
padding: 6px 14px;
|
||||
background: hsl(229, 48%, 55%);
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.cw-cta:hover { background: hsl(229, 48%, 62%); }
|
||||
.cw-close {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-ghost, #444);
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
padding: 4px 6px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
line-height: 1;
|
||||
transition: color 0.15s, background 0.15s;
|
||||
}
|
||||
.cw-close:hover { color: var(--text-dim, #888); background: var(--overlay-light, rgba(255,255,255,0.05)); }
|
||||
.cw-dismiss {
|
||||
display: block;
|
||||
margin: 6px 8px 0 auto;
|
||||
font-size: 10px;
|
||||
color: var(--text-ghost, #444);
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: none;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
.cw-dismiss:hover { color: var(--text-dim, #888); }
|
||||
|
||||
Reference in New Issue
Block a user