diff --git a/README.md b/README.md index a1c66eb..4389893 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,13 @@ bun dev Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +The frontend consumes the Python Colombia API from `NEXT_PUBLIC_API_BASE_URL`. +If the variable is not defined, it defaults to `http://localhost:8000`. + +```bash +NEXT_PUBLIC_API_BASE_URL=http://localhost:8000 npm run dev +``` + You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. diff --git a/SPEC.md b/SPEC.md deleted file mode 100644 index 6dc7581..0000000 --- a/SPEC.md +++ /dev/null @@ -1,492 +0,0 @@ -# Arquitectura del sitio web de Python Colombia - -## Barra superior (Header) - -``` ------------------------------------------------------------------------------------------ -Logo | Comunidades | Eventos | Noticias | Blog | Podcasts | Tienda | Iniciar sesión ------------------------------------------------------------------------------------------ -``` - -* **Logo** - - * Redirige siempre al Inicio. - -* **Tienda** - - * Botón destacado (Call To Action). - * Puede tener un icono de carrito. - -* **Iniciar sesión** - - * Si el usuario está autenticado: - - * Mi perfil - * Mis publicaciones - * Panel Administrativo (según permisos) - * Cerrar sesión - ---- - -# Inicio (Home) - -La página principal debe responder inmediatamente: - -> ¿Qué es Python Colombia y qué está pasando actualmente? - -## 1. Hero - -Un componente principal con: - -* Imagen o ilustración -* Slogan - -Ejemplo - -> La comunidad oficial de Python en Colombia. -> -> Aprende, comparte y construye con miles de desarrolladores. - -Botones: - -* Únete a la comunidad -* Próximos eventos - ---- - -## 2. Próximos eventos - -Mostrar los próximos eventos. - -Información: - -* Ciudad -* Fecha -* Lugar -* Cupos -* Botón "Ver evento" - ---- - -## 3. Comunidades - -Mapa de Colombia o tarjetas por ciudad. - -Ejemplo - -``` -Bogotá -450 miembros - -Medellín -280 miembros - -Cali -180 miembros - -Barranquilla -95 miembros -``` - -Cada comunidad tendrá: - -* Descripción -* Organizadores -* Próximos eventos -* Miembros -* Redes sociales - ---- - -## 4. Historia - -Una sección sobre: - -* Nacimiento de Python Colombia -* Objetivos -* Misión -* Organización -* Cómo participar - ---- - -## 5. Newsletter - -Formulario simple - -``` -Nombre - -Correo - -[Suscribirme] -``` - -Explicar: - -* Noticias -* Eventos -* Convocatorias -* Podcast -* Blogs destacados - ---- - -## 6. Últimas noticias - -Mostrar las últimas publicaciones. - -Cada tarjeta: - -* Imagen -* Categoría -* Fecha -* Autor -* Resumen -* Leer más - ---- - -## 7. Blogs destacados - -Últimos artículos escritos por la comunidad. - -Mostrar: - -* Autor -* Tiempo de lectura -* Etiquetas -* Likes -* Comentarios - ---- - -## 8. Últimos podcasts - -Tarjetas con: - -* Invitado -* Duración -* Plataformas (Spotify, YouTube, etc.) -* Escuchar - ---- - -## 9. Patrocinadores - -Empresas que apoyan la comunidad. - -Ejemplo: - -* Microsoft -* JetBrains -* PyCharm -* AWS -* Google Cloud - ---- - -## 10. Footer - -* Acerca de -* Código de conducta -* Contacto -* GitHub -* Discord -* LinkedIn -* YouTube -* Política de privacidad - ---- - -# Comunidades - -Listado de comunidades por ciudad. - -Cada comunidad tendrá: - -* Banner -* Descripción -* Organizadores -* Número de miembros -* Próximos eventos -* Historial de eventos -* Fotos -* Patrocinadores locales - ---- - -# Eventos - -Filtros: - -* Ciudad -* Fecha -* Tipo -* Virtual -* Presencial - -Cada evento tendrá: - -* Agenda -* Speakers -* Patrocinadores -* Registro -* Materiales -* Fotografías -* Grabaciones - -Separar: - -* Próximos eventos -* Eventos pasados - ---- - -# Noticias - -Administradas por la célula de Newsletter. - -Cada noticia incluye: - -* Autor -* Fecha -* Categoría -* Imagen -* Contenido -* Comentarios (opcional) - ---- - -# Blog - -Contenido generado por la comunidad. - -Si el usuario inicia sesión: - -``` -[ Publicar artículo ] -``` - -Flujo - -``` -Borrador - ↓ -En revisión - ↓ -Aprobado - ↓ -Publicado -``` - -Características: - -* Markdown -* Resaltado de código -* Imágenes -* Etiquetas -* Series de artículos -* Perfil del autor - ---- - -# Podcasts - -Administrados por la célula de Podcast. - -Cada episodio: - -* Invitados -* Resumen -* Recursos -* Plataformas -* Video -* Audio - ---- - -# Tienda - -Productos oficiales. - -Categorías - -* Camisetas -* Hoodies -* Gorras -* Mugs -* Stickers -* Botellas -* Cuadernos -* Mouse pads - -Cada producto: - -* Variantes -* Tallas -* Inventario -* Precio -* Galería - ---- - -# Autenticación - -Registro mediante: - -* GitHub -* Google -* Microsoft -* Correo electrónico - -Al crear la cuenta: - -``` -Pendiente de aprobación -``` - -Un moderador aprueba el acceso. - -Roles posibles: - -* Usuario -* Autor -* Moderador -* Administrador -* Super Administrador - ---- - -# Panel Administrativo - -## Dashboard - -* Usuarios registrados -* Suscriptores al newsletter -* Próximos eventos -* Publicaciones pendientes -* Estadísticas del sitio - ---- - -## Newsletter - -* CRUD de suscriptores -* Importar/Exportar -* Segmentación -* Historial de campañas - ---- - -## Comunidades - -CRUD de: - -* Ciudades -* Organizadores -* Redes sociales -* Información -* Logos - ---- - -## Eventos - -CRUD completo. - -Además: - -* Registro de asistentes -* Speakers -* Agenda -* Patrocinadores -* Fotografías - ---- - -## Noticias - -CRUD completo. - -Incluye: - -* Programar publicación -* Categorías -* Destacadas - ---- - -## Blogs - -Flujo editorial completo. - -Estados: - -* Borrador -* En revisión -* Requiere cambios -* Aprobado -* Publicado -* Archivado - ---- - -## Podcasts - -CRUD de episodios. - -Incluye: - -* Plataformas -* Invitados -* Recursos -* Transcripción - ---- - -## Tienda - -* Productos -* Categorías -* Inventario -* Pedidos -* Descuentos -* Cupones - ---- - -## Usuarios - -Gestión de: - -* Usuarios -* Roles -* Permisos -* Solicitudes de acceso -* Suspensiones - ---- - -## Configuración - -* Redes sociales -* Logos -* Banners -* SEO -* Analytics -* Integraciones (YouTube, Spotify, GitHub, Mailchimp/Brevo, etc.) - -## Funcionalidades adicionales recomendadas - -Para convertir el sitio en el punto central de la comunidad, incluiría además: - -* **Calendario global** con todos los eventos de Python Colombia. -* **Directorio de speakers**, donde los miembros puedan ofrecer charlas y talleres. -* **Directorio de empresas patrocinadoras** y organizaciones aliadas. -* **Página de voluntariado**, para unirse a células de trabajo (Newsletter, Podcast, Eventos, Diseño, Web, etc.). -* **Sistema de perfiles públicos**, donde cada miembro pueda mostrar su biografía, ciudad, redes sociales y publicaciones. -* **Página de recursos**, con enlaces a documentación, tutoriales, grabaciones de eventos y material de aprendizaje. -* **Galería multimedia** con fotografías y videos de eventos anteriores. -* **SEO optimizado**, con URLs amigables (`/comunidades/bogota`, `/eventos/pyday-medellin-2026`, `/blog/introduccion-a-fastapi`) y metadatos para compartir en redes sociales. -* **Buscador global**, capaz de encontrar comunidades, eventos, noticias, blogs, podcasts y miembros desde una única barra de búsqueda. diff --git a/app/globals.css b/app/globals.css index 3ed8004..0b7a38d 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,4 +1,6 @@ @import "tailwindcss"; +/* Antes de los overrides dark de la sección Leaflet para ganar en el cascade */ +@import "leaflet/dist/leaflet.css"; :root { --background: #0d0d1a; @@ -8,104 +10,176 @@ @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); - --font-sans: var(--font-geist-sans); + --font-sans: var(--font-titillium); --font-mono: var(--font-geist-mono); } +/* ── Paleta oficial (extraída de public/images/logo.png) ── + Tailwind v4 no usa tailwind.config.ts: los tokens se declaran aquí + y generan clases como bg-python-blue o text-accent-orange. */ +@theme { + /* Marca */ + --color-python-blue: #306998; + --color-python-blue-light: #4b8bbe; + --color-python-yellow: #ffd43b; + --color-python-yellow-light: #ffe873; + + /* Acentos del mosaico del logo */ + --color-accent-red: #e24329; + --color-accent-orange: #f58220; + --color-accent-green: #2fb350; + --color-accent-purple: #5c4ee5; + + /* Neutros (modo oscuro) */ + --color-night: #0d0d1a; + --color-night-soft: #12122a; + --color-night-card: #16162e; +} + +@media (prefers-reduced-motion: no-preference) { + html { + scroll-behavior: smooth; + } +} + body { background: var(--background); color: var(--foreground); - font-family: var(--font-geist-sans), Arial, Helvetica, sans-serif; + font-family: var(--font-titillium), "Segoe UI", Arial, sans-serif; } -/* ── Blob decorativo ──────────────────────────────────────── */ -.blob { - position: absolute; - border-radius: 50%; - filter: blur(100px); - opacity: 0.13; - pointer-events: none; - will-change: transform; +::selection { + background: var(--color-python-yellow); + color: var(--color-night); +} + +/* ── Utilidades editoriales de marca ──────────────────── */ + +/* Esquina inferior derecha cortada: contraste angular frente al mapa orgánico */ +@utility cut-corner { + clip-path: polygon( + 0 0, + 100% 0, + 100% calc(100% - 18px), + calc(100% - 18px) 100%, + 0 100% + ); +} + +/* Corte diagonal inferior para portadas de eventos */ +@utility clip-diagonal { + clip-path: polygon(0 0, 100% 0, 100% calc(100% - 20px), 0 100%); +} + +/* Texto contorneado para marcas de agua */ +@utility text-stroke { + -webkit-text-stroke: 1px rgb(255 255 255 / 0.1); + color: transparent; } -.blob-blue { - width: 520px; - height: 520px; - background: #4584b6; - top: -160px; - left: -160px; - animation: blob-float 11s ease-in-out infinite; +/* ── Animación de entrada al hacer scroll (componente Reveal) ── */ +.reveal { + opacity: 0; + transform: translateY(24px); + transition: + opacity 0.7s ease, + transform 0.7s ease; } -.blob-yellow { - width: 420px; - height: 420px; - background: #ffd43b; - top: -120px; - right: -120px; - animation: blob-float-reverse 13s ease-in-out infinite; +.reveal.is-visible { + opacity: 1; + transform: none; } -.blob-red { - width: 360px; - height: 360px; - background: #e63329; - bottom: -120px; - left: 8%; - animation: blob-float 15s ease-in-out infinite 2s; +@media (prefers-reduced-motion: reduce) { + .reveal { + opacity: 1; + transform: none; + transition: none; + } } -.blob-green { - width: 300px; - height: 300px; - background: #4caf50; - bottom: -80px; - right: 6%; - animation: blob-float-reverse 12s ease-in-out infinite 1s; +/* ── Leaflet (mapa del hero) ────────────────────────── */ +.leaflet-container { + background: var(--color-night); + font-family: inherit; } -/* ── Animaciones de blob ──────────────────────────────────── */ -@keyframes blob-float { - 0%, 100% { transform: translateY(0px) scale(1); } - 50% { transform: translateY(-28px) scale(1.06); } +/* Marcador cuadrado del mosaico con anillo pulsante */ +.map-marker { + background: transparent; + border: 0; +} + +.map-marker__dot { + position: absolute; + inset: 0; + border: 2px solid var(--color-night); +} + +.map-marker__ring { + position: absolute; + inset: 0; + opacity: 0.5; + animation: marker-pulse 2s ease-out infinite; +} + +@keyframes marker-pulse { + 0% { + transform: scale(1); + opacity: 0.5; + } + 100% { + transform: scale(2.4); + opacity: 0; + } +} + +@media (prefers-reduced-motion: reduce) { + .map-marker__ring { + animation: none; + opacity: 0; + } +} + +/* Popup acorde al estilo editorial (anguloso, sombra dura) */ +.leaflet-popup-content-wrapper { + background: var(--color-night); + color: var(--foreground); + border: 1px solid rgb(255 255 255 / 0.2); + border-radius: 0; + box-shadow: 8px 8px 0 0 rgb(0 0 0 / 0.45); } -@keyframes blob-float-reverse { - 0%, 100% { transform: translateY(0px) scale(1); } - 50% { transform: translateY(28px) scale(0.94); } +.leaflet-popup-content { + margin: 16px; } -/* ── Gradiente animado en el texto ───────────────────────── */ -.gradient-text { - background: linear-gradient(135deg, #ffd43b 0%, #4584b6 40%, #ffd43b 80%); - background-size: 200% 200%; - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - animation: gradient-shift 5s ease infinite; +.leaflet-popup-content p { + margin: 0; } -@keyframes gradient-shift { - 0%, 100% { background-position: 0% 50%; } - 50% { background-position: 100% 50%; } +.leaflet-popup-tip { + background: var(--color-night); + border: 1px solid rgb(255 255 255 / 0.2); + box-shadow: none; } -/* ── Glow suave en el logo ───────────────────────────────── */ -.logo-glow { - filter: drop-shadow(0 0 32px rgba(69, 132, 182, 0.25)); - transition: filter 0.4s ease; +.leaflet-container a.leaflet-popup-close-button { + color: #a1a1aa; } -.logo-glow:hover { - filter: drop-shadow(0 0 48px rgba(255, 212, 59, 0.3)); +.leaflet-container a.leaflet-popup-close-button:hover { + color: #ffffff; } -/* ── Badge «Próximamente» ────────────────────────────────── */ -@keyframes badge-ring { - 0%, 100% { box-shadow: 0 0 0 0 rgba(255, 212, 59, 0.45); } - 60% { box-shadow: 0 0 0 10px rgba(255, 212, 59, 0); } +/* Atribución OSM/CARTO discreta sobre fondo oscuro */ +.leaflet-control-attribution { + background: rgb(13 13 26 / 0.85) !important; + color: #71717a; + font-size: 10px; } -.badge-glow { - animation: badge-ring 2.8s ease-out infinite; +.leaflet-control-attribution a { + color: #a1a1aa; } diff --git a/app/layout.tsx b/app/layout.tsx index 01cba3e..40996e8 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,11 +1,13 @@ -import { Geist, Geist_Mono } from "next/font/google"; +import { Geist_Mono, Titillium_Web } from "next/font/google"; import ConsoleInit from "./ConsoleInit"; import "./globals.css"; export { metadata, viewport } from "@/lib/seo"; -const geistSans = Geist({ - variable: "--font-geist-sans", +// 300 detalles/lead · 400 cuerpo · 600 labels · 700 botones · 900 titulares +const titillium = Titillium_Web({ + variable: "--font-titillium", subsets: ["latin"], + weight: ["300", "400", "600", "700", "900"], }); const geistMono = Geist_Mono({ @@ -21,7 +23,7 @@ export default function RootLayout({ return ( diff --git a/app/page.tsx b/app/page.tsx index c681a6e..f321abd 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,89 +1,23 @@ -import Image from "next/image"; -import { asset } from "@/lib/assets"; +import Navbar from "@/components/layout/Navbar"; +import Footer from "@/components/layout/Footer"; +import Hero from "@/components/sections/Hero"; +import Communities from "@/components/sections/Communities"; +import Events from "@/components/sections/Events"; +import Sponsors from "@/components/sections/Sponsors"; +import Newsletter from "@/components/sections/Newsletter"; export default function Home() { return ( -
- - {/* ── Blobs de color decorativos ────────────────────────── */} -