/* global React */
const { useState, useEffect, useRef } = React;
// ─────────────────────────────────────────────
// Logo — real PNG provided by client (white / black / green variants)
// Native aspect ratio ≈ 2675×709 (~3.77:1).
// ─────────────────────────────────────────────
function TGLogo({ height = 26, variant = "white" }) {
const src =
variant === "green" ? "assets/logo-green.png" :
variant === "black" ? "assets/logo-black.png" :
"assets/logo-white.png";
return (
);
}
// ─────────────────────────────────────────────
// Hero
// ─────────────────────────────────────────────
function Hero({ tweaks }) {
const variant = tweaks.heroVariant; // image | split | card
const cta = tweaks.ctaCopy;
// Hero slideshow — rotates through 9 photos with a 6s interval and a soft
// crossfade. Disabled if the user picks a specific photo from Tweaks.
const HERO_SLIDESHOW = [
"assets/hero/hero-sunset-swing.jpg",
"assets/hero/hero-fairway-swing.jpg",
"assets/hero/hero-bunker-spray.jpg",
"assets/hero/hero-tee-clouds.jpg",
"assets/hero/hero-red-swing.jpg",
"assets/hero/hero-green-flag.jpg",
"assets/hero/hero-putt-line.jpg",
"assets/hero/hero-bunker-side.jpg",
"assets/hero/hero-cheers.jpg",
];
const HERO_PINNED = {
sunset: "assets/hero/hero-sunset-swing.jpg",
fairway: "assets/hero/hero-fairway-swing.jpg",
original: "https://images.unsplash.com/photo-1535132011086-b8818f016104?auto=format&fit=crop&w=2000&q=80",
};
const isSlideshow = tweaks.heroImage === "slideshow";
const [slideIdx, setSlideIdx] = useState(0);
useEffect(() => {
if (!isSlideshow) return;
// Preload all images so the crossfade is smooth.
HERO_SLIDESHOW.forEach((src) => { const img = new Image(); img.src = src; });
const id = setInterval(() => {
setSlideIdx((i) => (i + 1) % HERO_SLIDESHOW.length);
}, 8000);
return () => clearInterval(id);
}, [isSlideshow]);
const accentBgImage = isSlideshow
? HERO_SLIDESHOW[slideIdx]
: (HERO_PINNED[tweaks.heroImage] || HERO_PINNED.sunset);
const headlineByVariant = {
image: (
<>
EL CIRCUITO QUE MUEVE TU JUEGO
>
),
split: (
<>
FIXTURE.
RANKING.
RESERVAS.
>
),
card: (
<>
ENTRA EN CANCHA
>
),
};
// Renders the hero background. In slideshow mode, all images are stacked and
// we fade between them by toggling the active class. In pinned mode it's a
// single static layer.
const HeroBg = ({ opacity = 1 }) => {
if (isSlideshow) {
return (
{HERO_SLIDESHOW.map((src, i) => (
))}
);
}
return (
);
};
return (
{variant === "image" && (
<>
>
)}
{variant === "split" && }
{variant === "card" && (
<>
>
)}
{variant === "card" ? (
TEMPORADA 2026 · AMBA
{headlineByVariant.card}
Torneos semanales en los mejores clubes de Buenos Aires. Inscribite, jugá, sumá puntos para el ranking.
) : (
<>
TEMPORADA 2026 · AMBA
{headlineByVariant[variant]}
El circuito de torneos amateurs que recorre los mejores clubes del AMBA. Fixture semanal, resultados en vivo y próximamente rankings.
>
)}
SEÑAL EN VIVO · TG.LIVE
);
}
function FeaturedFixture() {
const [next, setNext] = useState(null);
useEffect(() => {
const handler = (e) => setNext(e.detail);
window.addEventListener("tg:next-fixture", handler);
return () => window.removeEventListener("tg:next-fixture", handler);
}, []);
if (!next) {
return (
PRÓXIMA FECHA
··
CARGANDO
SINCRONIZANDO FIXTURE
Conectando con golf.trendingolf.com…
);
}
return (
▶ PRÓXIMA FECHA
{String(next.day).padStart(2, "0")}
{next.monthShort}
{next.club}
{next.dow} · {next.price}
RESERVAR →
);
}
// ─────────────────────────────────────────────
// Fixture — pulls from adondejugamos via fetcher
// ─────────────────────────────────────────────
function Fixture({ tweaks }) {
// Try cache first → instant render on repeat visits.
const cached = (window.TGFixtures && window.TGFixtures.loadCached()) || null;
const today = new Date(); today.setHours(0,0,0,0);
const initialCached = cached ? cached.filter(f => f.date >= today) : [];
const [fixtures, setFixtures] = useState(initialCached);
const [status, setStatus] = useState(initialCached.length ? "ok" : "loading"); // loading | ok | error | empty
const [lastSync, setLastSync] = useState(null);
useEffect(() => {
let cancelled = false;
async function load() {
try {
if (!fixtures.length) setStatus("loading");
const data = await window.TGFixtures.load();
if (cancelled) return;
if (!data || data.length === 0) {
if (!fixtures.length) { setStatus("empty"); setFixtures([]); }
return;
}
setFixtures(data);
setStatus("ok");
setLastSync(new Date());
// Emit next fixture for hero featured card
window.dispatchEvent(new CustomEvent("tg:next-fixture", { detail: data[0] }));
} catch (e) {
console.error("Fixture load error", e);
if (!cancelled && !fixtures.length) setStatus("error");
}
}
load();
const id = setInterval(load, 60 * 60 * 1000); // hourly
return () => { cancelled = true; clearInterval(id); };
}, []);
const layoutClass = `fixture--${tweaks.fixtureLayout}`; // carousel | grid | list
const tickerItems = fixtures.slice(0, 8).map((f) => `${f.dow.slice(0,3)} ${String(f.day).padStart(2,"0")}/${f.monthShort} · ${f.club}`);
return (
{/* Marquee strip with dates */}
{Array(2).fill(0).map((_, dup) => (
{(tickerItems.length ? tickerItems : ["TRENDINGOLF · TEMPORADA 2026", "FIXTURE · AMBA", "RANKING EN VIVO"]).map((item, i) => (
{item}
))}
))}
FIXTURE · AUTO-SYNC
PRÓXIMAS FECHAS
Datos en vivo desde la operación de TRENDINGOLF. Actualización automática cada hora.
{status === "ok" && lastSync ? `SYNC ${lastSync.toLocaleTimeString("es-AR", {hour:"2-digit",minute:"2-digit"})}` : status === "loading" ? "CONECTANDO…" : status === "error" ? "OFFLINE — VOLVER A INTENTAR" : "SIN FECHAS"}
{status === "loading" && (
SINCRONIZANDO FIXTURE…
)}
{status === "error" && (
)}
{status === "empty" && (
NO HAY FECHAS PROGRAMADAS EN ESTE MOMENTO.
)}
{status === "ok" && (
{fixtures.slice(0, 12).map((f, i) => (
))}
)}
);
}
function FixtureCard({ f, isNext }) {
const rankingClass = f.isRanking ? "fcard--ranking" : "";
return (
{f.isRanking && (
★ RANKING{f.rankingLabel ? ` ${f.rankingLabel}` : ""}
)}
{isNext && !f.isRanking && ▶ PRÓXIMA
}
{String(f.day).padStart(2, "0")}
{f.monthShort}
{f.dow}
{f.club}
CIRCUITO
TRENDINGOLF
GREEN FEE
{f.price}
RESERVAR →
);
}
// ─────────────────────────────────────────────
// About
// ─────────────────────────────────────────────
function About() {
return (
QUÉ ES TRENDINGOLF
UN CIRCUITO. MUCHAS CANCHAS.
Organizamos torneos amateurs en los mejores clubes del AMBA. Cada semana, una cancha distinta. Cada torneo, una oportunidad de jugar, conocer y ranquear.
Reservas, inscripciones y resultados viven en golf.trendingolf.com. Acá te contamos qué se viene.
);
}
// ─────────────────────────────────────────────
// Clubs
// ─────────────────────────────────────────────
function Clubs() {
const clubs = [
{ name: "Highland Park CC", loc: "DEL VISO · BS.AS." },
{ name: "Los Lagartos CC", loc: "PILAR · BS.AS." },
{ name: "Golfer's CC", loc: "BERAZATEGUI · BS.AS." },
{ name: "Estancias GC", loc: "PILAR · BS.AS." },
{ name: "Club de Campo Los Pingüinos", loc: "ITUZAINGO · BS.AS." },
{ name: "Ituzaingo GC", loc: "ITUZAINGO · BS.AS." },
{ name: "Abril CC", loc: "HUDSON · BS.AS." },
{ name: "San Andrés GC", loc: "SAN ANDRÉS · BS.AS." },
{ name: "Club Campos de Golf Las Praderas de Luján", loc: "OPEN DOOR · BS.AS." },
{ name: "Haras Santa María", loc: "ESCOBAR · BS.AS." },
{ name: "La Colina Villa de Campo", loc: "OPEN DOOR · BS.AS." },
{ name: "La Martona CC", loc: "CAÑUELAS · BS.AS." },
];
return (
CLUBES · AMBA
DONDE JUGAMOS
Recorremos los campos más emblemáticos del Área Metropolitana. Cada fecha, una cancha distinta.
{clubs.map((c, i) => (
CLUB / {String(i+1).padStart(2,"0")}
{c.name}
{c.loc}
))}
);
}
// ─────────────────────────────────────────────
// Results — list of recent finished torneos pulled live from
// golf.trendingolf.com/golf/torneos.php (via TGResults loader).
// ─────────────────────────────────────────────
// Skeleton placeholder rows (rendered while we wait for live data on first
// visit — cached results from the previous load are preferred when available).
const RESULTS_SKELETON = Array.from({ length: 6 }, (_, i) => ({
skeleton: true,
id: i,
}));
function Results() {
// Try cache first → instant render on repeat visits.
const cached = (window.TGResults && window.TGResults.loadCached()) || null;
const [items, setItems] = useState(cached ? cached.filter(t => t.date < new Date()).slice(0, 8) : RESULTS_SKELETON);
const [status, setStatus] = useState(cached ? "live" : "loading"); // loading | live | error
useEffect(() => {
let cancelled = false;
if (!window.TGResults) { setStatus("error"); setItems([]); return; }
window.TGResults.load()
.then((data) => {
if (cancelled) return;
const past = data.filter((t) => t.date < new Date()).slice(0, 8);
if (past.length) {
setItems(past);
setStatus("live");
} else if (!cached) {
setStatus("error");
setItems([]);
}
})
.catch(() => {
if (cancelled) return;
if (!cached) { setStatus("error"); setItems([]); }
});
return () => { cancelled = true; };
}, []);
const last = items.find(t => !t.skeleton) || null;
const eyebrow = last
? `ÚLTIMA FECHA · ${String(last.day).padStart(2,"0")}/${String(last.date.getMonth()+1).padStart(2,"0")} · ${last.venue}`
: "TEMPORADA 2026";
return (
);
}
// ─────────────────────────────────────────────
// Sponsors
// ─────────────────────────────────────────────
function Sponsors() {
const partners = [
{ name: "Srixon", logo: "assets/sponsors/srixon.png", scale: 1.71 },
{ name: "WildGolf", logo: "assets/sponsors/wildgolf.png", scale: 1.0 },
{ name: "Colosso Wines", logo: "assets/sponsors/colosso.png", scale: 1.76 },
{ name: "TeeDel", logo: "assets/sponsors/teedel.png", scale: 1.0 },
];
return (
);
}
// ─────────────────────────────────────────────
// Viajes — feed of TEEDEL1 group trips
// ─────────────────────────────────────────────
function Viajes() {
const trips = [
{
title: "Patagonia Open 2026: Chapelco Edition",
sub: "El desafío Nicklaus en el corazón de la Cordillera",
img: "https://teedel1.com/wp-content/uploads/2026/01/Chapelco-01-374x226.jpg",
tag: "ARGENTINA · PATAGONIA",
duration: "4 DÍAS · 3 NOCHES",
price: "$690",
priceOriginal: "$770",
url: "https://teedel1.com/trip/patagonia-open-2026-chapelco-edition/",
},
{
title: "Costa del Sol Golf Challenge",
sub: "10 al 17 de Mayo 2026",
img: "https://teedel1.com/wp-content/uploads/2022/03/Golf-Course-374x226.webp",
tag: "ESPAÑA · COSTA DEL SOL",
duration: "8 DÍAS · 7 NOCHES",
price: "$2,890",
priceOriginal: "$3,090",
url: "https://teedel1.com/trip/costa-del-sol-golf-challenge/",
},
{
title: "Miami Golfer's World Cup 2026",
sub: "Trump National Doral & PGA National · 29 jun – 7 jul",
img: "https://teedel1.com/wp-content/uploads/2026/01/course-the-fazio-374x226.jpg",
tag: "USA · FLORIDA",
duration: "9 DÍAS · 8 NOCHES",
price: "$3,690",
priceOriginal: "$4,680",
url: "https://teedel1.com/trip/miami-golfers-world-cup-2026-trump-national-doral-pga-national/",
},
{
title: "Stage 1: Trump National Doral",
sub: "Blue Monster · 29 jun al 3 jul 2026",
img: "https://teedel1.com/wp-content/uploads/2026/01/Blue-Monster-2-374x226.jpg",
tag: "USA · FLORIDA",
duration: "5 DÍAS · 4 NOCHES",
price: "$2,190",
priceOriginal: "$2,490",
url: "https://teedel1.com/trip/miami-golfers-world-cup-2026-stage-1-trump-national-doral-blue-monster/",
},
{
title: "Stage 2: PGA National & The Bear Trap",
sub: "3 al 7 de julio 2026",
img: "https://teedel1.com/wp-content/uploads/2025/12/course-champion-1920x720-1-374x226.webp",
tag: "USA · FLORIDA",
duration: "5 DÍAS · 4 NOCHES",
price: "$1,790",
priceOriginal: "$2,190",
url: "https://teedel1.com/trip/pga-national-y-alentar-a-argentina/",
},
{
title: "Punta Cana Golf Trophy",
sub: "16 al 23 de agosto 2026",
img: "https://teedel1.com/wp-content/uploads/2025/12/GRUPAL-374x226.webp",
tag: "CARIBE · PUNTA CANA",
duration: "8 DÍAS · 7 NOCHES",
price: "$2,490",
priceOriginal: null,
url: "https://teedel1.com/trip/punta-cana-golf-trophy/",
},
];
return (
VIAJES · TEEDEL1
JUGÁ EN LAS MEJORES CANCHAS DEL MUNDO
Programas grupales seleccionados con TEEDEL1 Golf Travel. Argentina, Caribe, España y USA.
);
}
// ─────────────────────────────────────────────
// Final CTA
// ─────────────────────────────────────────────
function FinalCTA({ tweaks }) {
return (
JUGÁ LA PRÓXIMA
Asegurá tu lugar en el siguiente torneo. Reservas en línea, pago digital, salida garantizada.
{tweaks.ctaCopy} →
);
}
// ─────────────────────────────────────────────
// Footer
// ─────────────────────────────────────────────
function Footer() {
return (
);
}
// ─────────────────────────────────────────────
// Header
// ─────────────────────────────────────────────
function Header({ tweaks }) {
return (
);
}
// ─────────────────────────────────────────────
// App
// ─────────────────────────────────────────────
function App() {
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
"heroVariant": "image",
"heroImage": "slideshow",
"fixtureLayout": "carousel",
"ctaCopy": "RESERVAR"
}/*EDITMODE-END*/;
const [tweaks, setTweak] = window.useTweaks(TWEAK_DEFAULTS);
return (
<>
setTweak("heroVariant", v)}
/>
setTweak("heroImage", v)}
/>
setTweak("fixtureLayout", v)}
/>
setTweak("ctaCopy", v)}
/>
>
);
}
ReactDOM.createRoot(document.getElementById("root")).render();