// V2 — Brutalista / Dorsal de carrera
// Crema + rojo dorsal, tipo condensada masiva, bordes negros gruesos,
// elementos rotados, numeración tipo BIB.
const v2c = {
paper: '#f1ead8',
ink: '#0e0e0c',
red: 'var(--v2-accent, #e8201f)',
red2: '#c81817',
shadow: '#0e0e0c',
warn: '#fff4a0',
cond: '"Anton", "Bebas Neue", Impact, safns-serif',
body: '"Space Grotesk", system-ui, sans-serif',
mono: '"JetBrains Mono", ui-monospace, monospace',
};
function V2Brutalist({ tweaks }) {
const t = tweaks || V2_DEFAULTS;
return (
);
}
/* ───────── Tweaks context ───────── */
const V2_DEFAULTS = {
accent: '#e8201f',
highlight: 'TLÓN',
prefix: 'DUA',
dateLine1: 'DOM · 02',
dateLine2: 'AGO 2026',
subtitle: 'YAGUARCOCHA',
daysLeft: 40,
};
const V2Ctx = React.createContext(V2_DEFAULTS);
const useV2 = () => React.useContext(V2Ctx);
/* ───────── Hero ───────── */
function V2Hero() {
const t = useV2();
return (
{/* nav */}
DYR · 2026
ES·EN
{/* Massive numeric BIB */}
{/* date stamp top-right rotated */}
{t.dateLine1}
{t.dateLine2}
{t.prefix}
{t.highlight}
{/* Sub-event line */}
DE RUTA
{t.subtitle}
{/* Image slot — race style, hero.svg as default bg */}
{/* bib number overlay */}
N° 0026
IBARRA · ECUADOR · 2.225m
{/* CTA + countdown row */}
);
}
/* ───────── Días reales restantes ───────── */
const V2_RACE_DATE = new Date(2026, 7, 2, 0, 0, 0); // 02 AGO 2026
function useDaysLeft() {
const calc = () => Math.max(0, Math.ceil((V2_RACE_DATE - new Date()) / 86_400_000));
const [days, setDays] = React.useState(calc);
React.useEffect(() => {
const id = setInterval(() => setDays(calc()), 60 * 60 * 1000);
return () => clearInterval(id);
}, []);
return days;
}
/* ───────── Ticker ───────── */
function V2Ticker() {
const items = ['CORRE', 'PEDALEA', 'CORRE', 'RUTA', 'IBARRA', '02·08·26', 'YAGUARCOCHA'];
const seq = [...items, ...items, ...items, ...items];
return (
{seq.map((t, i) => (
{t}
{i % 3 === 1 && ★}
))}
);
}
/* ───────── Distancias ───────── */
function V2Distancias() {
const formats = [
{ code: 'SPR', name: 'SPRINT', bk: 36, rn: 7, tag: 'CATEGORÍA COMPETITIVA' },
{ code: 'NOV', name: 'NOVATOS', bk: 16, rn: 3.6, tag: 'DEBUT · INICIACIÓN' },
];
return (
DOS
DISTANCIAS
UNA META.
{formats.map((f, i) => (
+
{f.tag}
{f.bk + f.rn} KM TOTAL
))}
{/* POSTAS — relay card */}
EQUIPOS DE 2 · BIKE + RUN
{['SOLO H', 'SOLO M', 'MIXTAS'].map((t, i) => (
))}
FORMATO RELEVO
UN ATLETA · UNA POSTA
);
}
function V2Leg({ n, label }) {
return (
);
}
/* ───────── Mapa ───────── */
function V2Mapa() {
return (
03
EL CIRCUITO
VUELTA A
LA PISTA.
AUTÓDROMO INTERNACIONAL DE YAGUARCOCHA · ASFALTO CERRADO
{/* Track card */}
{/* Legend tape */}
ASFALTO · CERRADO AL TRÁFICO
{/* Stats */}
{[
['3,6', 'KM / VUELTA'],
['8', 'CURVAS'],
['2.225', 'm s.n.m.'],
].map(([n, l], i) => (
))}
{/* Vueltas por categoría */}
{[
{ name: 'SPRINT · BIKE', laps: 10, km: 36 },
{ name: 'SPRINT · RUN', laps: 2, km: 7 },
{ name: 'NOVATOS · BIKE', laps: 4, km: 16 },
{ name: 'NOVATOS · RUN', laps: 1, km: 3.6 },
].map((v, i) => (
{v.name}
{v.laps}vueltas
≈ {v.km}km
))}
);
}
/* ───────── Categorías ───────── */
function V2Categorias() {
// Categorías por edad — aplican en Sprint y Novatos, hombres y mujeres.
const cats = [
['PREJUVENIL', '13 — 16 AÑOS'],
['SUB-23', '16 — 22 AÑOS'],
['SÉNIOR A', '23 — 30 AÑOS'],
['SÉNIOR B', '31 — 39 AÑOS'],
['MASTER A', '40 — 49 AÑOS'],
['MASTER B', '50 — 55 AÑOS'],
['MASTER C', '56 EN ADELANTE'],
];
return (
{/* table */}
CATEGORÍAEDAD
{cats.map(([n, who], i) => (
))}
{/* Categorías abiertas MTB */}
CATEGORÍAS ABIERTAS · MTB
GENERAL SIN
LÍMITE DE EDAD MTB
{['SPRINT', 'NOVATOS', 'POSTAS'].map((t, i) => (
))}
⚠ Una categoría se abre con mínimo 6 participantes; caso contrario, se suma a la categoría superior.
PREMIACIÓN.
SPRINT & NOVATOS · HOMBRES Y MUJERES
PODIO 1·2·3 POR CATEGORÍA · MEDALLA FINISHER
{/* Foto premiación */}
PREMIACIÓN · EDICIÓN ANTERIOR
{/* Premios por categoría */}
PREMIOS POR CATEGORÍA
TROFEO + KIT DE
AUSPICIANTES
1º · 2º · 3º DE CADA CATEGORÍA · SPRINT Y NOVATOS · HOMBRES Y MUJERES
);
}
/* ───────── Precios ───────── */
function V2Precios() {
const price = { sprint: 34, novatos: 27, postas: 25 };
return (
VALOR
POR
PERSONA.
INSCRIPCIÓN 2026
PRECIOS POR PERSONA
ASEGURA TU DORSAL →
▶ INCLUYE
{[
'Dorsal + chip de cronometraje',
'Camiseta técnica LAFTECH',
'Correa de control',
'Kit de hidratación',
'Seguro del evento',
'Medalla finisher',
].map((it, i) => (
-
·
{it}
))}
);
}
/* ───────── Countdown 48h loop ───────── */
function V2Countdown() {
// Cycle: 2 days (48h). Counts down to the next midnight that is at
// least one full day away, then loops back to ~48h automatically.
const CYCLE_MS = 2 * 24 * 60 * 60 * 1000;
const getRemaining = () => {
const now = new Date();
const anchor = new Date(now);
anchor.setHours(24, 0, 0, 0); // tonight's midnight
anchor.setDate(anchor.getDate() + 1); // → tomorrow's midnight (≈ 24-48h ahead)
let diff = anchor - now;
while (diff <= 0) diff += CYCLE_MS;
const d = Math.floor(diff / 86_400_000);
const h = Math.floor((diff % 86_400_000) / 3_600_000);
const m = Math.floor((diff % 3_600_000) / 60_000);
const s = Math.floor((diff % 60_000) / 1000);
return { d, h, m, s };
};
const [t, setT] = React.useState(getRemaining);
React.useEffect(() => {
const id = setInterval(() => setT(getRemaining()), 1000);
return () => clearInterval(id);
}, []);
const pad = (n) => String(n).padStart(2, '0');
const cell = (n, l) => (
);
return (
{/* live pulse dot */}
EN VIVO
CUPOS LIMITADOS
TIEMPO APROXIMADO PARA TERMINAR REGISTROS
{cell(t.d, 'DÍAS')}
{cell(t.h, 'HORAS')}
{cell(t.m, 'MIN')}
{cell(t.s, 'SEG')}
);
}
function V2Sep() {
return (
:
);
}
/* ───────── Inscripción form (embed) ───────── */
function V2Form() {
// Load the embed script once.
React.useEffect(() => {
const SRC = 'https://link.centralize.es/js/form_embed.js';
if (document.querySelector(`script[src="${SRC}"]`)) return;
const s = document.createElement('script');
s.src = SRC;
s.async = true;
document.body.appendChild(s);
}, []);
return (
{/* Header bar — matches the brutalist tabs/labels above */}
▶ COMPLETA TUS DATOS
FORMULARIO DE
INSCRIPCIÓN
PASO 01
{/* Iframe container — tall enough for typical multi-field forms.
The embed script auto-resizes when it can. */}
✓
PAGO SEGURO
· CUPOS LIMITADOS ·
✓
CONFIRMACIÓN INMEDIATA
);
}
function V2PriceBlock({ label, price, invert }) {
const fg = invert ? v2c.paper : v2c.ink;
const bg = invert ? 'rgba(14,14,12,.2)' : 'rgba(14,14,12,.06)';
return (
);
}
/* ───────── Video ───────── */
function V2Video() {
return (
);
}
/* ───────── Auspiciantes ───────── */
function V2Auspiciantes() {
const logos = [
'https://i.postimg.cc/gkXhMmsw/Whats-App-Image-2026-01-16-at-3-13-10-PM.jpg',
'https://i.postimg.cc/tCY3Lhqz/Whats-App-Image-2026-03-31-at-10-16-01.jpg',
'https://i.postimg.cc/mrNCQjw8/NIKKY.jpg',
];
return (
{logos.map((src, i) => (
))}
);
}
/* ───────── Footer ───────── */
function V2Footer() {
return (
);
}
/* ───────── helper ───────── */
function V2Eyebrow({ num, label }) {
return (
{num}
{label}
);
}
window.V2Brutalist = V2Brutalist;