/* Torre Auro — Home (editorial, ecosystem-led, 6 sections) */
/* ─── S1 · HERO ─── */
function S1Hero() {
const [showVideo, setShowVideo] = useState(false);
const [videoFailed, setVideoFailed] = useState(false);
const iframeRef = useRef(null);
useEffect(() => {
const start = () => {
setTimeout(() => setShowVideo(true), 1200);
};
if (document.readyState === 'complete') start();
else window.addEventListener('load', start, { once: true });
return () => window.removeEventListener('load', start);
}, []);
// If the iframe never fires its load event within 8s, treat as failed
useEffect(() => {
if (!showVideo || videoFailed) return;
const t = setTimeout(() => {
const ifr = iframeRef.current;
if (!ifr || !ifr.dataset.loaded) setVideoFailed(true);
}, 8000);
return () => clearTimeout(t);
}, [showVideo, videoFailed]);
function handleVideoLoad(e) {
e.target.dataset.loaded = '1';
}
function handleVideoError() {
setVideoFailed(true);
}
return (
{showVideo && !videoFailed && (
)}
El ecosistema
empresarial.
document.getElementById('atmospheric')?.scrollIntoView({ behavior:'smooth' })}
>
Explorar
↓
Costa de Oro · Boca del Río
Frente al Golfo · zona moderna de Veracruz
7 niveles · 4 ecosistemas
);
}
/* ─── S2 · ATMOSPHERIC ─── */
function S2Atmospheric() {
return (
Día → Atardecer
/ Posicionamiento
Un solo edificio, todo un ecosistema .
Costa de Oro · al cruce de Ruiz Cortines + Av. Tiburón
);
}
/* ─── S2.5 · LA UBICACIÓN ─── */
// module-level cache so we don't refetch on re-mount/navigation
let _denueCache = null;
function haversine(aLat, aLng, bLat, bLng) {
const R = 6371000, toR = Math.PI / 180;
const dLat = (bLat - aLat) * toR, dLng = (bLng - aLng) * toR;
const s = Math.sin(dLat/2)**2 + Math.cos(aLat*toR)*Math.cos(bLat*toR)*Math.sin(dLng/2)**2;
return 2 * R * Math.asin(Math.sqrt(s));
}
function SLocation() {
const mapRef = useRef(null);
const clusterRef = useRef(null);
const placesRef = useRef([]); // {lat,lng,name,cat,clase,dist,marker}
const [ring, setRing] = useState(5000);
const [active, setActive] = useState('todos');
const [status, setStatus] = useState('cargando');
const [counts, setCounts] = useState({});
const ringRef = useRef(5000), activeRef = useRef('todos');
function makeMarker(p) {
const col = DISTRICT_CATS[p.cat]?.color || '#888';
const icon = window.L.divIcon({
className: 'lmk-pin-wrap',
html: ` `,
iconSize: [16, 16], iconAnchor: [8, 8],
});
const m = window.L.marker([p.lat, p.lng], { icon });
m.bindTooltip(`${p.name} ${p.clase || DISTRICT_CATS[p.cat]?.label || ''}`, {
direction:'top', offset:[0,-8], className:'lmk-tip',
});
m._cat = p.cat;
return m;
}
function applyFilters() {
const cluster = clusterRef.current;
if (!cluster) return;
cluster.clearLayers();
const r = ringRef.current, cat = activeRef.current;
const subset = placesRef.current.filter(p =>
p.dist <= r && (cat === 'todos' || p.cat === cat)
);
cluster.addLayers(subset.map(p => p.marker));
}
function recount() {
const r = ringRef.current;
const c = { todos: 0 };
placesRef.current.forEach(p => {
if (p.dist <= r) { c.todos++; c[p.cat] = (c[p.cat] || 0) + 1; }
});
setCounts(c);
}
async function fetchDenue(signal) {
if (_denueCache) return _denueCache;
const { lat, lng } = TORRE_COORD;
const out = [];
const seen = new Set();
await Promise.all(Object.entries(DENUE_TERMS).map(async ([cat, term]) => {
try {
const url = `https://www.inegi.org.mx/app/api/denue/v1/consulta/Buscar/${encodeURIComponent(term)}/${lat},${lng}/5000/${DENUE_TOKEN}`;
const res = await fetch(url, { signal });
if (!res.ok) return;
const data = await res.json();
(data || []).forEach(row => {
const la = parseFloat(row.Latitud), ln = parseFloat(row.Longitud);
if (!la || !ln) return;
const id = row.Id || (row.Nombre + la + ln);
if (seen.has(id)) return;
seen.add(id);
out.push({
lat: la, lng: ln,
name: row.Nombre || row.Razon_social || 'Establecimiento',
clase: row.Clase_actividad || '',
cat,
dist: haversine(lat, lng, la, ln),
});
});
} catch (e) { /* ignore per-category */ }
}));
_denueCache = out;
return out;
}
useEffect(() => {
if (!window.L || mapRef.current) return;
const el = document.getElementById('locmap-leaflet');
if (!el) return;
let cancelled = false;
const ac = new AbortController();
const { lat, lng } = TORRE_COORD;
const map = window.L.map(el, {
center: [lat, lng], zoom: 15,
scrollWheelZoom: false, zoomControl: true, attributionControl: false,
});
mapRef.current = map;
window.L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', { maxZoom: 19 }).addTo(map);
// radius rings
[5000, 3000, 1000].forEach(r => {
window.L.circle([lat, lng], { radius: r, color: '#C8A45C', weight: 1, opacity: .45, fill: false, dashArray: '4 6' }).addTo(map);
});
// Torre anchor
const torreIcon = window.L.divIcon({
className: 'lmk-torre-wrap',
html: '
Torre Auro
',
iconSize: [0, 0],
});
window.L.marker([lat, lng], { icon: torreIcon, zIndexOffset: 1000 }).addTo(map);
const cluster = window.L.markerClusterGroup({
maxClusterRadius: 100, showCoverageOnHover: false, spiderfyOnMaxZoom: false,
disableClusteringAtZoom: 17, chunkedLoading: true,
iconCreateFunction: (c) => {
const kids = c.getAllChildMarkers();
const tally = {};
kids.forEach(m => { tally[m._cat] = (tally[m._cat] || 0) + 1; });
let dom = null, max = 0;
for (const k in tally) { if (tally[k] > max) { max = tally[k]; dom = k; } }
const col = DISTRICT_CATS[dom]?.color || '#1F4C82';
const n = c.getChildCount();
const size = n < 8 ? 42 : n < 20 ? 58 : n < 45 ? 76 : 96;
return window.L.divIcon({
className: 'lmk-blob-wrap',
html: ` `,
iconSize: [size, size],
});
},
});
clusterRef.current = cluster;
map.addLayer(cluster);
map.setView([lat, lng], 13);
(async () => {
let places = [];
try { places = await fetchDenue(ac.signal); } catch (e) { /* fallthrough */ }
if (cancelled || mapRef.current !== map) return;
let usedFallback = false;
if (!places || places.length === 0) {
// fallback to curated static data
usedFallback = true;
places = DISTRICT_PLACES.map(p => ({
...p, clase: DISTRICT_CATS[p.cat]?.label || '',
dist: haversine(lat, lng, p.lat, p.lng),
}));
}
places.forEach(p => { p.marker = makeMarker(p); });
placesRef.current = places;
applyFilters();
recount();
setStatus(usedFallback ? 'fallback' : 'ok');
})();
return () => { cancelled = true; ac.abort(); map.remove(); mapRef.current = null; clusterRef.current = null; placesRef.current = []; };
}, []);
function pickRing(r) { setRing(r); ringRef.current = r; applyFilters(); recount(); }
function filterCat(cat) { setActive(cat); activeRef.current = cat; applyFilters(); }
const RINGS = [{ v:1000, l:'1 km' }, { v:3000, l:'3 km' }, { v:5000, l:'5 km' }];
// top categories in active ring, sorted by count (for the density read)
const ranked = Object.entries(DISTRICT_CATS)
.map(([id, c]) => ({ id, label: c.label, color: c.color, n: counts[id] || 0 }))
.sort((a, b) => b.n - a.n);
const topN = ranked.length ? Math.max(...ranked.map(r => r.n), 1) : 1;
return (
/ 01 Dónde está Torre Auro
En el corazón del distrito de negocios de Boca del Río.
Torre Auro está sobre Blvd. Adolfo Ruiz Cortines, en Costa de Oro — el corredor de mayor densidad corporativa de Veracruz. El mapa concentra los giros relevantes para un centro de negocios: corporativo, convenciones, hospedaje ejecutivo, comercio ancla y salud, según el DENUE del INEGI.
{/* MAP */}
{Object.entries(DISTRICT_CATS).map(([id, c]) => (
filterCat(active === id ? 'todos' : id)}>
{c.label}
{counts[id] || 0}
))}
{/* INSIGHT PANEL */}
Torre Auro
Blvd. Adolfo Ruiz Cortines · Costa de Oro, Boca del Río, Ver.
Radio de análisis
{RINGS.map(r => (
pickRing(r.v)}>{r.l}
))}
{status === 'cargando' ? '···' : (counts.todos || 0)}
negocios a {ring/1000} km de Torre Auro
{status === 'cargando' ? 'Consultando DENUE · INEGI…'
: status === 'fallback' ? 'Datos curados · DENUE no disponible'
: 'Fuente: DENUE · INEGI'}
Qué te rodea
{ranked.map(r => (
filterCat(active === r.id ? 'todos' : r.id)}>
{r.label}
{r.n}
))}
Toca una categoría para aislarla en el mapa. Acerca el zoom para ver cada negocio; los grupos se concentran alrededor de la torre.
);
}
/* ─── S3 · LOS 4 ECOSISTEMAS ─── */
function S3Ecosystems() {
return (
/ 02 Los cuatro ecosistemas
Cada giro, su ecosistema.
Torre Auro agrupa a sus inquilinos por afinidad. Cuatro ecosistemas donde cada empresa convive con quienes comparten su tipo de cliente, su ritmo y su perfil profesional.
{ECOSYSTEMS.map((e, i) => (
navigate('/' + e.primaryFloor)}
style={{ '--acc': e.accHex }}
>
{e.levels}
{e.name}
{e.descriptor}
Explorar →
))}
);
}
/* ─── S4 · LA TORRE — interactive floor + plan ─── */
function S4Tower() {
const [selId, setSelId] = useState('nivel-1');
const [fading, setFading] = useState(false);
function selectFloor(id) {
if (id === selId) return;
setFading(true);
setTimeout(() => { setSelId(id); setFading(false); }, 200);
}
const floor = FLOOR_BY_ID[selId] || FLOORS[5];
const z = ZONES[floor.zone];
return (
/ 03 La torre
Una arquitectura de afinidades.
Selecciona un nivel para ver su plano y su perfil de ecosistema. Cada piso está asignado a un entorno específico — los inquilinos comparten visión, no solo espacio.
{/* Architectural elevation */}
Elevación · Costa de Oro
7 nivelesEsc. 1:200
{FLOORS.map((f) => {
const fz = ZONES[f.zone];
const isSel = f.id === selId;
return (
selectFloor(f.id)}
aria-label={`${f.sh} — ${fz.tag}`}
aria-pressed={isSel}
style={isSel ? { borderColor: fz.accHex, background:'linear-gradient(90deg, rgba(0,0,0,.04), #fff 40%)' } : {}}
>
{f.sh}
{f.short.split('·')[0].trim()}
);
})}
LOBBY · ACCESO
S1 · ESTACIONAMIENTO
S2
{/* Floor detail panel */}
{/* Tag + header */}
{z.tag}
{floor.sh}
{floor.name}
{floor.desc}
{/* Plan */}
{floor.planClass ? (
Nivel
{floor.sh}
Ancla · C
137 m²
Consolid. · B
120 m²
Boutique · A
3 × 85 m²
) : null}
{/* Stats + CTA */}
{floor.av}/{floor.tot}
disponibles
{FMT(floor.pMin)}
desde / mes
navigate(floor.route)}>
Ver el nivel
);
}
/* ─── S4.5 · VISTAS · real interiors gallery ─── */
function SVistas() {
const shots = [
{ cls:'bg--interior-vista-2', label:'Niveles altos · panorámica', lvl:'N5 — N6' },
{ cls:'bg--interior-n4', label:'Vista al Golfo de México', lvl:'N4' },
{ cls:'bg--interior-vista-1', label:'Acceso poniente · obra gris', lvl:'N5' },
];
return (
/ 04 El entorno · vistas reales
Lo que se vedesde adentro.
Cada nivel de Torre Auro abre a una vista distinta del Golfo y de Costa de Oro. El entorno que rodea a una empresa forma parte de cómo opera y de cómo la perciben sus clientes.
{shots.map((s, i) => (
{s.lvl}
{s.label}
))}
);
}
/* ─── S5.5 · LA CONVERSACIÓN — voice teaser ─── */
function SConversacion() {
const sorted = [...POSTS].sort((a, b) => b.dateISO.localeCompare(a.dateISO));
const latestPost = sorted[0];
const featured = EVENTS.find(e => e.status === 'featured');
return (
/ 05 La conversación
Lo que pasaen Torre Auro.
El ecosistema no es solo el edificio. Es la agenda de eventos, el punto de vista editorial y la comunidad que se construye con el tiempo.
{featured && (
navigate('/eventos/' + featured.slug)}>
El calendario · {STATUS_META[featured.status]}
{new Date(featured.dateISO + 'T12:00:00').getDate()}
{new Date(featured.dateISO + 'T12:00:00').toLocaleDateString('es-MX', { month:'short' }).replace('.', '').toUpperCase()}
{featured.kind}
{featured.title}
{featured.short}
Ver el evento →
)}
{latestPost && (
navigate('/perspectiva/' + latestPost.slug)}>
Perspectiva · {latestPost.issue.split(' · ').pop()}
{latestPost.eyebrow}
{latestPost.title}
{latestPost.excerpt}
{latestPost.date}
·
{latestPost.readMin} min de lectura
Leer la entrada →
)}
navigate('/eventos')}>
Ver todo el calendario
navigate('/perspectiva')}>
Leer Perspectiva
);
}
function S5Availability() {
return (
/ 06 Disponibilidad actual
El edificio,al día de hoy.
{ECOSYSTEMS.map((e) => {
const floors = e.floors.map(id => FLOOR_BY_ID[id]).filter(Boolean);
const totalAv = floors.reduce((a, f) => a + f.av, 0);
const totalTot = floors.reduce((a, f) => a + f.tot, 0);
return (
navigate('/' + e.primaryFloor)}>
{totalAv}/{totalTot}
espacios
→
);
})}
navigate('/nivel-1')}>
Ver disponibilidad completa
);
}
/* ─── S6 · CIERRE ─── */
function S6Close() {
return (
07
Un lugar en el
ecosistema empresarial.
navigate('/contacto')}>
Solicitar información
{
document.getElementById('ecosistemas')?.scrollIntoView({ behavior:'smooth' });
}}>
Explorar ecosistemas
openChat('general')}>
Conocer disponibilidad →
);
}
/* ─── HOME ─── */
function Home() {
return (
{/* */}
);
}
Object.assign(window, { Home });