feat(v2): Phases 2-6 — trails, heatmap, portals, windows, effects

Phase 2 — Map overlays:
- TrailsSVG: SVG polylines per character from /trails, polled 2s
- HeatmapCanvas: canvas radial gradients from /spawns/heatmap
- PortalMarkers: emoji markers from /portals
- Sidebar toggles for heatmap and portals

Phase 3 — Draggable windows:
- WindowManagerContext: z-index stack for open windows
- DraggableWindow: generic shell with drag-header, close btn, z-stack
- ChatWindow: color-coded messages + input form (1000 msg buffer)
- CharacterWindow: combat stats with monster damage table
- InventoryWindow: item table with material/set/AL/dmg/workmanship
- WindowRenderer: reads context, renders all open windows
- Action buttons (Chat/Stats/Inv/Char/Radar) now open windows

Phase 4 — Window types share same DraggableWindow shell with
character-specific content. Combat stats and inventory via API.

Phase 5 — Effects:
- RareNotification: slide-in/slide-out banner with gold border
- Fireworks: 30-particle explosion with CSS custom property animation
- Notification queue with 6s display + 0.5s exit animation

Phase 6 — Polish:
- Window header uses modern blue gradient (not solid purple)
- Chat uses monospace font
- All overlay layers properly stacked (heatmap → trails → dots → portals)
- Mobile: sidebar stacks above map at 768px breakpoint
- Chat messages tracked per-character in useLiveData

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-12 15:58:58 +02:00
parent 183d662bb9
commit de7b547349
20 changed files with 1040 additions and 193 deletions

View file

@ -373,6 +373,235 @@
font-variant-numeric: tabular-nums;
}
/* ── Map toggles ──────────────────────────────────────── */
.ml-toggles {
display: flex;
gap: 12px;
margin-bottom: 8px;
font-size: 0.72rem;
}
.ml-toggle-label {
display: flex;
align-items: center;
gap: 4px;
color: #aaa;
cursor: pointer;
}
.ml-toggle-label input { accent-color: #4488ff; }
/* ── Trail SVG overlay ────────────────────────────────── */
.ml-trails-svg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
/* ── Heatmap canvas overlay ───────────────────────────── */
.ml-heatmap-canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
opacity: 0.8;
}
/* ── Portal markers ───────────────────────────────────── */
.ml-portals-layer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.ml-portal-icon {
position: absolute;
width: 6px;
height: 6px;
transform: translate(-50%, -50%);
pointer-events: all;
cursor: help;
}
.ml-portal-icon::before {
content: '🌀';
font-size: 10px;
position: absolute;
transform: translate(-50%, -50%);
}
/* ── Draggable windows ────────────────────────────────── */
.ml-window {
position: fixed;
background: #1a1a1a;
border: 1px solid #444;
border-radius: 6px;
display: flex;
flex-direction: column;
overflow: hidden;
box-shadow: 0 8px 32px rgba(0,0,0,0.5);
}
.ml-window-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 6px 12px;
background: linear-gradient(135deg, #2a3a5a, #1a2a40);
cursor: move;
user-select: none;
border-bottom: 1px solid #334;
}
.ml-window-title {
font-size: 0.8rem;
font-weight: 600;
color: #aaccff;
}
.ml-window-close {
background: none;
border: none;
color: #888;
font-size: 1.1rem;
cursor: pointer;
line-height: 1;
padding: 0 4px;
}
.ml-window-close:hover { color: #f66; }
.ml-window-content {
flex: 1;
overflow: auto;
display: flex;
flex-direction: column;
}
/* ── Chat window ──────────────────────────────────────── */
.ml-chat-messages {
flex: 1;
overflow-y: auto;
padding: 6px 10px;
font-size: 0.75rem;
font-family: 'Consolas', 'Courier New', monospace;
line-height: 1.4;
}
.ml-chat-line {
word-break: break-word;
}
.ml-chat-form {
display: flex;
border-top: 1px solid #333;
padding: 4px;
}
.ml-chat-input {
flex: 1;
background: #222;
color: #eee;
border: 1px solid #444;
border-radius: 3px;
padding: 4px 8px;
font-size: 0.78rem;
outline: none;
}
.ml-chat-input:focus { border-color: #4488ff; }
.ml-chat-input::placeholder { color: #666; }
/* ── Rare notifications ───────────────────────────────── */
.ml-rare-notifications {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 99999;
display: flex;
flex-direction: column;
gap: 8px;
pointer-events: none;
}
.ml-rare-notif {
background: linear-gradient(135deg, #1a0a2e, #2a1040);
border: 2px solid #ffcc00;
border-radius: 8px;
padding: 16px 32px;
text-align: center;
animation: ml-notif-in 0.5s ease-out;
box-shadow: 0 0 40px rgba(255, 204, 0, 0.3);
}
.ml-rare-notif.exiting {
animation: ml-notif-out 0.5s ease-in forwards;
}
.ml-rare-notif-title {
font-size: 1.4rem;
font-weight: 800;
color: #ffcc00;
text-shadow: 0 0 20px rgba(255, 204, 0, 0.5);
margin-bottom: 4px;
}
.ml-rare-notif-name {
font-size: 1.1rem;
font-weight: 600;
color: #fff;
margin-bottom: 4px;
}
.ml-rare-notif-by {
font-size: 0.75rem;
color: #888;
}
.ml-rare-notif-char {
font-size: 1rem;
font-weight: 700;
color: #ffcc00;
}
@keyframes ml-notif-in {
from { transform: translateY(-40px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
@keyframes ml-notif-out {
to { transform: translateY(-60px); opacity: 0; }
}
/* ── Fireworks ────────────────────────────────────────── */
.ml-fireworks {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 99998;
}
.ml-firework-particle {
position: absolute;
width: 6px;
height: 6px;
border-radius: 50%;
animation: ml-particle 2s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
}
@keyframes ml-particle {
0% { transform: translate(0, 0) scale(1); opacity: 1; }
100% { transform: translate(var(--dx), var(--dy)) scale(0); opacity: 0; }
}
/* ── Mobile ───────────────────────────────────────────── */
@media (max-width: 768px) {
.ml-layout { flex-direction: column; }