Rediseño impeccable v2: refresh visual de todo el sistema

Segunda iteración del rediseño (sesión separada con skill impeccable).
Toca prácticamente toda la UI: layout base, tokens (global.css),
todas las páginas de alumno y admin, componentes de solicitudes,
inventario, catálogo, filtros, toaster y modales.

Docs y sistema:
- DESIGN.md y PRODUCT.md actualizados con la nueva iteración.
- .impeccable/design.json refleja tokens vigentes.
- .impeccable/review/ contiene screenshots de la revisión visual.

Dependencias:
- Añade @fontsource-variable/jetbrains-mono (fuente monoespaciada).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-08-16 17:11:20 -07:00
parent d996be5cf8
commit 0247b979ca
36 changed files with 511 additions and 468 deletions
+4 -4
View File
@@ -7,11 +7,11 @@ Astro.response.status = 403;
<Layout title="Acceso denegado — LabPréstamos">
<main id="main" class="min-h-screen grid place-items-center p-4">
<div class="w-full max-w-md text-center">
<div class="inline-grid place-items-center w-16 h-16 rounded-2xl mb-4 p-4" style="background: color-mix(in oklab, var(--color-danger) 12%, transparent);">
<BrandMark class="w-full h-full" style="color: var(--color-danger);" />
<div class="inline-grid place-items-center w-16 h-16 rounded-[2px] mb-4 p-4" style="background: var(--color-danger); border: 2px solid var(--color-ink); box-shadow: var(--shadow-hard);">
<BrandMark class="w-full h-full" style="color: var(--color-ink);" />
</div>
<h1 class="text-2xl font-semibold">Acceso denegado</h1>
<p class="text-sm opacity-70 mt-2">Esta sección es solo para administradores del laboratorio.</p>
<h1 class="text-2xl font-semibold uppercase tracking-wide">Acceso denegado</h1>
<p class="text-sm mt-2" style="color: var(--color-pencil);">Esta sección es solo para administradores del laboratorio.</p>
<a href="/" class="btn btn-primary mt-6 inline-flex">Volver al inicio</a>
</div>
</main>
+4 -4
View File
@@ -7,11 +7,11 @@ Astro.response.status = 404;
<Layout title="Página no encontrada — LabPréstamos">
<main id="main" class="min-h-screen grid place-items-center p-4">
<div class="w-full max-w-md text-center">
<div class="inline-grid place-items-center w-16 h-16 rounded-2xl bg-[color:var(--color-primary)] mb-4 p-4">
<BrandMark class="w-full h-full text-white" />
<div class="inline-grid place-items-center w-16 h-16 rounded-[2px] bg-[color:var(--color-primary)] mb-4 p-4" style="border: 2px solid var(--color-ink); box-shadow: var(--shadow-hard);">
<BrandMark class="w-full h-full" />
</div>
<h1 class="text-2xl font-semibold">Página no encontrada</h1>
<p class="text-sm opacity-70 mt-2">La dirección no existe o cambió de lugar.</p>
<h1 class="text-2xl font-semibold uppercase tracking-wide">Página no encontrada</h1>
<p class="text-sm mt-2" style="color: var(--color-pencil);">La dirección no existe o cambió de lugar.</p>
<a href="/" class="btn btn-primary mt-6 inline-flex">Volver al inicio</a>
</div>
</main>
+6 -9
View File
@@ -38,32 +38,29 @@ const estadoLabel: Record<string, string> = {
<div class="max-w-6xl">
<header class="mb-6">
<h1 class="text-2xl md:text-3xl font-semibold">Panel</h1>
<p class="text-sm opacity-70 mt-1">Resumen del laboratorio.</p>
<p class="text-sm mt-1" style="color: var(--color-pencil);">Resumen del laboratorio.</p>
</header>
<section aria-label="Indicadores" class="grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-4 mb-8">
{kpis.map((k) => (
<a
href={k.href}
class="card block group focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--color-primary)] motion-safe:hover:-translate-y-0.5 transition-transform duration-150"
>
<a href={k.href} class="card-link">
<div
class="text-4xl font-semibold leading-none"
style={`font-variant-numeric: tabular-nums; color: var(--color-${k.tone === 'ink' ? 'ink' : k.tone});`}
>
{k.value}
</div>
<div class="mt-2 text-sm opacity-70 group-hover:opacity-100 transition-opacity">{k.label}</div>
<div class="mt-2 text-sm uppercase tracking-wide" style="color: var(--color-pencil);">{k.label}</div>
</a>
))}
</section>
<section aria-labelledby="ultimas-h">
<div class="flex items-baseline justify-between mb-3">
<h2 id="ultimas-h" class="text-lg font-semibold">Últimas 5 solicitudes</h2>
<a href="/admin/solicitudes" class="text-sm underline decoration-[color:var(--color-primary)] underline-offset-4">Ver todas</a>
<h2 id="ultimas-h" class="text-lg font-semibold uppercase tracking-wide">Últimas 5 solicitudes</h2>
<a href="/admin/solicitudes" class="text-sm underline decoration-[color:var(--color-ink)] underline-offset-4">Ver todas</a>
</div>
<div class="card p-0 overflow-hidden">
<div class="card-flat p-0 overflow-hidden">
{ultimas.data && ultimas.data.length > 0 ? (
<table class="w-full text-sm">
<thead class="text-left" style="background: color-mix(in oklab, var(--color-ink) 4%, transparent);">
+1 -1
View File
@@ -41,7 +41,7 @@ const categorias = ((data ?? []) as unknown as CategoriaRow[]).map((c) => ({
{error && (
<div class="card mb-6" role="alert" style="border-color: color-mix(in oklab, var(--color-danger) 30%, transparent);">
<p class="text-sm" style="color: var(--color-danger);">
<p class="text-sm" style="color: var(--color-danger-text);">
No se pudieron cargar las categorías. Recarga la página.
</p>
</div>
+6 -6
View File
@@ -83,16 +83,16 @@ const estadoBadge = (e: Material['estado']) => {
if (e === 'disponible')
return {
label: 'Disponible',
style: 'background: color-mix(in oklab, var(--color-primary) 12%, transparent); color: var(--color-primary);',
style: 'background: color-mix(in oklab, var(--color-positive) 18%, white); color: var(--color-ink); border: 1.5px solid var(--color-positive);',
};
if (e === 'mantenimiento')
return {
label: 'Mantenimiento',
style: 'background: color-mix(in oklab, var(--color-secondary) 15%, transparent); color: var(--color-secondary-hover);',
style: 'background: color-mix(in oklab, var(--color-secondary) 18%, white); color: var(--color-ink); border: 1.5px solid var(--color-secondary);',
};
return {
label: 'Baja',
style: 'background: color-mix(in oklab, var(--color-ink) 8%, transparent); color: color-mix(in oklab, var(--color-ink) 60%, transparent);',
style: 'background: var(--color-chalk); color: var(--color-ink); border: 1.5px solid var(--color-graphite);',
};
};
---
@@ -166,7 +166,7 @@ const estadoBadge = (e: Material['estado']) => {
{matError && (
<div class="card mb-6" role="alert" style="border-color: color-mix(in oklab, var(--color-danger) 30%, transparent);">
<p class="text-sm" style="color: var(--color-danger);">
<p class="text-sm" style="color: var(--color-danger-text);">
No se pudo cargar el inventario. Recarga la página.
</p>
</div>
@@ -214,7 +214,7 @@ const estadoBadge = (e: Material['estado']) => {
{m.cantidad_disponible} / {m.cantidad_total}
</td>
<td class="px-4 py-3">
<span class="text-xs px-2 py-0.5 rounded-full whitespace-nowrap" style={badge.style}>
<span class="text-xs px-2 py-0.5 rounded-[2px] whitespace-nowrap" style={badge.style}>
{badge.label}
</span>
</td>
@@ -257,7 +257,7 @@ const estadoBadge = (e: Material['estado']) => {
<p class="text-xs opacity-70 mt-0.5">{m.categoria.nombre}</p>
)}
</div>
<span class="text-xs px-2 py-0.5 rounded-full whitespace-nowrap" style={badge.style}>
<span class="text-xs px-2 py-0.5 rounded-[2px] whitespace-nowrap" style={badge.style}>
{badge.label}
</span>
</div>
+2 -2
View File
@@ -338,7 +338,7 @@ const exportFilename = `reporte-${today}.csv`;
</td>
<td class="p-3" style="font-variant-numeric: tabular-nums;">{r.cantidad}</td>
<td class="p-3 whitespace-nowrap">{fmt(r.fecha_devolucion_estimada)}</td>
<td class="p-3 font-medium" style={`font-variant-numeric: tabular-nums; color: var(--color-danger);`}>
<td class="p-3 font-medium" style={`font-variant-numeric: tabular-nums; color: var(--color-danger-text);`}>
{dias} {dias === 1 ? 'día' : 'días'}
</td>
</tr>
@@ -361,7 +361,7 @@ const exportFilename = `reporte-${today}.csv`;
</div>
<div class="mt-2 flex items-center justify-between text-sm">
<span>Debía devolver el {fmt(r.fecha_devolucion_estimada)}</span>
<span class="font-medium" style="font-variant-numeric: tabular-nums; color: var(--color-danger);">
<span class="font-medium" style="font-variant-numeric: tabular-nums; color: var(--color-danger-text);">
{dias} {dias === 1 ? 'día' : 'días'}
</span>
</div>
+6 -6
View File
@@ -30,8 +30,8 @@ const tabs = [
<div class="flex items-center gap-3 flex-wrap">
<h1 class="text-2xl md:text-3xl font-semibold">En préstamo</h1>
<span
class="inline-flex items-center rounded-full px-2.5 py-0.5 text-sm font-semibold"
style="background: color-mix(in oklab, var(--color-primary) 12%, transparent); color: var(--color-primary); font-variant-numeric: tabular-nums;"
class="inline-flex items-center rounded-[2px] px-2.5 py-0.5 text-sm font-semibold"
style="background: var(--color-positive); color: var(--color-ink); border: 1.5px solid var(--color-ink); font-variant-numeric: tabular-nums;"
aria-label={`${filas.length} préstamos activos`}
>
{filas.length}
@@ -56,7 +56,7 @@ const tabs = [
</nav>
{error && (
<div role="alert" class="mb-4 rounded-lg p-3 text-sm" style="background: color-mix(in oklab, var(--color-danger) 12%, transparent); color: var(--color-danger);">
<div role="alert" class="mb-4 rounded-lg p-3 text-sm" style="background: color-mix(in oklab, var(--color-danger) 20%, white); color: var(--color-ink); border: 1.5px solid var(--color-danger);">
Error cargando préstamos: {error.message}
</div>
)}
@@ -108,8 +108,8 @@ const tabs = [
</span>
{vencido && (
<span
class="ml-2 inline-flex items-center rounded-full px-2 py-0.5 text-xs font-semibold"
style="background: var(--color-danger); color: white;"
class="ml-2 inline-flex items-center rounded-[2px] px-2 py-0.5 text-xs font-semibold"
style="background: var(--color-danger); color: var(--color-ink); border: 1.5px solid var(--color-ink);"
>
Vencido
</span>
@@ -136,7 +136,7 @@ const tabs = [
<div class="flex items-baseline justify-between gap-2">
<div class="font-medium">{r.alumno?.nombre ?? '—'}</div>
{vencido && (
<span class="inline-flex rounded-full px-2 py-0.5 text-xs font-semibold" style="background: var(--color-danger); color: white;">Vencido</span>
<span class="inline-flex rounded-[2px] px-2 py-0.5 text-xs font-semibold" style="background: var(--color-danger); color: var(--color-ink); border: 1.5px solid var(--color-ink);">Vencido</span>
)}
</div>
<div class="text-xs opacity-70 mb-2">{r.alumno?.matricula ?? ''} {r.alumno?.email ? `· ${r.alumno.email}` : ''}</div>
+4 -4
View File
@@ -41,8 +41,8 @@ const estadoLabel: Record<string, string> = {
<div class="flex items-center gap-3 flex-wrap">
<h1 class="text-2xl md:text-3xl font-semibold">Historial</h1>
<span
class="inline-flex items-center rounded-full px-2.5 py-0.5 text-sm font-semibold"
style="background: color-mix(in oklab, var(--color-ink) 12%, transparent); color: var(--color-ink); font-variant-numeric: tabular-nums;"
class="inline-flex items-center rounded-[2px] px-2.5 py-0.5 text-sm font-semibold"
style="background: var(--color-chalk); color: var(--color-ink); border: 1.5px solid var(--color-ink); font-variant-numeric: tabular-nums;"
aria-label={`${filas.length} registros`}
>
{filas.length}
@@ -87,7 +87,7 @@ const estadoLabel: Record<string, string> = {
</form>
{error && (
<div role="alert" class="mb-4 rounded-lg p-3 text-sm" style="background: color-mix(in oklab, var(--color-danger) 12%, transparent); color: var(--color-danger);">
<div role="alert" class="mb-4 rounded-lg p-3 text-sm" style="background: color-mix(in oklab, var(--color-danger) 20%, white); color: var(--color-ink); border: 1.5px solid var(--color-danger);">
Error cargando historial: {error.message}
</div>
)}
@@ -136,7 +136,7 @@ const estadoLabel: Record<string, string> = {
<li class="card">
<div class="flex items-baseline justify-between gap-2">
<div class="font-medium">{r.alumno?.nombre ?? '—'}</div>
<span class="text-xs rounded-full px-2 py-0.5" style="background: color-mix(in oklab, var(--color-ink) 10%, transparent);">{estadoLabel[r.estado] ?? r.estado}</span>
<span class="text-xs rounded-[2px] px-2 py-0.5" style="background: color-mix(in oklab, var(--color-ink) 10%, transparent);">{estadoLabel[r.estado] ?? r.estado}</span>
</div>
<div class="text-xs opacity-70">{r.alumno?.matricula ?? ''} {r.alumno?.email ? `· ${r.alumno.email}` : ''}</div>
<div class="text-sm mt-2">{r.material?.nombre ?? '—'} · Cantidad <strong style="font-variant-numeric: tabular-nums;">{r.cantidad}</strong></div>
+3 -3
View File
@@ -26,8 +26,8 @@ const tabs = [
<div class="flex items-center gap-3 flex-wrap">
<h1 class="text-2xl md:text-3xl font-semibold">Solicitudes pendientes</h1>
<span
class="inline-flex items-center rounded-full px-2.5 py-0.5 text-sm font-semibold"
style="background: color-mix(in oklab, var(--color-primary) 12%, transparent); color: var(--color-primary); font-variant-numeric: tabular-nums;"
class="inline-flex items-center rounded-[2px] px-2.5 py-0.5 text-sm font-semibold"
style="background: var(--color-primary); color: var(--color-ink); border: 1.5px solid var(--color-ink); font-variant-numeric: tabular-nums;"
aria-label={`${filas.length} pendientes`}
>
{filas.length}
@@ -52,7 +52,7 @@ const tabs = [
</nav>
{error && (
<div role="alert" class="mb-4 rounded-lg p-3 text-sm" style="background: color-mix(in oklab, var(--color-danger) 12%, transparent); color: var(--color-danger);">
<div role="alert" class="mb-4 rounded-lg p-3 text-sm" style="background: color-mix(in oklab, var(--color-danger) 20%, white); color: var(--color-ink); border: 1.5px solid var(--color-danger);">
Error cargando solicitudes: {error.message}
</div>
)}
+2 -2
View File
@@ -42,7 +42,7 @@ const categoriasFiltro = grupoList
{error && (
<div class="card mb-6" role="alert" style="border-color: color-mix(in oklab, var(--color-danger) 30%, transparent);">
<p class="text-sm" style="color: var(--color-danger);">
<p class="text-sm" style="color: var(--color-danger-text);">
No se pudo cargar el catálogo. Recarga la página o intenta más tarde.
</p>
</div>
@@ -76,7 +76,7 @@ const categoriasFiltro = grupoList
<div class="flex items-start justify-between gap-2">
<h3 class="font-semibold leading-snug">{m.nombre}</h3>
{m.categoria && (
<span class="text-xs px-2 py-0.5 rounded-full whitespace-nowrap" style="background: color-mix(in oklab, var(--color-primary) 10%, transparent); color: var(--color-primary);">
<span class="text-xs px-2 py-0.5 rounded-[2px] whitespace-nowrap uppercase tracking-wide" style="background: color-mix(in oklab, var(--color-primary) 25%, white); color: var(--color-ink); border: 1.5px solid var(--color-primary);">
{m.categoria.nombre}
</span>
)}
+6 -6
View File
@@ -43,15 +43,15 @@ const estadoLabel: Record<Estado, string> = {
const estadoColor: Record<Estado, string> = {
pendiente: 'var(--color-ink)',
aprobado: 'var(--color-primary)',
activo: 'var(--color-primary)',
aprobado: 'var(--color-positive)',
activo: 'var(--color-positive)',
devuelto: 'var(--color-ink)',
rechazado: 'var(--color-danger)',
vencido: 'var(--color-danger)',
};
const badgeStyle = (e: Estado) =>
`background: color-mix(in oklab, ${estadoColor[e]} 15%, transparent); color: ${estadoColor[e]};`;
`background: color-mix(in oklab, ${estadoColor[e]} 18%, white); color: var(--color-ink); border: 1.5px solid ${estadoColor[e]};`;
---
<AppLayout title="Mis préstamos — LabPréstamos">
<div class="max-w-4xl">
@@ -62,7 +62,7 @@ const badgeStyle = (e: Estado) =>
{error && (
<div class="card mb-6" role="alert" style="border-color: color-mix(in oklab, var(--color-danger) 30%, transparent);">
<p class="text-sm" style="color: var(--color-danger);">
<p class="text-sm" style="color: var(--color-danger-text);">
No se pudieron cargar tus préstamos. Recarga la página o intenta más tarde.
</p>
</div>
@@ -93,7 +93,7 @@ const badgeStyle = (e: Estado) =>
{p.material?.numero_inventario ?? '—'}
</p>
</div>
<span class="text-xs font-medium px-2 py-1 rounded-full whitespace-nowrap" style={badgeStyle(p.estado)}>
<span class="text-xs font-medium px-2 py-1 rounded-[2px] whitespace-nowrap" style={badgeStyle(p.estado)}>
{estadoLabel[p.estado]}
</span>
</div>
@@ -147,7 +147,7 @@ const badgeStyle = (e: Estado) =>
{p.material?.numero_inventario ?? '—'}
</p>
</div>
<span class="text-xs font-medium px-2 py-1 rounded-full whitespace-nowrap" style={badgeStyle(p.estado)}>
<span class="text-xs font-medium px-2 py-1 rounded-[2px] whitespace-nowrap" style={badgeStyle(p.estado)}>
{estadoLabel[p.estado]}
</span>
</div>
+17 -17
View File
@@ -8,35 +8,35 @@ const nombre = profile?.nombre ?? profile?.email?.split('@')[0] ?? '';
<AppLayout title="Inicio — LabPréstamos">
<div class="max-w-3xl">
<header class="mb-8">
<p class="text-sm opacity-60">Bienvenido{nombre ? ',' : ''}</p>
<p class="text-sm" style="color: var(--color-pencil);">Bienvenido{nombre ? ',' : ''}</p>
<h1 class="text-2xl md:text-3xl font-semibold">{nombre || 'Laboratorio de Sistemas'}</h1>
</header>
<div class="grid gap-4 md:grid-cols-2">
<div class="grid gap-5 md:grid-cols-2">
{isAdmin ? (
<>
<a href="/admin/solicitudes" class="card block hover:brightness-105 transition-[filter,transform] duration-150 motion-safe:hover:-translate-y-0.5">
<h2 class="font-semibold text-lg mb-1">Solicitudes</h2>
<p class="text-sm opacity-70">Aprobar o rechazar peticiones de material.</p>
<a href="/admin/solicitudes" class="card-link">
<h2 class="font-semibold text-lg mb-1 uppercase tracking-wide">Solicitudes</h2>
<p class="text-sm" style="color: var(--color-pencil);">Aprobar o rechazar peticiones de material.</p>
</a>
<a href="/admin/inventario" class="card block hover:brightness-105 transition-[filter,transform] duration-150 motion-safe:hover:-translate-y-0.5">
<h2 class="font-semibold text-lg mb-1">Inventario</h2>
<p class="text-sm opacity-70">Materiales y categorías del laboratorio.</p>
<a href="/admin/inventario" class="card-link">
<h2 class="font-semibold text-lg mb-1 uppercase tracking-wide">Inventario</h2>
<p class="text-sm" style="color: var(--color-pencil);">Materiales y categorías del laboratorio.</p>
</a>
<a href="/admin/reportes" class="card block hover:brightness-105 transition-[filter,transform] duration-150 motion-safe:hover:-translate-y-0.5 md:col-span-2">
<h2 class="font-semibold text-lg mb-1">Reportes</h2>
<p class="text-sm opacity-70">Historial y exportación de préstamos.</p>
<a href="/admin/reportes" class="card-link md:col-span-2">
<h2 class="font-semibold text-lg mb-1 uppercase tracking-wide">Reportes</h2>
<p class="text-sm" style="color: var(--color-pencil);">Historial y exportación de préstamos.</p>
</a>
</>
) : (
<>
<a href="/alumno/catalogo" class="card block hover:brightness-105 transition-[filter,transform] duration-150 motion-safe:hover:-translate-y-0.5">
<h2 class="font-semibold text-lg mb-1">Catálogo</h2>
<p class="text-sm opacity-70">Ver material disponible y solicitar un préstamo.</p>
<a href="/alumno/catalogo" class="card-link">
<h2 class="font-semibold text-lg mb-1 uppercase tracking-wide">Catálogo</h2>
<p class="text-sm" style="color: var(--color-pencil);">Ver material disponible y solicitar un préstamo.</p>
</a>
<a href="/alumno/mis-prestamos" class="card block hover:brightness-105 transition-[filter,transform] duration-150 motion-safe:hover:-translate-y-0.5">
<h2 class="font-semibold text-lg mb-1">Mis préstamos</h2>
<p class="text-sm opacity-70">Estado de tus solicitudes actuales e historial.</p>
<a href="/alumno/mis-prestamos" class="card-link">
<h2 class="font-semibold text-lg mb-1 uppercase tracking-wide">Mis préstamos</h2>
<p class="text-sm" style="color: var(--color-pencil);">Estado de tus solicitudes actuales e historial.</p>
</a>
</>
)}
+11 -11
View File
@@ -16,33 +16,33 @@ const googleAuthUrl = '/api/auth/signin';
<main id="main" class="min-h-screen grid place-items-center p-4">
<div class="w-full max-w-md">
<div class="text-center mb-8">
<div class="inline-grid place-items-center w-16 h-16 rounded-2xl bg-[color:var(--color-primary)] p-4 mb-4 motion-safe:animate-[fadein_.4s_ease-out]">
<BrandMark class="w-full h-full text-white" />
<div class="inline-grid place-items-center w-16 h-16 rounded-[2px] bg-[color:var(--color-primary)] p-4 mb-4 motion-safe:animate-[fadein_.4s_ease-out]" style="border: 2px solid var(--color-ink); box-shadow: var(--shadow-hard);">
<BrandMark class="w-full h-full" />
</div>
<h1 class="text-2xl md:text-3xl font-semibold">Sistema de Préstamos</h1>
<p class="text-sm mt-2 opacity-70">Laboratorio de Sistemas Computacionales · UABC</p>
<h1 class="text-2xl md:text-3xl font-semibold uppercase tracking-wide">Sistema de Préstamos</h1>
<p class="text-sm mt-2" style="color: var(--color-pencil);">Laboratorio de Sistemas Computacionales · UABC</p>
</div>
<div class="card motion-safe:animate-[fadein_.5s_ease-out]">
<h2 class="text-lg font-semibold mb-1">Inicia sesión</h2>
<p class="text-sm opacity-70 mb-6">Accede con tu cuenta institucional.</p>
<div class="card-raised motion-safe:animate-[fadein_.5s_ease-out]">
<h2 class="text-lg font-semibold mb-1 uppercase tracking-wide">Inicia sesión</h2>
<p class="text-sm mb-6" style="color: var(--color-pencil);">Accede con tu cuenta institucional.</p>
<a href={googleAuthUrl} class="btn btn-primary w-full">
<svg width="18" height="18" viewBox="0 0 48 48" aria-hidden="true" focusable="false">
<path fill="#fff" d="M44.5 20H24v8.5h11.7C34.4 33.4 29.7 36 24 36c-6.6 0-12-5.4-12-12s5.4-12 12-12c2.9 0 5.5 1 7.6 2.7l6.2-6.2C33.8 5.1 29.2 3 24 3 12.4 3 3 12.4 3 24s9.4 21 21 21c11 0 20-8 20-21 0-1.4-.1-2.7-.5-4z"/>
<path fill="var(--color-ink)" d="M44.5 20H24v8.5h11.7C34.4 33.4 29.7 36 24 36c-6.6 0-12-5.4-12-12s5.4-12 12-12c2.9 0 5.5 1 7.6 2.7l6.2-6.2C33.8 5.1 29.2 3 24 3 12.4 3 3 12.4 3 24s9.4 21 21 21c11 0 20-8 20-21 0-1.4-.1-2.7-.5-4z"/>
</svg>
Continuar con Google
</a>
{errorMsg && (
<div role="alert" class="mt-4 rounded-lg p-3 text-sm" style="background: color-mix(in oklab, var(--color-danger) 12%, transparent); color: var(--color-danger);">
<div role="alert" class="mt-4 rounded-[2px] p-3 text-sm" style="background: color-mix(in oklab, var(--color-danger) 20%, transparent); border: 1.5px solid var(--color-danger); color: var(--color-ink);">
{errorMsg}
</div>
)}
</div>
<p class="text-center text-xs opacity-60 mt-6">
Acceso exclusivo con correo institucional <strong>@uabc.edu.mx</strong>
<p class="text-center text-xs mt-6" style="color: var(--color-pencil);">
Acceso exclusivo con correo institucional <strong style="color: var(--color-ink);">@uabc.edu.mx</strong>
</p>
</div>
</main>