diff --git a/src/components/map/DistrictBlock.tsx b/src/components/map/DistrictBlock.tsx index 71ca3b5..c28e835 100644 --- a/src/components/map/DistrictBlock.tsx +++ b/src/components/map/DistrictBlock.tsx @@ -14,6 +14,23 @@ function seededUnit(seed: number) { return value - Math.floor(value); } +const HERO_LABEL_OFFSETS: Record = { + "21010": [0.05, -0.12], + "21020": [-0.16, -0.08], + "21030": [0.08, 0.12], + "21040": [0.18, -0.05], + "21050": [0.12, 0.08], + "21060": [0.02, 0.12], + "21070": [0.16, -0.06], + "21080": [-0.12, 0.08], + "21090": [0.08, 0.04], + "21100": [-0.04, -0.12], + "21120": [1.45, 1.6], + "21130": [0.12, -0.04], + "21140": [0.2, -0.06], + "21150": [-0.12, 0.02], +}; + function makeGeo(polygons: Polygon[], bbox: BBox, heroStyle: boolean): THREE.ExtrudeGeometry { const shapes = polygons.map((poly) => { const shape = new THREE.Shape( @@ -74,6 +91,7 @@ export function DistrictBlock({ index, phase, heroStyle = false, + showHeroLabel = false, onAssemblyComplete, onSelect, selected, @@ -86,6 +104,7 @@ export function DistrictBlock({ index: number; phase: string; heroStyle?: boolean; + showHeroLabel?: boolean; onAssemblyComplete?: () => void; onSelect: (code: string, name: string) => void; selected: boolean; @@ -134,6 +153,7 @@ export function DistrictBlock({ const nextColor = new THREE.Color(selected ? "#3b82f6" : color); return hovered ? nextColor.lerp(new THREE.Color("#8be6ff"), 0.24) : nextColor; }, [color, hovered, selected]); + const [heroLabelDx, heroLabelDy] = HERO_LABEL_OFFSETS[district.code] ?? [0, 0]; useCursor(heroStyle && hovered); @@ -299,6 +319,45 @@ export function DistrictBlock({ )} + + {heroStyle && ( + + + + )} ); } diff --git a/src/components/map/IntroScene2.module.css b/src/components/map/IntroScene2.module.css index 6284a4f..01f3af9 100644 --- a/src/components/map/IntroScene2.module.css +++ b/src/components/map/IntroScene2.module.css @@ -153,29 +153,77 @@ } .routeShadow, +.routeRail, .routePath { fill: none; stroke-linecap: round; + stroke-linejoin: round; } .routeShadow { - stroke: rgb(14 63 115 / 20%); + stroke: rgb(14 63 115 / 24%); + stroke-width: 11; + transform: translateY(3px); +} + +.routeRail, +.routePath { + stroke-dasharray: 1; + stroke-dashoffset: 1; +} + +.routeRail { + stroke: #2e7df2; stroke-width: 9; - transform: translateY(4px); + filter: drop-shadow(0 2px 5px rgb(25 99 168 / 24%)); } .routePath { stroke: white; - stroke-width: 6; - stroke-dasharray: 390; - stroke-dashoffset: 390; - filter: drop-shadow(0 2px 5px rgb(25 99 168 / 25%)); + stroke-width: 5.5; } +.routeVisible .routeRail, .routeVisible .routePath { animation: drawRoute 850ms ease-out 120ms forwards; } +.routeStop { + fill: white; + stroke: #56a5ee; + stroke-width: 2.5; + opacity: 0; + transform-box: fill-box; + transform-origin: center; +} + +.routePin { + opacity: 0; + transform-box: fill-box; + transform-origin: center; +} + +.routePin path { + fill: #2e7df2; + stroke: white; + stroke-width: 3.5; + paint-order: stroke; + filter: drop-shadow(0 3px 4px rgb(14 63 115 / 30%)); +} + +.routePin circle { + fill: white; +} + +.routeVisible .routeStop, +.routeVisible .routePin { + animation: popIn 420ms cubic-bezier(0.34, 1.56, 0.64, 1) 760ms forwards; +} + +.routeVisible .routePinEnd { + animation-delay: 920ms; +} + .routeDot { fill: #2e7df2; stroke: white; @@ -462,6 +510,9 @@ } .routePath, + .routeRail, + .routeStop, + .routePin, .routeDot, .busBadge { animation-duration: 1ms !important; diff --git a/src/components/map/IntroScene2.tsx b/src/components/map/IntroScene2.tsx index af8888a..382e7dd 100644 --- a/src/components/map/IntroScene2.tsx +++ b/src/components/map/IntroScene2.tsx @@ -264,6 +264,7 @@ export default function IntroScene2({ onEnter }: { onEnter: () => void }) { phase={scenePhase} colorPalette={LOGIN_DISTRICT_COLORS} heroStyle + showHeroLabels={panelVisible} onAssemblyComplete={handleAssemblyComplete} onSelect={() => {}} selected={null} @@ -279,16 +280,47 @@ export default function IntroScene2({ onEnter }: { onEnter: () => void }) { viewBox="0 0 360 170" aria-hidden="true" > - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/map/Scene.tsx b/src/components/map/Scene.tsx index 89efd09..09b3f21 100644 --- a/src/components/map/Scene.tsx +++ b/src/components/map/Scene.tsx @@ -17,6 +17,7 @@ export function Scene({ controlsRef, colorPalette, heroStyle = false, + showHeroLabels = false, onAssemblyComplete, onTap, tappedName, @@ -29,6 +30,7 @@ export function Scene({ controlsRef: React.RefObject; colorPalette?: readonly string[]; heroStyle?: boolean; + showHeroLabels?: boolean; onAssemblyComplete?: () => void; onTap?: (name: string) => void; tappedName?: string | null; @@ -85,6 +87,7 @@ export function Scene({ index={i} phase={phase} heroStyle={heroStyle} + showHeroLabel={showHeroLabels} onAssemblyComplete={i === districts.length - 1 ? onAssemblyComplete : undefined} onSelect={onSelect} selected={selected === d.code}