feat(midsummer): dancing maypole pinned to map centre
This commit is contained in:
parent
2fb6fd2f3e
commit
da0cc79def
3 changed files with 94 additions and 0 deletions
|
|
@ -4,6 +4,7 @@ import { PlayerDots } from './PlayerDots';
|
||||||
import { TrailsSVG } from './TrailsSVG';
|
import { TrailsSVG } from './TrailsSVG';
|
||||||
import { HeatmapCanvas } from './HeatmapCanvas';
|
import { HeatmapCanvas } from './HeatmapCanvas';
|
||||||
import { PortalMarkers } from './PortalMarkers';
|
import { PortalMarkers } from './PortalMarkers';
|
||||||
|
import { Maypole } from '../midsummer/Maypole';
|
||||||
import type { TelemetrySnapshot } from '../../types';
|
import type { TelemetrySnapshot } from '../../types';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -143,6 +144,7 @@ export const MapView: React.FC<Props> = ({ players, getColor, onSelectPlayer, sh
|
||||||
selectedPlayer={selectedPlayer}
|
selectedPlayer={selectedPlayer}
|
||||||
/>
|
/>
|
||||||
<PortalMarkers imgW={imgSize.w} imgH={imgSize.h} enabled={showPortals} />
|
<PortalMarkers imgW={imgSize.w} imgH={imgSize.h} enabled={showPortals} />
|
||||||
|
<Maypole imgW={imgSize.w} imgH={imgSize.h} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
41
frontend/src/components/midsummer/Maypole.tsx
Normal file
41
frontend/src/components/midsummer/Maypole.tsx
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { useMidsummer } from '../../hooks/useMidsummer';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
imgW: number;
|
||||||
|
imgH: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kept small for perf — these orbit the pole via one CSS animation.
|
||||||
|
const FROG_COUNT = 6;
|
||||||
|
// Default: dead centre of the Dereth map image. To plant at a landmark,
|
||||||
|
// import { worldToPx } from '../../utils/coordinates' and compute from
|
||||||
|
// world coords instead.
|
||||||
|
const center = (imgW: number, imgH: number) => ({ x: imgW / 2, y: imgH / 2 });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Midsommarstång planted inside the map's pan/zoom group, so it scales and
|
||||||
|
* pans with the world automatically. Carries its own ring of dancing frogs
|
||||||
|
* (one CSS rotation) so the spectacle is independent of who is online.
|
||||||
|
*/
|
||||||
|
export const Maypole: React.FC<Props> = ({ imgW, imgH }) => {
|
||||||
|
const { enabled } = useMidsummer();
|
||||||
|
if (!enabled || imgW === 0) return null;
|
||||||
|
const { x, y } = center(imgW, imgH);
|
||||||
|
return (
|
||||||
|
<div className="ms-maypole" style={{ left: x, top: y }} aria-hidden="true">
|
||||||
|
<div className="ms-maypole-pole" />
|
||||||
|
<div className="ms-maypole-ring">
|
||||||
|
{Array.from({ length: FROG_COUNT }).map((_, i) => (
|
||||||
|
<span
|
||||||
|
key={i}
|
||||||
|
className="ms-frog"
|
||||||
|
style={{ transform: `rotate(${(360 / FROG_COUNT) * i}deg) translateY(-40px)` }}
|
||||||
|
>
|
||||||
|
🐸
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -31,3 +31,54 @@
|
||||||
:root[data-midsummer] .ml-btn {
|
:root[data-midsummer] .ml-btn {
|
||||||
border-color: #2c6e36;
|
border-color: #2c6e36;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ms-maypole {
|
||||||
|
position: absolute;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 6;
|
||||||
|
}
|
||||||
|
.ms-maypole-pole {
|
||||||
|
position: absolute;
|
||||||
|
left: -2px;
|
||||||
|
top: -64px;
|
||||||
|
width: 4px;
|
||||||
|
height: 70px;
|
||||||
|
background: #6b4f2a;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
.ms-maypole-pole::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
left: -16px;
|
||||||
|
width: 36px;
|
||||||
|
height: 4px;
|
||||||
|
background: #3b6d11;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
.ms-maypole-pole::after {
|
||||||
|
content: '🌼';
|
||||||
|
position: absolute;
|
||||||
|
top: -16px;
|
||||||
|
left: -8px;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.ms-maypole-ring {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: -30px;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
animation: ms-spin 12s linear infinite;
|
||||||
|
}
|
||||||
|
.ms-frog {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
@keyframes ms-spin { to { transform: rotate(360deg); } }
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.ms-maypole-ring { animation: none; }
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue