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 });