Rafael Lopez

nuevo landing

This diff could not be displayed because it is too large.
1 <script> 1 <script>
2 import { onMount } from 'svelte'; 2 import { onMount } from 'svelte';
3 + import { goto } from '$app/navigation';
4 + import {
5 + query,
6 + results,
7 + isLoading,
8 + indexLoaded,
9 + error,
10 + selectedIndex,
11 + showResults,
12 + initIndex,
13 + performSearch,
14 + clearSearch,
15 + navigateResults
16 + } from '$lib/stores/searchStore';
3 17
4 let mounted = false; 18 let mounted = false;
5 let mode = 'archivo'; 19 let mode = 'archivo';
...@@ -12,8 +26,13 @@ ...@@ -12,8 +26,13 @@
12 let hWidget = null; 26 let hWidget = null;
13 let hGear = false; 27 let hGear = false;
14 let mobileSettingsOpen = false; 28 let mobileSettingsOpen = false;
29 + let searchInput;
30 + let debounceTimer;
15 31
16 - onMount(() => { setTimeout(() => { mounted = true; }, 80); }); 32 + onMount(() => {
33 + setTimeout(() => { mounted = true; }, 80);
34 + initIndex();
35 + });
17 36
18 function toggleMobileSettings() { 37 function toggleMobileSettings() {
19 mobileSettingsOpen = !mobileSettingsOpen; 38 mobileSettingsOpen = !mobileSettingsOpen;
...@@ -38,8 +57,82 @@ ...@@ -38,8 +57,82 @@
38 { label: 'Regalías', bg: 'rgba(160,150,170,0.09)', color: '#aaa0b0' }, 57 { label: 'Regalías', bg: 'rgba(160,150,170,0.09)', color: '#aaa0b0' },
39 ]; 58 ];
40 59
41 - function switchMode(m) { mode = m; searchVal = ''; } 60 + function switchMode(m) {
42 - function handleBlur() { setTimeout(() => { searchFocused = false; }, 200); } 61 + mode = m;
62 + searchVal = '';
63 + clearSearch();
64 + if (searchInput) searchInput.value = '';
65 + }
66 +
67 + function handleBlur() {
68 + setTimeout(() => { searchFocused = false; }, 200);
69 + }
70 +
71 + function handleInput(e) {
72 + const value = e.target.value;
73 + searchVal = value;
74 + clearTimeout(debounceTimer);
75 + debounceTimer = setTimeout(() => {
76 + performSearch(value);
77 + }, 250);
78 + }
79 +
80 + function handleKeydown(e) {
81 + if (!$showResults) return;
82 + switch (e.key) {
83 + case 'ArrowDown':
84 + e.preventDefault();
85 + navigateResults('down', $results.length);
86 + break;
87 + case 'ArrowUp':
88 + e.preventDefault();
89 + navigateResults('up', $results.length);
90 + break;
91 + case 'Enter':
92 + e.preventDefault();
93 + if ($selectedIndex >= 0 && $results[$selectedIndex]) {
94 + handleSelect($results[$selectedIndex]);
95 + }
96 + break;
97 + case 'Escape':
98 + clearSearch();
99 + searchVal = '';
100 + if (searchInput) searchInput.value = '';
101 + searchInput?.blur();
102 + break;
103 + }
104 + }
105 +
106 + function handleSelect(item) {
107 + if (item.tipo === 'entidad') {
108 + goto(`/entidad/${item.codigo}`);
109 + } else if (item.tipo === 'objeto_gasto') {
110 + goto(`/objeto/${item.codigo}`);
111 + } else if (item.tipo === 'rubro') {
112 + goto(`/rubro/${item.codigo}`);
113 + }
114 + clearSearch();
115 + searchVal = '';
116 + if (searchInput) searchInput.value = '';
117 + }
118 +
119 + function getTypeLabel(tipo) {
120 + switch (tipo) {
121 + case 'entidad': return 'ENTIDAD';
122 + case 'objeto_gasto': return 'OBJETO';
123 + case 'rubro': return 'RUBRO';
124 + default: return tipo?.toUpperCase() || '';
125 + }
126 + }
127 +
128 + function getTypeColor(tipo) {
129 + switch (tipo) {
130 + case 'entidad': return '#4A8B6E';
131 + case 'objeto_gasto': return '#C9A751';
132 + case 'rubro': return '#8B6E9B';
133 + default: return '#6B6860';
134 + }
135 + }
43 136
44 const WIDGETS_INGRESOS = [ 137 const WIDGETS_INGRESOS = [
45 { id: 'rubros', label: 'tipos de ingreso', d: 'Impuestos, regalías, ventas, créditos', t: 'Recursos por rubros', n: '324', href: '/clasificadores/rubros' }, 138 { id: 'rubros', label: 'tipos de ingreso', d: 'Impuestos, regalías, ventas, créditos', t: 'Recursos por rubros', n: '324', href: '/clasificadores/rubros' },
...@@ -155,7 +248,7 @@ ...@@ -155,7 +248,7 @@
155 <div class="hero-search anim" style="--d:520ms"> 248 <div class="hero-search anim" style="--d:520ms">
156 <div class="search-wrap"> 249 <div class="search-wrap">
157 <!-- Search bar unificado --> 250 <!-- Search bar unificado -->
158 - <div class="search-bar" class:search-focused={searchFocused} style="--accent:{accent}"> 251 + <div class="search-bar" class:search-focused={searchFocused} class:search-has-results={searchFocused && ($results.length > 0 || ($query.length >= 2 && $results.length === 0) || $isLoading)} style="--accent:{accent}">
159 <!-- Toggle integrado (desktop) --> 252 <!-- Toggle integrado (desktop) -->
160 <div class="toggle-bar toggle-desktop"> 253 <div class="toggle-bar toggle-desktop">
161 <button class="toggle-btn" class:toggle-active={mode === 'archivo'} on:click={() => switchMode('archivo')}> 254 <button class="toggle-btn" class:toggle-active={mode === 'archivo'} on:click={() => switchMode('archivo')}>
...@@ -186,14 +279,28 @@ ...@@ -186,14 +279,28 @@
186 <circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/> 279 <circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/>
187 </svg> 280 </svg>
188 <div class="search-input-wrap"> 281 <div class="search-input-wrap">
189 - <input type="text" bind:value={searchVal} on:focus={() => searchFocused = true} on:blur={handleBlur} /> 282 + <input
283 + type="text"
284 + bind:this={searchInput}
285 + bind:value={searchVal}
286 + on:input={handleInput}
287 + on:keydown={handleKeydown}
288 + on:focus={() => searchFocused = true}
289 + on:blur={handleBlur}
290 + disabled={!$indexLoaded}
291 + />
190 {#if !searchVal && !searchFocused} 292 {#if !searchVal && !searchFocused}
191 <div class="search-placeholder"> 293 <div class="search-placeholder">
192 <span class="blink-cursor" style="background:{accent}"></span> 294 <span class="blink-cursor" style="background:{accent}"></span>
193 - <span class="placeholder-text placeholder-desktop">{placeholder}</span> 295 + <span class="placeholder-text placeholder-desktop">{$indexLoaded ? placeholder : 'Cargando...'}</span>
194 - <span class="placeholder-text placeholder-mobile">{isD ? 'MEFP, Min. Salud, GAM...' : 'BOA, Publicidad, IDH...'}</span> 296 + <span class="placeholder-text placeholder-mobile">{$indexLoaded ? (isD ? 'MEFP, Min. Salud, GAM...' : 'BOA, Publicidad, IDH...') : 'Cargando...'}</span>
195 </div> 297 </div>
196 {/if} 298 {/if}
299 + {#if searchVal && !$isLoading}
300 + <button class="search-clear" on:click={() => { clearSearch(); searchVal = ''; if (searchInput) searchInput.value = ''; }}>
301 + <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
302 + </button>
303 + {/if}
197 </div> 304 </div>
198 {#if !isD} 305 {#if !isD}
199 <div class="gear-btn gear-desktop" class:gear-hover={hGear} on:mouseenter={() => hGear = true} on:mouseleave={() => hGear = false} role="button" tabindex="0"> 306 <div class="gear-btn gear-desktop" class:gear-hover={hGear} on:mouseenter={() => hGear = true} on:mouseleave={() => hGear = false} role="button" tabindex="0">
...@@ -205,6 +312,45 @@ ...@@ -205,6 +312,45 @@
205 {/if} 312 {/if}
206 </div> 313 </div>
207 314
315 + <!-- Search Results Dropdown -->
316 + {#if $isLoading}
317 + <div class="search-results">
318 + <div class="search-loading">Buscando...</div>
319 + </div>
320 + {:else if searchFocused && $results.length > 0}
321 + <div class="search-results">
322 + {#each $results as item, i}
323 + <button
324 + type="button"
325 + class="search-result-item"
326 + class:result-selected={$selectedIndex === i}
327 + on:click={() => handleSelect(item)}
328 + >
329 + <div class="result-main">
330 + <span class="result-name">{item.nombre}</span>
331 + {#if item.sigla}
332 + <span class="result-sigla">({item.sigla})</span>
333 + {/if}
334 + </div>
335 + <div class="result-meta">
336 + {#if item.codigo}
337 + <span class="result-code">{item.codigo}</span>
338 + {/if}
339 + <span class="result-type" style="color:{getTypeColor(item.tipo)}">{getTypeLabel(item.tipo)}</span>
340 + </div>
341 + </button>
342 + {/each}
343 + </div>
344 + {:else if searchFocused && $query.length >= 2 && $results.length === 0}
345 + <div class="search-results">
346 + <div class="search-no-results">Sin resultados para "{$query}"</div>
347 + </div>
348 + {/if}
349 +
350 + {#if $error}
351 + <div class="search-error">{$error}</div>
352 + {/if}
353 +
208 <!-- Mobile settings panel --> 354 <!-- Mobile settings panel -->
209 <div class="mobile-settings-panel" class:mobile-settings-panel-open={mobileSettingsOpen}> 355 <div class="mobile-settings-panel" class:mobile-settings-panel-open={mobileSettingsOpen}>
210 <div class="mobile-toggle-bar"> 356 <div class="mobile-toggle-bar">
...@@ -237,7 +383,7 @@ ...@@ -237,7 +383,7 @@
237 </div> 383 </div>
238 384
239 <!-- Scroll hint --> 385 <!-- Scroll hint -->
240 - <div class="scroll-hint anim" style="--d:700ms"> 386 + <div class="scroll-hint anim" style="--d:700ms" class:scroll-hint-hidden={searchFocused && $results.length > 0}>
241 <svg width="18" height="28" viewBox="0 0 18 28" fill="none" stroke="#B8B5AD" stroke-width="1.2" stroke-linecap="round"> 387 <svg width="18" height="28" viewBox="0 0 18 28" fill="none" stroke="#B8B5AD" stroke-width="1.2" stroke-linecap="round">
242 <rect x="1" y="1" width="16" height="26" rx="8"/><line x1="9" y1="7" x2="9" y2="12" opacity="0.7"/> 388 <rect x="1" y="1" width="16" height="26" rx="8"/><line x1="9" y1="7" x2="9" y2="12" opacity="0.7"/>
243 </svg> 389 </svg>
...@@ -659,9 +805,9 @@ ...@@ -659,9 +805,9 @@
659 .hero{background:#1C1C1A;color:#F5F0E8;flex:1;display:flex;flex-direction:column;justify-content:center;padding:40px 48px 32px;position:relative;overflow:hidden} 805 .hero{background:#1C1C1A;color:#F5F0E8;flex:1;display:flex;flex-direction:column;justify-content:center;padding:40px 48px 32px;position:relative;overflow:hidden}
660 .dot-pattern{position:absolute;inset:0;opacity:0.02;background-image:radial-gradient(circle at 1px 1px,#B8B5AD 0.5px,transparent 0);background-size:40px 40px} 806 .dot-pattern{position:absolute;inset:0;opacity:0.02;background-image:radial-gradient(circle at 1px 1px,#B8B5AD 0.5px,transparent 0);background-size:40px 40px}
661 .dot-pattern-sm{position:absolute;inset:0;opacity:0.015;background-image:radial-gradient(circle at 1px 1px,#fff 0.5px,transparent 0);background-size:32px 32px} 807 .dot-pattern-sm{position:absolute;inset:0;opacity:0.015;background-image:radial-gradient(circle at 1px 1px,#fff 0.5px,transparent 0);background-size:32px 32px}
662 - .hero-inner{max-width:1060px;margin:0 auto;width:100%;position:relative;margin-top:-24px;text-align:center} 808 + .hero-inner{max-width:1060px;margin:0 auto;width:100%;position:relative;margin-top:-80px;text-align:center}
663 - .hero-identity{margin-bottom:28px} 809 + .hero-identity{margin-bottom:20px}
664 - .hero-search{display:flex;justify-content:center} 810 + .hero-search{display:flex;justify-content:center;position:relative;z-index:50}
665 .hero-tag{font-family:var(--mono);font-size:12px;letter-spacing:0.25em;color:#B8B5AD} 811 .hero-tag{font-family:var(--mono);font-size:12px;letter-spacing:0.25em;color:#B8B5AD}
666 .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} 812 .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}
667 .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} 813 .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}
...@@ -670,9 +816,10 @@ ...@@ -670,9 +816,10 @@
670 .stat-dot{margin:0 10px;opacity:0.3} 816 .stat-dot{margin:0 10px;opacity:0.3}
671 817
672 /* Search */ 818 /* Search */
673 - .search-wrap{max-width:860px;width:100%;margin:0 auto} 819 + .search-wrap{max-width:860px;width:100%;margin:0 auto;position:relative;z-index:9999}
674 - .search-bar{display:flex;align-items:center;background:rgba(255,255,255,0.04);border-radius:16px;padding:8px 14px 8px 8px;transition:all 0.4s cubic-bezier(0.16,1,0.3,1);box-shadow:0 0 0 1px rgba(201,167,81,0.12),0 8px 32px rgba(0,0,0,0.25)} 820 + .search-bar{display:flex;align-items:center;background:rgba(255,255,255,0.04);border-radius:16px;padding:8px 14px 8px 8px;transition:all 0.4s cubic-bezier(0.16,1,0.3,1);box-shadow:0 0 0 1px rgba(201,167,81,0.12),0 8px 32px rgba(0,0,0,0.25);position:relative}
675 .search-focused{background:rgba(255,255,255,0.07);box-shadow:0 0 0 1.5px rgba(201,167,81,0.35),0 8px 40px rgba(0,0,0,0.3)} 821 .search-focused{background:rgba(255,255,255,0.07);box-shadow:0 0 0 1.5px rgba(201,167,81,0.35),0 8px 40px rgba(0,0,0,0.3)}
822 + .search-has-results{border-radius:16px 16px 0 0}
676 .toggle-bar{display:flex;border-radius:10px;padding:2px;background:rgba(0,0,0,0.2);flex-shrink:0} 823 .toggle-bar{display:flex;border-radius:10px;padding:2px;background:rgba(0,0,0,0.2);flex-shrink:0}
677 .toggle-btn{background:transparent;border:none;border-radius:8px;padding:12px 16px;cursor:pointer;display:flex;align-items:center;gap:7px;transition:all 0.3s;font-family:var(--sans);font-size:14px;font-weight:400;color:#9B9890} 824 .toggle-btn{background:transparent;border:none;border-radius:8px;padding:12px 16px;cursor:pointer;display:flex;align-items:center;gap:7px;transition:all 0.3s;font-family:var(--sans);font-size:14px;font-weight:400;color:#9B9890}
678 .toggle-btn svg{display:block;flex-shrink:0} 825 .toggle-btn svg{display:block;flex-shrink:0}
...@@ -703,6 +850,27 @@ ...@@ -703,6 +850,27 @@
703 .mobile-toggle-btn span{font-family:var(--sans);font-size:14px;font-weight:600;color:#F5F0E8} 850 .mobile-toggle-btn span{font-family:var(--sans);font-size:14px;font-weight:600;color:#F5F0E8}
704 .mobile-toggle-btn svg{margin-bottom:4px} 851 .mobile-toggle-btn svg{margin-bottom:4px}
705 .mobile-toggle-sub{font-size:11px!important;font-weight:400!important;color:#9B9890!important;font-family:var(--mono)!important;letter-spacing:0.05em} 852 .mobile-toggle-sub{font-size:11px!important;font-weight:400!important;color:#9B9890!important;font-family:var(--mono)!important;letter-spacing:0.05em}
853 +
854 + /* Search clear button */
855 + .search-clear{background:none;border:none;cursor:pointer;color:#6B6860;padding:4px;display:flex;align-items:center;justify-content:center;transition:color 0.2s;flex-shrink:0}
856 + .search-clear:hover{color:#F5F0E8}
857 +
858 + /* Search Results Dropdown */
859 + .search-results{position:absolute;top:100%;left:0;right:0;background:rgba(28,28,26,0.98);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);border:1px solid rgba(255,255,255,0.1);border-top:none;border-radius:0 0 16px 16px;max-height:320px;overflow-y:auto;z-index:9999}
860 + .search-loading,.search-no-results{padding:16px 20px;font-family:var(--sans);font-size:14px;color:#9B9890;text-align:center}
861 + .search-error{margin-top:8px;font-family:var(--sans);font-size:13px;color:#C44B3F;text-align:center}
862 +
863 + .search-result-item{width:100%;text-align:left;background:none;border:none;border-top:1px solid rgba(255,255,255,0.06);padding:14px 20px;display:flex;justify-content:space-between;align-items:center;cursor:pointer;transition:background 0.2s}
864 + .search-result-item:first-child{border-top:none}
865 + .search-result-item:hover,.result-selected{background:rgba(255,255,255,0.06)}
866 +
867 + .result-main{display:flex;align-items:center;gap:8px;flex:1;min-width:0}
868 + .result-name{font-family:var(--sans);font-size:14px;color:#F5F0E8;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
869 + .result-sigla{font-family:var(--sans);font-size:12px;color:#9B9890;flex-shrink:0}
870 +
871 + .result-meta{display:flex;align-items:center;gap:8px;flex-shrink:0;margin-left:12px}
872 + .result-code{font-family:var(--mono);font-size:10px;color:#9B9890;background:rgba(255,255,255,0.06);padding:2px 6px;border-radius:4px}
873 + .result-type{font-family:var(--mono);font-size:9px;letter-spacing:0.1em;font-weight:600}
706 .pills{display:flex;gap:8px;margin-top:16px;flex-wrap:wrap;align-items:center;justify-content:center} 874 .pills{display:flex;gap:8px;margin-top:16px;flex-wrap:wrap;align-items:center;justify-content:center}
707 .pills-label{font-family:var(--mono);font-size:11px;letter-spacing:0.1em;color:#B8B5AD;margin-right:2px} 875 .pills-label{font-family:var(--mono);font-size:11px;letter-spacing:0.1em;color:#B8B5AD;margin-right:2px}
708 .pill{font-family:var(--mono);font-size:12px;font-weight:300;letter-spacing:0.03em;border:none;border-radius:6px;padding:5px 12px;cursor:pointer;transition:all 0.25s;line-height:1.4;position:relative;overflow:hidden} 876 .pill{font-family:var(--mono);font-size:12px;font-weight:300;letter-spacing:0.03em;border:none;border-radius:6px;padding:5px 12px;cursor:pointer;transition:all 0.25s;line-height:1.4;position:relative;overflow:hidden}
...@@ -710,7 +878,8 @@ ...@@ -710,7 +878,8 @@
710 .pill:hover::before{opacity:1} 878 .pill:hover::before{opacity:1}
711 .pill:hover{transform:translateY(-1px)} 879 .pill:hover{transform:translateY(-1px)}
712 880
713 - .scroll-hint{position:absolute;bottom:28px;left:0;right:0;margin:0 auto;width:fit-content;display:flex;flex-direction:column;align-items:center;gap:10px;animation:scrollHint 2.5s ease-in-out infinite} 881 + .scroll-hint{position:absolute;bottom:28px;left:0;right:0;margin:0 auto;width:fit-content;display:flex;flex-direction:column;align-items:center;gap:10px;animation:scrollHint 2.5s ease-in-out infinite;z-index:1;transition:opacity 0.3s}
882 + .scroll-hint-hidden{opacity:0;pointer-events:none}
714 .scroll-hint span{font-family:var(--mono);font-size:10px;letter-spacing:0.2em;color:#B8B5AD;opacity:0.7} 883 .scroll-hint span{font-family:var(--mono);font-size:10px;letter-spacing:0.2em;color:#B8B5AD;opacity:0.7}
715 884
716 /* ── DIRECTORY ── */ 885 /* ── DIRECTORY ── */
...@@ -917,6 +1086,14 @@ ...@@ -917,6 +1086,14 @@
917 .search-input-wrap input{font-size:15px;padding:10px 8px} 1086 .search-input-wrap input{font-size:15px;padding:10px 8px}
918 .placeholder-desktop{display:none} 1087 .placeholder-desktop{display:none}
919 .placeholder-mobile{display:inline;font-size:14px} 1088 .placeholder-mobile{display:inline;font-size:14px}
1089 +
1090 + /* Search results mobile */
1091 + .search-results{max-height:260px;border-radius:0 0 12px 12px}
1092 + .search-result-item{padding:12px 16px}
1093 + .result-name{font-size:13px}
1094 + .result-meta{flex-direction:column;align-items:flex-end;gap:4px}
1095 + .result-code{font-size:9px}
1096 + .result-type{font-size:8px}
920 .pills{gap:6px} 1097 .pills{gap:6px}
921 .pill{font-size:11px;padding:4px 10px} 1098 .pill{font-size:11px;padding:4px 10px}
922 .scroll-hint{bottom:20px} 1099 .scroll-hint{bottom:20px}
...@@ -1001,6 +1178,10 @@ ...@@ -1001,6 +1178,10 @@
1001 .mobile-settings-btn{width:36px;height:36px} 1178 .mobile-settings-btn{width:36px;height:36px}
1002 .mobile-toggle-btn{padding:14px 10px} 1179 .mobile-toggle-btn{padding:14px 10px}
1003 .mobile-toggle-btn span{font-size:13px} 1180 .mobile-toggle-btn span{font-size:13px}
1181 + .search-results{max-height:220px}
1182 + .search-result-item{padding:10px 14px}
1183 + .result-name{font-size:12px}
1184 + .result-sigla{font-size:10px}
1004 .pills-label{display:none} 1185 .pills-label{display:none}
1005 .pill{font-size:10px;padding:4px 8px} 1186 .pill{font-size:10px;padding:4px 8px}
1006 .scroll-hint{display:none} 1187 .scroll-hint{display:none}
......