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 invisibleopacity-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-auxiliartitulo
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