/ src / routes / +layout.svelte
+layout.svelte
  1  <script lang="ts">
  2    import SearchBar from './components/SearchBar.svelte';
  3    import ViewModeSelector from './components/ViewModeSelector.svelte';
  4    import PageTransition from '$lib/components/PageTransition.svelte';
  5    import { page } from '$app/stores';
  6    import { onMount, onDestroy, afterUpdate } from 'svelte';
  7    import { derived } from 'svelte/store';
  8    import '$lib/styles.css'; // Import global styles
  9    
 10    export let data;
 11    
 12    // Extraer datos de la URL para su uso reactivo
 13    $: pageUrl = $page.url;
 14    $: hasQueryParam = pageUrl.searchParams.has('query');
 15    
 16    // Detectar si estamos en la página principal (/) o mostrando resultados de búsqueda
 17    // La detección se hace ahora solo basada en la presencia del parámetro query
 18    $: isHomePage = !hasQueryParam;
 19    export {};
 20    
 21    // Manejar transiciones de página - derivar de page.url para crear una clave única
 22    // para las transiciones
 23    const pageTransitionKey = derived(page, (pageStore) => {
 24      return pageStore.url.pathname + '?' + pageStore.url.searchParams;
 25    });
 26  
 27    // Estado de navegación para animaciones
 28    let isNavigating = false;
 29    let navigationTimeout: number | undefined;
 30    
 31    // Indicador de primera carga para deshabilitar animaciones la primera vez
 32    let isFirstLoad = true;
 33    
 34    // Función para manejar transiciones de navegación
 35    function handleNavigation() {
 36      if (!isFirstLoad) {
 37        isNavigating = true;
 38        
 39        // Permitir que terminen las animaciones antes de restablecer
 40        if (navigationTimeout) clearTimeout(navigationTimeout);
 41        navigationTimeout = window.setTimeout(() => {
 42          isNavigating = false;
 43        }, 500); // Duración ligeramente mayor que la animación más larga
 44      }
 45    }
 46    
 47    // Detectar cambios en la URL para aplicar transiciones
 48    // Reaccionar a cambios en la URL
 49    $: if (pageUrl) {
 50      if (!isFirstLoad) {
 51        handleNavigation();
 52      }
 53    }
 54    
 55    // Inicializar
 56    onMount(() => {
 57      // Habilitar transiciones después de la carga inicial
 58      setTimeout(() => {
 59        document.body.classList.add('transitions-enabled');
 60        isFirstLoad = false;
 61      }, 300);
 62      
 63      // Evento para detectar inicio de navegación
 64      const handleBeforeNavigate = () => {
 65        handleNavigation();
 66      };
 67      
 68      // Añadir listener para eventos de navegación SvelteKit
 69      window.addEventListener('sveltekit:navigation-start', handleBeforeNavigate);
 70      
 71      return () => {
 72        window.removeEventListener('sveltekit:navigation-start', handleBeforeNavigate);
 73      };
 74    });
 75    
 76    onDestroy(() => {
 77      if (navigationTimeout) clearTimeout(navigationTimeout);
 78    });
 79    
 80    // Hacer scroll al principio cuando cambia la página
 81    afterUpdate(() => {
 82      if (isNavigating && !isFirstLoad) {
 83        window.scrollTo({ top: 0, behavior: 'smooth' });
 84      }
 85    });
 86  </script>
 87  
 88  <div class="app-container {isNavigating ? 'is-navigating' : ''}">
 89    {#if isHomePage}
 90      <!-- Página de inicio - Barra de búsqueda centrada -->
 91      <div class="home-container">
 92        <div class="home-hero">
 93          <div class="home-logo-large" in:fly={{ y: -30, duration: 600, delay: 100 }}>
 94            <img src="/logo.png" alt="Logo" class="logo-img-large" />
 95            <h1 class="home-title">CelestiumOS</h1>
 96          </div>
 97          <p class="home-tagline" in:fly={{ y: 10, duration: 500, delay: 200 }}>El buscador que respeta tu privacidad</p>
 98          <p class="home-privacy" in:fly={{ y: 10, duration: 500, delay: 300 }}>
 99            <span class="privacy-feature">✓ Sin rastreadores</span>
100            <span class="privacy-feature">✓ Sin registro de búsquedas</span>
101            <span class="privacy-feature">✓ Sin perfiles de usuario</span>
102          </p>
103          <div class="home-search" in:fly={{ y: 20, duration: 500, delay: 300 }}>
104            <SearchBar />
105          </div>
106        </div>
107      </div>
108    {:else}
109      <!-- Página de resultados - Utilizar el componente SearchBar que ya contiene la barra de navegación -->
110      <div class="search-header-wrapper">
111        <SearchBar />
112        
113        <!-- Controles de vista (columnas) ahora ubicados en la parte inferior izquierda -->
114        <!-- Los controles se eliminan de aquí y se moverán a +page.svelte para la página de resultados -->
115      </div>
116    {/if}
117  
118    <main class="app-main {isHomePage ? 'home-main' : 'results-main'}" style="margin-top: {isHomePage ? '0' : 'calc(var(--header-height) + 50px)'};">
119      <PageTransition key={$pageTransitionKey}>
120        <slot />
121      </PageTransition>
122    </main>
123  
124    {#if isHomePage || !hasQueryParam}
125    <footer class="app-footer">
126      <div class="footer-content">
127        <p>© 2025 CelestiumOS. Todos los derechos reservados.</p>
128        <div class="footer-links">
129          <a href="/about" aria-label="Acerca de">Acerca de</a>
130          <a href="/privacy" aria-label="Privacidad">Privacidad</a>
131          <a href="/terms" aria-label="Términos">Términos</a>
132          <a href="/contact" aria-label="Contacto">Contacto</a>
133        </div>
134      </div>
135    </footer>
136    {/if}
137  </div>
138  
139  <style>
140  /* Transiciones suaves */
141  :global(body) {
142    transition: none; /* Deshabilitar transiciones inicialmente */
143  }
144  
145  :global(body.transitions-enabled) {
146    transition: background-color 0.3s ease, color 0.3s ease; /* Habilitar solo después de carga */
147  }
148  
149  :global(body.transitions-enabled *) {
150    transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
151  }
152  
153  /* Aplicar efecto de desenfoque durante la navegación */
154  .app-container.is-navigating :global(:not(.page-transition)) {
155    filter: blur(0.5px);
156    opacity: 0.98;
157    transition: filter 0.3s ease, opacity 0.3s ease;
158  }
159  
160  /* Añadir transiciones suaves */
161  .app-container {
162    transition: filter 0.3s ease, opacity 0.3s ease;
163  }
164  
165  /* Mejoras para controles de vista */
166  .view-controls {
167    position: fixed; 
168    top: calc(var(--header-height) + 12px); 
169    right: var(--space-xl); 
170    z-index: 1000; 
171    box-shadow: 0 2px 8px rgba(0,0,0,0.15); 
172    border-radius: var(--border-radius); 
173    animation: pulse-glow 2s infinite alternate;
174  }
175  
176  @keyframes pulse-glow {
177    0% {
178      box-shadow: 0 2px 8px rgba(0,0,0,0.1);
179    }
180    100% {
181      box-shadow: 0 2px 12px rgba(var(--primary-rgb), 0.5);
182    }
183  }
184  
185  /* Ajuste para asegurar que --primary-rgb está disponible */
186  :root {
187    --primary-rgb: 34, 106, 243; /* Valor por defecto que se sobrescribirá */
188  }
189  
190  /* Contenedor principal */
191  .app-container {
192    display: flex;
193    flex-direction: column;
194    min-height: 100vh;
195    background: var(--bg-primary);
196    color: var(--text-color);
197    font-family: var(--font-primary);
198    overflow-x: hidden; /* Evitar scroll horizontal */
199  }
200  
201  /* Header */
202  .app-header {
203    display: flex;
204    align-items: center;
205    justify-content: space-between;
206    padding: 0 var(--space-xl);
207    background: var(--bg-primary);
208    border-bottom: 1px solid var(--border-color);
209    position: sticky;
210    top: 0;
211    z-index: 100;
212    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
213    height: var(--header-height);
214    transition: all var(--transition-speed);
215  }
216  
217  .home-header {
218    background: transparent;
219    border: none;
220    box-shadow: none;
221  }
222  
223  .header-left,
224  .header-right {
225    display: flex;
226    align-items: center;
227  }
228  
229  .header-left {
230    flex: 1;
231    min-width: 120px;
232  }
233  
234  .logo {
235    display: flex;
236    align-items: center;
237    gap: var(--space-sm);
238    text-decoration: none;
239    transition: opacity var(--transition-speed);
240  }
241  
242  .logo:hover {
243    opacity: 0.9;
244  }
245  
246  .logo-img {
247    width: 36px;
248    height: 36px;
249    object-fit: contain;
250  }
251  
252  .logo-text {
253    font-size: 1.25rem;
254    font-weight: var(--font-weight-semibold);
255    margin: 0;
256    color: var(--primary);
257  }
258  
259  .header-right {
260    flex: 1;
261    display: flex;
262    justify-content: flex-end;
263    gap: var(--space-md);
264    min-width: 120px;
265  }
266  
267  /* Página de inicio */
268  .home-container {
269    display: flex;
270    flex-direction: column;
271    justify-content: center;
272    align-items: center;
273    padding: var(--space-md) var(--space-lg);
274    margin-top: 0;
275    min-height: 60vh; /* Suficiente altura para centrar verticalmente */
276    position: relative;
277  }
278  
279  .home-hero {
280    display: flex;
281    flex-direction: column;
282    align-items: center;
283    max-width: 650px;
284    width: 100%;
285    text-align: center;
286    justify-content: center;
287  }
288  
289  .home-logo-large {
290    display: flex;
291    flex-direction: column;
292    align-items: center;
293    margin-bottom: var(--space-md);
294  }
295  
296  .logo-img-large {
297    width: 140px; /* Más pequeño como Qwant */
298    height: 140px;
299    margin-bottom: var(--space-sm);
300    object-fit: contain;
301  }
302  
303  .home-title {
304    font-size: 2.5rem;
305    font-weight: var(--font-weight-bold);
306    margin: var(--space-md) 0 0;
307    color: var(--primary);
308    letter-spacing: -0.03em;
309  }
310  
311  .home-tagline {
312    font-size: 1.1rem;
313    color: var(--text-color);
314    font-weight: 500;
315    margin: var(--space-md) 0 var(--space-md);
316    max-width: 80%;
317    line-height: 1.4;
318  }
319  
320  .home-privacy {
321    display: flex;
322    flex-wrap: wrap;
323    justify-content: center;
324    gap: var(--space-lg);
325    margin-bottom: var(--space-lg);
326    color: var(--text-secondary);
327    font-size: 0.95rem;
328  }
329  
330  .privacy-feature {
331    display: flex;
332    align-items: center;
333    gap: var(--space-xs);
334  }
335  
336  .privacy-feature::before {
337    content: "✓";
338    color: var(--primary);
339    font-weight: bold;
340  }
341  
342  .home-search {
343    width: 100%;
344    max-width: 600px;
345    transition: all 0.3s ease;
346  }
347  
348  .home-search:focus-within {
349    transform: scale(1.02);
350  }
351  
352  /* Sección de búsqueda para páginas de resultados */
353  .search-container {
354    padding: var(--space-md) var(--space-lg);
355    background: var(--bg-secondary);
356    border-bottom: 1px solid var(--border-color);
357    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.03);
358    display: flex;
359    justify-content: center;
360    align-items: center;
361    position: relative;
362    z-index: 10;
363  }
364  
365  .search-section {
366    display: flex;
367    flex-direction: column;
368    gap: var(--space-sm);
369    width: 100%;
370    max-width: 900px;
371    margin: 0 auto;
372  }
373  
374  .search-filters {
375    display: flex;
376    justify-content: space-between;
377    width: 100%;
378  }
379  
380  .filters-group {
381    display: flex;
382    gap: var(--space-md);
383    flex-wrap: wrap;
384    width: 100%;
385    justify-content: flex-end;
386  }
387  
388  /* Barra de navegación fija */
389  .sticky-search-nav {
390    position: fixed;
391    bottom: 0;
392    left: 0;
393    right: 0;
394    background: var(--bg-primary);
395    border-top: 1px solid var(--border-color);
396    box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.08);
397    z-index: 50;
398    height: 56px;
399    transform: translateY(100%);
400    transition: transform 0.3s ease;
401    display: flex;
402    align-items: center;
403    justify-content: center;
404  }
405  
406  .sticky-search-nav.visible {
407    transform: translateY(0);
408  }
409  
410  .sticky-search-container {
411    display: flex;
412    justify-content: space-between;
413    align-items: center;
414    width: 100%;
415    max-width: 900px;
416    padding: 0 var(--space-md);
417  }
418  
419  .sticky-search-types {
420    display: flex;
421    gap: var(--space-sm);
422    overflow-x: auto;
423    scrollbar-width: none;
424    -ms-overflow-style: none;
425    padding: var(--space-xs) 0;
426  }
427  
428  .sticky-search-types::-webkit-scrollbar {
429    display: none;
430  }
431  
432  .sticky-type-button {
433    display: flex;
434    align-items: center;
435    gap: var(--space-xs);
436    background: transparent;
437    border: none;
438    color: var(--text-secondary);
439    font-size: 0.85rem;
440    padding: var(--space-xs) var(--space-sm);
441    border-radius: var(--border-radius-sm);
442    white-space: nowrap;
443    cursor: pointer;
444    transition: all var(--transition-speed);
445  }
446  
447  .sticky-type-button:hover {
448    color: var(--primary);
449    background: rgba(var(--primary-rgb), 0.05);
450  }
451  
452  .sticky-type-button.active {
453    color: var(--primary);
454    background: rgba(var(--primary-rgb), 0.1);
455    font-weight: var(--font-weight-medium);
456  }
457  
458  .sticky-type-button svg {
459    opacity: 0.8;
460  }
461  
462  .sticky-search-actions {
463    display: flex;
464    align-items: center;
465    gap: var(--space-sm);
466  }
467  
468  .theme-controls {
469    display: flex;
470    align-items: center;
471    gap: var(--space-xs);
472  }
473  
474  /* Main y Footer */
475  .app-main {
476    flex: 1;
477    padding: var(--space-md) var(--space-xl); /* Reducido de lg a md */
478    max-width: var(--content-max-width);
479    margin: 0 auto;
480    width: 100%;
481    box-sizing: border-box;
482    overflow-x: hidden;
483    padding-bottom: var(--space-md); /* Reducido para minimizar espacio */
484    display: flex;
485    flex-direction: column;
486  }
487  
488  .home-main {
489    padding: 0;
490    display: flex;
491    justify-content: center;
492    flex: 0 1 auto;
493    max-height: 55vh; /* Reducido para dejar más espacio al footer */
494  }
495  
496  .app-footer {
497    background: var(--bg-primary);
498    padding: var(--space-sm) var(--space-lg);
499    border-top: 1px solid var(--border-color);
500    position: static;
501    padding-bottom: var(--space-sm);
502    width: 100%;
503    /* Sin margen para evitar problemas con el scroll y detección duplicada */
504  }
505  
506  .footer-content {
507    max-width: var(--content-max-width);
508    margin: 0 auto;
509    display: flex;
510    justify-content: space-between;
511    align-items: center;
512    flex-wrap: wrap;
513    gap: var(--space-md);
514  }
515  
516  .footer-content p {
517    font-size: 0.85rem;
518    color: var(--text-secondary);
519    margin: 0;
520  }
521  
522  .footer-links {
523    display: flex;
524    gap: var(--space-xl);
525  }
526  
527  .footer-links a {
528    font-size: 0.85rem;
529    color: var(--text-secondary);
530    text-decoration: none;
531    transition: all var(--transition-speed);
532    padding: var(--space-xs) 0;
533    position: relative;
534  }
535  
536  .footer-links a:hover {
537    color: var(--primary);
538  }
539  
540  .footer-links a:hover::after {
541    content: "";
542    position: absolute;
543    left: 0;
544    bottom: 0;
545    width: 100%;
546    height: 1px;
547    background-color: var(--primary);
548    transform: scaleX(1);
549    transform-origin: left;
550    transition: transform var(--transition-speed);
551  }
552  
553  /* Responsividad */
554  @media (max-width: 1024px) {
555    .app-main {
556      padding: var(--space-md) var(--space-lg);
557      padding-bottom: var(--space-md);
558    }
559  }
560  
561  @media (max-width: 768px) {
562    .app-header {
563      padding: 0 var(--space-md);
564    }
565    
566    .home-container {
567      padding: var(--space-md) var(--space-md);
568      min-height: 50vh;
569    }
570    
571    .logo-img-large {
572      width: 120px;
573      height: 120px;
574    }
575    
576    .home-title {
577      font-size: 2.2rem;
578    }
579    
580    .home-tagline {
581      font-size: 1rem;
582    }
583    
584    .home-privacy {
585      gap: var(--space-md);
586      font-size: 0.9rem;
587      flex-direction: column;
588      align-items: center;
589    }
590    
591    .search-container {
592      padding: var(--space-sm) var(--space-md);
593    }
594    
595    .search-section {
596      gap: var(--space-xs);
597    }
598    
599    .search-filters {
600      flex-direction: column;
601      gap: var(--space-xs);
602    }
603    
604    .filters-group {
605      justify-content: flex-start;
606    }
607    
608    .app-main {
609      padding: var(--space-md);
610      padding-bottom: var(--space-md);
611    }
612    
613    .app-footer {
614      padding: var(--space-sm) var(--space-md);
615      padding-bottom: var(--space-sm);
616    }
617    
618    .footer-content {
619      flex-direction: column;
620      text-align: center;
621      gap: var(--space-md);
622    }
623    
624    .footer-links {
625      margin: 0 auto;
626      flex-wrap: wrap;
627      justify-content: center;
628      gap: var(--space-lg);
629    }
630    
631    /* Ajustes para barra fija en móvil */
632    .sticky-type-button span {
633      font-size: 0.75rem;
634    }
635  }
636  
637  @media (max-width: 480px) {
638    .app-header {
639      flex-wrap: wrap;
640      gap: var(--space-xs);
641      height: auto;
642      padding: var(--space-xs) var(--space-sm);
643    }
644    
645    .header-left {
646      flex: 1 0 100%;
647      justify-content: center;
648      margin-bottom: var(--space-xs);
649    }
650    
651    .header-right {
652      flex: 1 0 100%;
653      justify-content: center;
654    }
655    
656    .home-container {
657      padding: var(--space-sm) var(--space-md);
658    }
659    
660    .logo-img-large {
661      width: 150px; /* Reducido de 180px */
662      height: 150px; /* Reducido de 180px */
663    }
664    
665    .home-title {
666      font-size: 2.5rem; /* Reducido de 3rem */
667    }
668    
669    .home-logo-img {
670      width: 72px;
671      height: 72px;
672    }
673    
674    .app-main {
675      padding: var(--space-sm) var(--space-xs);
676      padding-bottom: var(--space-sm);
677    }
678    
679    .search-container {
680      padding: var(--space-xs) var(--space-xs);
681    }
682    
683    .search-filters {
684      padding: 0 var(--space-xs);
685    }
686    
687    .app-footer {
688      padding: var(--space-xs) var(--space-md);
689      padding-bottom: var(--space-xs);
690    }
691    
692    /* Ajustes para barra fija en móvil */
693    .sticky-type-button span {
694      display: none;  /* Ocultar texto, mostrar solo iconos */
695    }
696    
697    .sticky-type-button {
698      padding: var(--space-xs);
699      width: 36px;
700      height: 36px;
701      display: flex;
702      align-items: center;
703      justify-content: center;
704    }
705    
706    .theme-controls {
707      scale: 0.9; /* Reducir ligeramente el tamaño */
708    }
709  }
710  
711  /* Optimizaciones para pantallas muy pequeñas */
712  @media (max-width: 350px) {
713    .app-header {
714      padding: var(--space-xs) 4px;
715    }
716    
717    .app-main {
718      padding: var(--space-xs) 4px;
719      padding-bottom: var(--space-xs);
720    }
721    
722    .search-container {
723      padding: var(--space-xs) 4px;
724    }
725    
726    .logo-img-large {
727      width: 120px; /* Reducido de 150px */
728      height: 120px; /* Reducido de 150px */
729    }
730    
731    .home-title {
732      font-size: 2.2rem; /* Reducido de 2.5rem */
733    }
734    
735    .logo-text {
736      font-size: 1.1rem;
737    }
738    
739    .footer-links {
740      gap: var(--space-md);
741    }
742    
743    .footer-links a {
744      font-size: 0.75rem;
745    }
746    
747    /* Miniaturización extrema para pantallas muy pequeñas */
748    .sticky-search-container {
749      padding: 0 var(--space-xs);
750    }
751    
752    .sticky-type-button {
753      width: 32px;
754      height: 32px;
755    }
756    
757    .sticky-type-button svg {
758      width: 14px;
759      height: 14px;
760    }
761  }
762  </style>