{/* Sites - SAME 5 sites in Legacy or Nexodata */}
{SITES.map((rawSite, i) => {
const site = getDisplayInfo(rawSite, deployMode);
const state = siteStates[i];
const status = derived.statuses[i];
const sc = statusColors[status];
const has = state.attacks.includes(selectedTool);
const action = !selectedTool ? null
: (has ? "remove"
: (selectedTool === "outage" ? (status !== "outage" ? "add" : null)
: (isFunctional(status) ? "add" : null)));
const interactable = action !== null;
const flagInfo = FLAG_COLORS[site.flag];
const stsRem = state.stsExpiresAt ? Math.max(0, state.stsExpiresAt - now) : 0;
const subpoenaRem = state.subpoenaAt ? Math.max(0, state.subpoenaAt - now) : 0;
const isRepairing = !legacy && state.repairAt && state.repairAt > now;
const isFlashing = presetFlash && presetFlash.siteIdx === i;
// Indirect exposure marker for legacy: data already lost via another site
const indirectExposed = legacy && legacyDamage.indirectExposure && status === "online";
return (
interactable && toggleAttack(i)}
className={interactable ? "site-hit" : ""}
style={{
position: "absolute", left: `${site.x}%`, top: `${site.y}%`,
transform: "translate(-50%,-50%)",
width: "70px", height: "70px",
display: "flex", alignItems: "center", justifyContent: "center",
zIndex: 10,
}}>
{/* Big invisible click area is the parent div above. Visual elements inside: */}
{/* Pulse ring (online) */}
{status === "online" && !indirectExposed && (
)}
{/* Indirect exposure pulse (legacy) */}
{indirectExposed && (
)}
{/* Targeting reticle (manual mode) */}
{selectedTool && action === "add" && (
)}
{/* Preset flash (auto mode) */}
{isFlashing && (
)}
{/* Repair pulse */}
{isRepairing && (
)}
{/* Subpoena warning ring */}
{status === "subpoena" && (
)}
{/* Site icon */}
{/* Label - positioned absolute outside the click area for cleanness */}
{site.name}, {site.prov}
{/* Physical location (always Canadian) */}
CA
{/* Legal control jurisdiction */}
{flagInfo.label}
{legacy ? "FULL COPY (replica)" : "1 FRAGMENT (1/" + N + ")"}
{state.attacks.length > 0 && (
{state.attacks.map(a => ATTACKS[a].label.toUpperCase()).join(" + ")}
)}
{indirectExposed && (
! INDIRECTLY EXPOSED
)}
{stsRem > 0 && (
STS {(stsRem / 1000).toFixed(1)}s
)}
{subpoenaRem > 0 && (
DISCLOSED {(subpoenaRem / 1000).toFixed(1)}s
)}
{isRepairing && (
REPAIRING via FMSR
)}
);
})}
{/* (Bottom strip removed - duplicated info from site labels and was overlapping bottom-row sites) */}