Rafael Lopez

landing

......@@ -111,35 +111,35 @@
============================================ */
:root {
/* Modo claro (default) - Slate/White */
--theme-body: #fafafa;
--theme-background: #f8fafc;
--theme-titulo: #1a1a1a;
--theme-texto: #64748b;
--theme-fill: #f1f5f9;
--theme-borde: rgba(0, 0, 0, 0.08);
--theme-accent: #c9a751;
--theme-accent-hover: #b8963f;
/* Modo claro (default) - Estilo Apple, limpio y con contraste */
--theme-body: #ffffff;
--theme-background: #ffffff;
--theme-titulo: #1d1d1f;
--theme-texto: #424245;
--theme-fill: #ffffff;
--theme-borde: rgba(0, 0, 0, 0.04);
--theme-accent: #9A8550;
--theme-accent-hover: #857040;
/* Superficies alternativas */
--theme-surface: #ffffff;
--theme-surface-hover: #f8fafc;
--theme-surface-hover: #f5f5f7;
}
:root.dark {
/* Modo oscuro */
--theme-body: #0d0d0d;
--theme-background: #1a1a1a;
--theme-titulo: #e8e4d8;
--theme-texto: #b8b3a3;
--theme-fill: #2d2d2d;
--theme-borde: rgba(255, 255, 255, 0.1);
--theme-accent: #c9a751;
--theme-accent-hover: #d4b76a;
/* Modo oscuro - Cálido como el landing (#1C1C1A base) */
--theme-body: #1C1C1A;
--theme-background: #242422;
--theme-titulo: #F5F0E8;
--theme-texto: #B8B5AD;
--theme-fill: #2A2A27;
--theme-borde: rgba(255, 255, 255, 0.08);
--theme-accent: #C9A751;
--theme-accent-hover: #D4B76A;
/* Superficies alternativas */
--theme-surface: #1a1a1a;
--theme-surface-hover: #2d2d2d;
--theme-surface: #232321;
--theme-surface-hover: #2A2A27;
}
/* ============================================
......
......@@ -49,6 +49,7 @@
<div class="relative group/home">
<a
href="/"
data-sveltekit-preload-data="off"
class="nav-icon p-2 rounded-full transition-all block"
>
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
......
......@@ -35,8 +35,8 @@ const createClasificadorCache = () => {
const cache = currentCache.objetoGasto;
if (!cache.lastFetched) return null;
// Cache valid for 5 minutes
const isValid = Date.now() - cache.lastFetched < 5 * 60 * 1000;
// Cache valid for 30 minutes
const isValid = Date.now() - cache.lastFetched < 30 * 60 * 1000;
if (!isValid) return null;
return cache;
......
import { writable } from 'svelte/store';
export const isNavigating = writable(false);
export const navigationTarget = writable(null);
<script>
import '../app.css';
import { page } from '$app/stores';
import { page, navigating } from '$app/stores';
import favicon from '$lib/assets/favicon.svg';
import Navbar from '$lib/components/ui/Navbar.svelte';
import NavigationDrawer from '$lib/components/layout/NavigationDrawer.svelte';
import { openDrawer } from '$lib/stores/drawer.js';
import Spinner from '$lib/components/ui/Spinner.svelte';
let { children } = $props();
// Don't show navbar on landing page
let isLanding = $derived($page.url.pathname === '/');
// Navigation loading state
let showLoader = $state(false);
let loaderTimeout = null;
let navStartTime = null;
// Show loader after 150ms delay (avoid flash for fast navigations)
$effect(() => {
if ($navigating) {
navStartTime = performance.now();
console.log(`[NAV] Navigation started → ${$navigating.to?.url?.pathname}`);
loaderTimeout = setTimeout(() => {
showLoader = true;
}, 150);
} else {
if (loaderTimeout) clearTimeout(loaderTimeout);
if (navStartTime) {
console.log(`[NAV] Navigation complete: ${(performance.now() - navStartTime).toFixed(0)}ms`);
navStartTime = null;
}
showLoader = false;
}
});
</script>
<svelte:head>
......@@ -26,8 +50,55 @@
<Navbar onOpenDrawer={openDrawer} />
{/if}
<!-- Navigation loading overlay -->
{#if showLoader}
<div class="nav-loader">
<div class="nav-loader-content">
<Spinner size={32} color="#C9A751" />
<span class="nav-loader-text">Cargando...</span>
</div>
</div>
{/if}
<div class="min-h-screen" style="background-color: var(--theme-body);">
<main>
{@render children()}
</main>
</div>
<style>
.nav-loader {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(28, 28, 26, 0.85);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
animation: fadeIn 0.15s ease-out;
}
.nav-loader-content {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
}
.nav-loader-text {
font-family: 'DM Mono', monospace;
font-size: 13px;
color: #B8B5AD;
letter-spacing: 0.05em;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
</style>
......
<script>
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
import { supabase } from '$lib/supabase';
import {
query,
results,
......@@ -15,6 +16,13 @@
navigateResults
} from '$lib/stores/searchStore';
// Pre-warm Supabase connection (evita cold start al navegar)
async function warmupSupabase() {
console.time('[WARMUP] Supabase');
await supabase.schema('ppto').from('vista_objeto_estado').select('objeto').limit(1);
console.timeEnd('[WARMUP] Supabase');
}
let mounted = false;
let mode = 'archivo';
let searchFocused = false;
......@@ -56,6 +64,8 @@
onMount(() => {
setTimeout(() => { mounted = true; }, 80);
initIndex();
// Pre-calentar Supabase en background para evitar cold start
warmupSupabase();
});
function toggleMobileSettings() {
......@@ -791,7 +801,7 @@
<div class="descargas-formats">
<span class="format-pill">.csv</span>
<span class="format-pill">.parquet</span>
<span class="format-pill">.json</span>
<span class="format-pill">.gpkg</span>
</div>
<a href="/descargas" class="descargas-link">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 4v12"/><polyline points="8 12 12 16 16 12"/><path d="M4 18h16"/></svg>
......@@ -872,7 +882,7 @@
.hero-search{display:flex;justify-content:center;position:relative;z-index:50}
.hero-tag{font-family:var(--mono);font-size:12px;letter-spacing:0.25em;color:#B8B5AD}
.hero-title{font-size:clamp(44px,5.5vw,72px);font-weight:900;line-height:1.02;letter-spacing:-0.035em;max-width:720px;margin:14px auto 0}
.hero-sub{font-family:var(--sans);font-size:clamp(16px,1.6vw,18px);color:#9B9890;max-width:100%;margin-top:14px;line-height:1.6}
.hero-sub{font-family:var(--sans);font-size:clamp(16px,1.6vw,18px);color:#A5A29A;max-width:100%;margin-top:14px;line-height:1.6}
.hero-stats{font-family:var(--mono);font-size:13px;letter-spacing:0.04em;color:#B8B5AD;margin-top:16px;line-height:1}
.stat-num{color:#F5F0E8}
.stat-dot{margin:0 10px;opacity:0.3}
......
......@@ -2,13 +2,16 @@ import { supabase } from '$lib/supabase';
import { error } from '@sveltejs/kit';
export async function load({ params }) {
console.time('[SERVER] Total load objeto');
const { codigo } = params;
console.time('[SERVER] Query clas_objetos');
const { data, error: dbError } = await supabase
.schema('ppto')
.from('clas_objetos')
.select('*')
.eq('objeto', codigo);
console.timeEnd('[SERVER] Query clas_objetos');
if (dbError || !data || data.length === 0) {
throw error(404, 'Objeto no encontrado');
......@@ -21,6 +24,7 @@ export async function load({ params }) {
let padres = [];
let hijos = [];
console.time('[SERVER] Query padres');
// Buscar padre según nivel
if (objeto.nivel === 'subpartida') {
// Padre es partida
......@@ -58,7 +62,9 @@ export async function load({ params }) {
.eq('objeto', grupoCodigo);
if (padreData?.length) padres.unshift(padreData[0]);
}
console.timeEnd('[SERVER] Query padres');
console.time('[SERVER] Query hijos');
// Buscar hijos según nivel
if (objeto.nivel === 'grupo') {
const { data: hijosData } = await supabase
......@@ -100,7 +106,9 @@ export async function load({ params }) {
return acc;
}, []) || [];
}
console.timeEnd('[SERVER] Query hijos');
console.timeEnd('[SERVER] Total load objeto');
return {
objeto,
padres,
......
This diff is collapsed. Click to expand it.