File size: 11,422 Bytes
85dca58 79cf005 85dca58 79cf005 85dca58 79cf005 85dca58 79cf005 85dca58 79cf005 85dca58 79cf005 85dca58 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 | /* Static SVG mock of the MapLibre map for 80 Pioneer St, Red Hook.
No network dependency. Encodes all four evidence-tier styles
per the brief: empirical solid + 0.4 fill, modeled solid + 0.25,
synthetic dashed + 0.25 with stripe, proxy graduated dots no fill.
*/
const RedHookMapMock = ({ activeLayers, queriedAddress }) => {
return (
<svg
viewBox="0 0 800 560"
width="100%"
height="100%"
role="application"
aria-label={`NYC flood-exposure map for ${queriedAddress}`}
style={{ display: "block", background: "#F2F2EE" }}
preserveAspectRatio="xMidYMid slice"
>
<defs>
{/* Diagonal stripe pattern for synthetic-prior fill */}
<pattern id="syn-stripe" width="6" height="6" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
<rect width="6" height="6" fill="rgba(42,111,168,0.18)"/>
<line x1="0" y1="0" x2="0" y2="6" stroke="#2A6FA8" strokeWidth="1" />
</pattern>
{/* Halo for label readability */}
<filter id="label-halo" x="-10%" y="-10%" width="120%" height="120%">
<feMorphology in="SourceAlpha" radius="1.5" operator="dilate" result="halo"/>
<feFlood floodColor="#FAFAF7"/>
<feComposite in2="halo" operator="in"/>
<feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
{/* ββ Basemap: Carto Positron register ββ */}
{/* Water (Erie Basin / Buttermilk Channel) */}
<path d="M 0 380 L 220 360 L 360 410 L 520 470 L 800 500 L 800 560 L 0 560 Z" fill="#DCE6EC"/>
<path d="M 540 0 L 580 0 L 600 90 L 640 180 L 700 240 L 800 280 L 800 0 Z" fill="#DCE6EC"/>
{/* Park (Coffey Park) */}
<rect x="380" y="240" width="90" height="60" fill="#E2E8DA"/>
{/* Parcels (reference layer #E5E5E5) */}
<g stroke="#C9C9C5" strokeWidth="0.5" fill="#FAFAF7">
{Array.from({ length: 8 }).map((_, r) =>
Array.from({ length: 14 }).map((_, c) => (
<rect key={`p-${r}-${c}`} x={60 + c * 50} y={60 + r * 38} width="46" height="34" />
))
)}
</g>
{/* Streets */}
<g stroke="#FAFAF7" strokeWidth="6" fill="none">
<path d="M 0 100 L 800 90"/>
<path d="M 0 200 L 800 190"/>
<path d="M 0 300 L 800 290"/>
<path d="M 60 0 L 50 380"/>
<path d="M 200 0 L 190 380"/>
<path d="M 340 0 L 330 380"/>
<path d="M 480 0 L 470 380"/>
<path d="M 620 0 L 610 380"/>
</g>
<g stroke="#C9C9C5" strokeWidth="6.5" fill="none" opacity="0.0"/>
{/* ββ Empirical layer: Sandy Inundation Zone ββ */}
{activeLayers.empirical && (
<g aria-label="Sandy Inundation Zone">
<path
d="M 0 380 L 220 360 L 360 410 L 520 470 L 800 500 L 800 560 L 0 560 Z
M 0 360 L 240 340 L 380 390 L 540 450 L 800 480
L 800 380 L 600 360 L 420 320 L 240 300 L 0 320 Z"
fill="rgba(11,83,148,0.40)"
stroke="#0B5394"
strokeWidth="1.5"
fillRule="evenodd"
/>
</g>
)}
{/* ββ Modeled layer: FEMA AE zone (solid line, 0.25 fill) ββ */}
{activeLayers.modeled && (
<g aria-label="FEMA Zone AE">
<path
d="M 40 340 L 280 320 L 440 360 L 600 420 L 800 440 L 800 560 L 0 560 L 0 350 Z"
fill="rgba(42,111,168,0.25)"
stroke="#2A6FA8"
strokeWidth="1.5"
strokeDasharray="0"
/>
</g>
)}
{/* ββ Synthetic-prior: dashed line + stripe pattern ββ */}
{activeLayers.synthetic && (
<g aria-label="Synthetic SAR backscatter (TerraMind, 2025-09-14)">
<path
d="M 100 380 L 260 360 L 380 390 L 480 420 L 600 440 L 720 460 L 720 500 L 100 500 Z"
fill="url(#syn-stripe)"
stroke="#2A6FA8"
strokeWidth="1.5"
strokeDasharray="4 3"
/>
</g>
)}
{/* ββ Proxy: 311 flood complaints (graduated dots, no fill) ββ */}
{activeLayers.proxy && (
<g aria-label="311 flood complaints, 2019-2025">
{[
[120, 320, 5], [180, 350, 8], [220, 280, 4], [280, 330, 11],
[340, 360, 6], [240, 240, 3], [380, 320, 9], [440, 350, 7],
[200, 220, 4], [160, 280, 5], [340, 240, 3], [420, 280, 4],
[500, 360, 6], [540, 400, 8], [180, 380, 5],
].map(([x, y, r], i) => (
<circle key={i} cx={x} cy={y} r={r} fill="none" stroke="#6B6B6B" strokeWidth="1.25" />
))}
</g>
)}
{/* ββ Asset pins for register specialists ββ */}
{/* Subway entrance , square */}
<g transform="translate(580 260)">
<rect x="-5" y="-5" width="10" height="10" fill="#1A1A1A" />
<text x="0" y="-9" fontSize="9" fontFamily="IBM Plex Sans" textAnchor="middle" fill="#1A1A1A" filter="url(#label-halo)">Smithβ9 St</text>
</g>
{/* NYCHA , open square */}
<g transform="translate(420 200)">
<rect x="-5" y="-5" width="10" height="10" fill="none" stroke="#1A1A1A" strokeWidth="1.5"/>
<text x="0" y="-9" fontSize="9" fontFamily="IBM Plex Sans" textAnchor="middle" fill="#1A1A1A" filter="url(#label-halo)">Red Hook Houses</text>
</g>
{/* School , cross */}
<g transform="translate(360 280)" stroke="#1A1A1A" strokeWidth="1.75" fill="none">
<line x1="-5" y1="0" x2="5" y2="0"/><line x1="0" y1="-5" x2="0" y2="5"/>
<text x="8" y="3" fontSize="9" fontFamily="IBM Plex Sans" fill="#1A1A1A" filter="url(#label-halo)">PS 15</text>
</g>
{/* Hospital , circle */}
<g transform="translate(680 160)">
<circle r="5" fill="#1A1A1A"/>
<text x="8" y="3" fontSize="9" fontFamily="IBM Plex Sans" fill="#1A1A1A" filter="url(#label-halo)">NYU Cobble Hill</text>
</g>
{/* ββ Queried-address pin (warm orange, dominant at z14+) ββ */}
<g transform="translate(300 320)">
<circle r="14" fill="rgba(209,124,0,0.20)"/>
<circle r="6" fill="#D17C00" stroke="#FAFAF7" strokeWidth="2"/>
<line x1="0" y1="6" x2="0" y2="22" stroke="#D17C00" strokeWidth="2"/>
<text x="0" y="-12" fontSize="11" fontWeight="600" fontFamily="IBM Plex Sans" textAnchor="middle" fill="#1A1A1A" filter="url(#label-halo)">80 Pioneer St</text>
</g>
{/* ββ Map labels (Imhof hierarchy: water italic, neighborhoods regular caps) ββ */}
<text x="640" y="490" fontSize="13" fontStyle="italic" fontFamily="IBM Plex Sans" fill="#5A7B8A" filter="url(#label-halo)">Buttermilk Channel</text>
<text x="120" y="450" fontSize="13" fontStyle="italic" fontFamily="IBM Plex Sans" fill="#5A7B8A" filter="url(#label-halo)">Erie Basin</text>
<text x="180" y="40" fontSize="14" fontFamily="IBM Plex Sans" fontWeight="500" letterSpacing="0.18em" fill="#4A4A4A" filter="url(#label-halo)">RED HOOK</text>
<text x="600" y="40" fontSize="14" fontFamily="IBM Plex Sans" fontWeight="500" letterSpacing="0.18em" fill="#4A4A4A" filter="url(#label-halo)">CARROLL GARDENS</text>
<text x="425" y="265" fontSize="11" fontFamily="IBM Plex Sans" fill="#4A6B4A" filter="url(#label-halo)">Coffey Park</text>
{/* Street labels at z15+ */}
<text x="120" y="195" fontSize="10" fontFamily="IBM Plex Sans" fill="#6B6B6B" filter="url(#label-halo)">Van Brunt St</text>
<text x="120" y="295" fontSize="10" fontFamily="IBM Plex Sans" fill="#6B6B6B" filter="url(#label-halo)">Pioneer St</text>
<text x="120" y="345" fontSize="10" fontFamily="IBM Plex Sans" fill="#6B6B6B" filter="url(#label-halo)">Imlay St</text>
{/* ββ Scale bar + zoom indicator (corners, like USGS quad) ββ */}
<g transform="translate(20 530)" fontFamily="IBM Plex Mono" fontSize="10" fill="#4A4A4A">
<line x1="0" y1="-2" x2="80" y2="-2" stroke="#1A1A1A" strokeWidth="1.5"/>
<line x1="0" y1="-5" x2="0" y2="1" stroke="#1A1A1A" strokeWidth="1"/>
<line x1="40" y1="-5" x2="40" y2="1" stroke="#1A1A1A" strokeWidth="1"/>
<line x1="80" y1="-5" x2="80" y2="1" stroke="#1A1A1A" strokeWidth="1"/>
<text x="0" y="14">0</text>
<text x="40" y="14" textAnchor="middle">200</text>
<text x="80" y="14" textAnchor="middle">400 ft</text>
</g>
<g transform="translate(720 28)" fontFamily="IBM Plex Mono" fontSize="10" fill="#4A4A4A">
<text x="0" y="0">z 16 Β· 40.6776Β°N 74.0096Β°W</text>
</g>
</svg>
);
};
const MapLegend = ({ activeLayers, onToggle }) => {
/* v0.4.5: restructured by Stone. Each row carries its source-Stone so the
panel visually mirrors the Findings stack. The tier swatch is unchanged. */
const layers = [
{ key: "empirical", tier: "empirical", stone: "cornerstone", label: "Sandy Inundation Zone (2012)", source: "NYC OEM" },
{ key: "modeled", tier: "modeled", stone: "cornerstone", label: "FEMA Zone AE Β· preliminary FIRM", source: "FEMA" },
{ key: "proxy", tier: "proxy", stone: "touchstone", label: "311 flood complaints, 2019β25", source: "NYC 311" },
{ key: "synthetic", tier: "synthetic", stone: "touchstone", label: "Synthetic LULC (2025-09-14)", source: "TerraMind v1.2" },
{ key: "prithvi-pluvial", tier: "modeled", stone: "touchstone", label: "Prithvi pluvial prediction", source: "Prithvi-NYC v2" },
];
const stoneOrder = ["cornerstone", "touchstone"];
const stoneMeta = {
cornerstone: { name: "Cornerstone", role: "what NYC's ground remembers" },
touchstone: { name: "Touchstone", role: "what's happening now" },
};
return (
<div className="map-legend" role="group" aria-label="Map layer toggles, grouped by Stone">
<div className="map-legend-head">
<span className="section-label">Layers Β· by Stone</span>
</div>
{stoneOrder.map((sk) => (
<div key={sk} className={`map-legend-stone map-legend-stone-${sk}`} data-stone={sk}>
<div className="map-legend-stone-head">
<span className={`map-legend-stone-dot map-legend-stone-dot-${sk}`} aria-hidden="true"></span>
<span className="map-legend-stone-name">{stoneMeta[sk].name}</span>
<span className="map-legend-stone-role">Β· {stoneMeta[sk].role}</span>
</div>
{layers.filter((l) => l.stone === sk).map((l) => (
<button
key={l.key}
type="button"
className={`map-legend-item ${activeLayers[l.key] ? "is-on" : "is-off"}`}
onClick={() => onToggle(l.key)}
aria-pressed={activeLayers[l.key]}
>
<span className="map-legend-swatch" aria-hidden="true">
<TierGlyph tier={l.tier} size={11} color={`var(--tier-${l.tier})`} />
</span>
<span className="map-legend-text">
<span className="map-legend-label">{l.label}</span>
<span className="map-legend-source">{l.source} Β· <TierBadge tier={l.tier} compact /></span>
</span>
<span className="map-legend-toggle" aria-hidden="true">{activeLayers[l.key] ? "ON" : "OFF"}</span>
</button>
))}
</div>
))}
</div>
);
};
Object.assign(window, { RedHookMapMock, MapLegend });
|