ESTILOS_UI.md
16.6 KB
Estilos UI - Dark Mode, Menu y Drawer
1. Variables CSS (app.css)
/* Light mode colors */
:root {
--color-light-body: #f5ebe0;
--color-light-background: #f3ead8;
--color-light-titulo: #363534;
--color-light-texto-auxiliar: #676767;
--color-light-fill: #f3ead8;
--color-light-borde: rgba(0, 0, 0, 0.08);
--color-light-focus-secondary: #676767;
--color-light-accent: #c9a751;
}
/* Dark mode colors */
:root.dark {
--color-light-body: #0d0d0d;
--color-light-background: #1a1a1a;
--color-light-titulo: #e8e4d8;
--color-light-texto-auxiliar: #b8b3a3;
--color-light-fill: #2d2d2d;
--color-light-borde: rgba(255, 255, 255, 0.1);
--color-light-focus-secondary: #c9a751;
--color-light-accent: #c9a751;
}
2. Inicialización del tema (app.html)
Evita el flash de tema incorrecto cargando la preferencia antes del render.
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- Theme initialization - ANTES de cargar CSS -->
<script>
(function() {
const savedTheme = localStorage.getItem('theme');
let isDark = false;
if (savedTheme) {
isDark = savedTheme === 'dark';
}
if (isDark) {
document.documentElement.classList.add('dark');
}
})();
</script>
</head>
<body>
<!-- contenido -->
</body>
</html>
3. BARRA DE CONTROLES (Pastilla con iconos)
Esta es la barra que contiene: Home + Theme Toggle + Menú Hamburguesa
Estructura completa
<script>
import { onMount } from 'svelte';
let isDark = $state(false);
let drawerOpen = $state(false);
function toggleTheme() {
isDark = !isDark;
document.documentElement.classList.toggle('dark', isDark);
localStorage.setItem('theme', isDark ? 'dark' : 'light');
}
function toggleDrawer() {
drawerOpen = !drawerOpen;
}
onMount(() => {
const savedTheme = localStorage.getItem('theme');
if (savedTheme) {
isDark = savedTheme === 'dark';
}
document.documentElement.classList.toggle('dark', isDark);
});
</script>
<!-- BARRA DE CONTROLES - Esquina superior derecha -->
<div class="fixed top-6 right-6 z-[110]">
<div class="flex items-center gap-1">
<!-- ========== BOTÓN HOME (4 cuadrados) ========== -->
<div class="relative group/home">
<a
href="/"
class="p-2 rounded-full transition-all block"
style="background-color: transparent; color: var(--color-light-texto-auxiliar);"
onmouseenter={(e) => e.currentTarget.style.color = 'var(--color-light-titulo)'}
onmouseleave={(e) => e.currentTarget.style.color = 'var(--color-light-texto-auxiliar)'}
>
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<rect x="2" y="2" width="7" height="7" rx="1.5"/>
<rect x="11" y="2" width="7" height="7" rx="1.5"/>
<rect x="2" y="11" width="7" height="7" rx="1.5"/>
<rect x="11" y="11" width="7" height="7" rx="1.5"/>
</svg>
</a>
<!-- Tooltip -->
<div class="absolute right-0 top-full mt-2 px-3 py-1.5 bg-gray-900/90 text-white text-xs rounded-lg whitespace-nowrap opacity-0 invisible group-hover/home:opacity-100 group-hover/home:visible transition-all duration-200 pointer-events-none backdrop-blur-sm">
Página principal
<div class="absolute right-3 bottom-full mb-[-4px] w-2 h-2 bg-gray-900/90 rotate-45"></div>
</div>
</div>
<!-- ========== BOTÓN THEME TOGGLE (Sol/Luna) ========== -->
<div class="relative group/theme">
<button
onclick={toggleTheme}
class="p-2 rounded-full transition-all"
style="background-color: transparent; color: var(--color-light-texto-auxiliar); cursor: pointer;"
onmouseenter={(e) => e.currentTarget.style.color = 'var(--color-light-titulo)'}
onmouseleave={(e) => e.currentTarget.style.color = 'var(--color-light-texto-auxiliar)'}
aria-label={isDark ? 'Cambiar a modo claro' : 'Cambiar a modo oscuro'}
>
{#if isDark}
<!-- LUNA (modo oscuro activo) -->
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"/>
</svg>
{:else}
<!-- SOL (modo claro activo) -->
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z" clip-rule="evenodd"/>
</svg>
{/if}
</button>
<!-- Tooltip -->
<div class="absolute right-0 top-full mt-2 px-3 py-1.5 bg-gray-900/90 text-white text-xs rounded-lg whitespace-nowrap opacity-0 invisible group-hover/theme:opacity-100 group-hover/theme:visible transition-all duration-200 pointer-events-none backdrop-blur-sm">
{isDark ? 'Cambiar a modo claro' : 'Cambiar a modo oscuro'}
<div class="absolute right-3 bottom-full mb-[-4px] w-2 h-2 bg-gray-900/90 rotate-45"></div>
</div>
</div>
<!-- ========== BOTÓN MENÚ HAMBURGUESA ========== -->
<div class="relative group/menu">
<button
onclick={toggleDrawer}
class="p-2 rounded-full transition-all"
style="background-color: transparent; color: var(--color-light-texto-auxiliar); cursor: pointer;"
onmouseenter={(e) => e.currentTarget.style.color = 'var(--color-light-titulo)'}
onmouseleave={(e) => e.currentTarget.style.color = 'var(--color-light-texto-auxiliar)'}
aria-label="Abrir menú"
>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"/>
</svg>
</button>
<!-- Tooltip -->
<div class="absolute right-0 top-full mt-2 px-3 py-1.5 bg-gray-900/90 text-white text-xs rounded-lg whitespace-nowrap opacity-0 invisible group-hover/menu:opacity-100 group-hover/menu:visible transition-all duration-200 pointer-events-none backdrop-blur-sm">
Menú
<div class="absolute right-3 bottom-full mb-[-4px] w-2 h-2 bg-gray-900/90 rotate-45"></div>
</div>
</div>
</div>
</div>
Estilos clave de cada botón
| Propiedad | Valor |
|---|---|
| Padding |
p-2 (8px) |
| Forma |
rounded-full (circular) |
| Fondo | transparent |
| Color normal | var(--color-light-texto-auxiliar) |
| Color hover | var(--color-light-titulo) |
| Tamaño icono |
w-5 h-5 (20px) |
| Transición | transition-all |
| Cursor | cursor: pointer |
Estilos del contenedor (la "pastilla")
| Propiedad | Valor |
|---|---|
| Layout | flex items-center |
| Espaciado |
gap-1 (4px entre iconos) |
| Posición | fixed top-6 right-6 |
| Z-index | z-[110] |
Tooltip (aparece abajo del icono)
| Propiedad | Valor |
|---|---|
| Posición | absolute right-0 top-full mt-2 |
| Fondo | bg-gray-900/90 |
| Texto | text-white text-xs |
| Padding | px-3 py-1.5 |
| Bordes | rounded-lg |
| Efecto | backdrop-blur-sm |
| Transición |
opacity-0 invisible → opacity-100 visible en hover |
| Flecha |
w-2 h-2 bg-gray-900/90 rotate-45 posicionada arriba |
4. VERSIÓN MÓVIL (Solo hamburguesa + theme en drawer)
En móvil, solo se muestra el botón hamburguesa. El theme toggle va dentro del drawer.
<!-- Móvil: solo hamburguesa arriba a la derecha -->
<div class="fixed top-2 right-2 z-[110] md:hidden">
<button
onclick={toggleDrawer}
class="p-2 rounded-full transition-all flex-shrink-0"
style="background-color: transparent; color: var(--color-light-texto-auxiliar); cursor: pointer;"
onmouseenter={(e) => e.currentTarget.style.color = 'var(--color-light-titulo)'}
onmouseleave={(e) => e.currentTarget.style.color = 'var(--color-light-texto-auxiliar)'}
aria-label="Abrir menú"
>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"/>
</svg>
</button>
</div>
<!-- Desktop: barra completa con home + theme + menu -->
<div class="hidden md:flex fixed top-6 right-6 z-[110]">
<!-- ... los 3 botones ... -->
</div>
5. NavigationDrawer.svelte (Drawer completo)
<script>
import { onMount } from 'svelte';
let { isOpen = $bindable(false) } = $props();
let isDarkMode = $state(false);
// Detectar cambios de tema
onMount(() => {
isDarkMode = document.documentElement.classList.contains('dark');
const observer = new MutationObserver(() => {
isDarkMode = document.documentElement.classList.contains('dark');
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['class']
});
});
function closeDrawer() {
isOpen = false;
}
function toggleTheme() {
isDarkMode = !isDarkMode;
document.documentElement.classList.toggle('dark', isDarkMode);
localStorage.setItem('theme', isDarkMode ? 'dark' : 'light');
}
</script>
<!-- Overlay (fondo oscuro) -->
{#if isOpen}
<div
class="fixed inset-0 bg-black/40 z-[200]"
style="opacity: {isOpen ? '1' : '0'}; transition: opacity 0.2s ease-out;"
onclick={closeDrawer}
></div>
{/if}
<!-- Drawer -->
<aside
class="fixed top-0 right-0 h-full w-80 border-l shadow-2xl z-[201]"
style="
background-color: var(--color-light-body);
border-color: rgba(0, 0, 0, 0.05);
transform: {isOpen ? 'translate3d(0, 0, 0)' : 'translate3d(100%, 0, 0)'};
transition: transform 0.3s cubic-bezier(0.33, 1, 0.68, 1);
"
>
<div class="flex flex-col h-full">
<!-- Header del Drawer -->
<div class="py-3 md:py-4 px-4 md:px-6 border-b flex-shrink-0 relative" style="border-color: rgba(0, 0, 0, 0.05);">
<!-- Theme Toggle - SOLO en móvil (arriba izquierda del drawer) -->
<div class="absolute top-3 md:top-4 left-4 md:left-6 md:hidden">
<button
onclick={toggleTheme}
class="p-2 rounded-full transition-all"
style="background-color: transparent; color: var(--color-light-texto-auxiliar); cursor: pointer;"
>
{#if isDarkMode}
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"/>
</svg>
{:else}
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z" clip-rule="evenodd"/>
</svg>
{/if}
</button>
</div>
<!-- Botón cerrar (X) - arriba derecha -->
<div class="group absolute top-3 md:top-4 right-4 md:right-6">
<button
onclick={closeDrawer}
class="p-2 rounded-lg transition-colors flex-shrink-0"
style="color: var(--color-light-focus-secondary); cursor: pointer;"
aria-label="Cerrar menú"
>
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
<!-- Tooltip del botón cerrar -->
<div class="absolute left-1/2 -translate-x-1/2 top-full mt-2 px-3 py-1.5 bg-gray-900/90 text-white text-xs rounded-lg whitespace-nowrap opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 pointer-events-none backdrop-blur-sm">
Cerrar menú
<div class="absolute left-1/2 -translate-x-1/2 -top-1 w-2 h-2 bg-gray-900/90 rotate-45"></div>
</div>
</div>
<!-- Logo centrado (cambia según tema) -->
<div class="flex flex-col items-center">
<img
src={isDarkMode ? "/logos_oscuro/logo.png" : "/logos_claro/logo.png"}
alt="Logo"
class="h-24 md:h-32 w-auto mb-3"
/>
</div>
</div>
<!-- Navigation -->
<nav class="flex-1 overflow-y-auto p-4">
<!-- Item de navegación con hover -->
<a
href="/"
class="w-full flex items-center gap-3 px-3 py-3 rounded-lg transition-colors text-left group"
onmouseenter={(e) => e.currentTarget.style.backgroundColor = 'var(--color-light-fill)'}
onmouseleave={(e) => e.currentTarget.style.backgroundColor = 'transparent'}
>
<svg class="w-4 h-4 flex-shrink-0" style="color: var(--color-light-focus-secondary);" fill="currentColor" viewBox="0 0 20 20">
<rect x="2" y="2" width="7" height="7" rx="1.5"/>
<rect x="11" y="2" width="7" height="7" rx="1.5"/>
<rect x="2" y="11" width="7" height="7" rx="1.5"/>
<rect x="11" y="11" width="7" height="7" rx="1.5"/>
</svg>
<span class="font-medium text-sm" style="color: var(--color-light-titulo);">Página principal</span>
</a>
<!-- Sección con título -->
<div class="mb-6 mt-4">
<h3 class="text-xs font-semibold uppercase tracking-wider mb-3 px-2" style="color: var(--color-light-focus-secondary);">
Sección
</h3>
<div class="space-y-1">
<!-- Items de navegación aquí -->
</div>
</div>
</nav>
<!-- Footer -->
<div class="p-4 border-t flex-shrink-0" style="border-color: rgba(0, 0, 0, 0.05);">
<p class="text-xs text-center" style="color: var(--color-light-focus-secondary);">
© 2026
</p>
</div>
</div>
</aside>
6. Iconos SVG
Sol (modo claro activo)
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z" clip-rule="evenodd"/>
</svg>
Luna (modo oscuro activo)
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"/>
</svg>
Hamburguesa (menú)
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"/>
</svg>
X (cerrar)
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/>
</svg>
Home (4 cuadrados)
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<rect x="2" y="2" width="7" height="7" rx="1.5"/>
<rect x="11" y="2" width="7" height="7" rx="1.5"/>
<rect x="2" y="11" width="7" height="7" rx="1.5"/>
<rect x="11" y="11" width="7" height="7" rx="1.5"/>
</svg>
7. Resumen de comportamiento
| Viewport | Qué se ve | Theme toggle |
|---|---|---|
Móvil (<768px) |
Solo hamburguesa arriba derecha | Dentro del drawer (arriba izquierda) |
Desktop (≥768px) |
Home + Theme + Hamburguesa | En la barra de controles |
Animaciones
| Elemento | Animación |
|---|---|
| Hover en iconos | Color cambia de texto-auxiliar → titulo
|
| Tooltip |
opacity 0→1, invisible→visible con duration-200
|
| Drawer abre |
translate3d(100%, 0, 0) → translate3d(0, 0, 0) con cubic-bezier(0.33, 1, 0.68, 1)
|
| Overlay |
opacity 0→1 con 0.2s ease-out
|