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
+46 -42
View File
@@ -61,26 +61,25 @@ const activeCheck = (item: NavItem) => {
<div class="min-h-screen flex flex-col md:block">
<!-- Sidebar desktop -->
<aside id="app-sidebar" class="hidden md:flex md:fixed md:top-0 md:left-0 md:w-64 md:h-screen bg-[color:var(--color-primary)] text-white md:flex-col md:z-40 sidebar-panel transition-transform duration-200 ease-out">
<a href="/" class="p-6 flex items-center gap-3 border-b border-white/10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white" aria-label="LabPréstamos — Inicio">
<div class="w-9 h-9 rounded-lg bg-white/15 grid place-items-center p-2" aria-hidden="true">
<BrandMark class="w-full h-full text-white" />
<aside id="app-sidebar" class="hidden md:flex md:fixed md:top-0 md:left-0 md:w-64 md:h-screen bg-white text-[color:var(--color-ink)] md:flex-col md:z-40 sidebar-panel transition-transform duration-200 ease-out" style="border-right: 2px solid var(--color-ink);">
<a href="/" class="p-6 flex items-center gap-3 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--color-ink)]" aria-label="LabPréstamos — Inicio" style="border-bottom: 2px solid var(--color-ink);">
<div class="w-9 h-9 rounded-[2px] bg-[color:var(--color-primary)] grid place-items-center p-2" style="border: 2px solid var(--color-ink);" aria-hidden="true">
<BrandMark class="w-full h-full" />
</div>
<div>
<div class="font-semibold leading-tight">LabPréstamos</div>
<div class="text-xs text-white/70">UABC · Lab. Sistemas</div>
<div class="font-semibold leading-tight uppercase tracking-wide text-sm">LabPréstamos</div>
<div class="text-xs" style="color: var(--color-pencil);">UABC · Lab. Sistemas</div>
</div>
</a>
<nav class="flex-1 p-3 flex flex-col gap-1" aria-label="Navegación principal">
<nav class="flex-1 p-3 flex flex-col gap-1.5" aria-label="Navegación principal">
{nav.map((item) => (
<a
href={item.href}
class:list={[
'flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium whitespace-nowrap transition-colors',
activeCheck(item)
? 'bg-white/15 text-white'
: 'text-white/85 hover:bg-white/10 hover:text-white'
'flex items-center gap-3 px-3 py-2.5 rounded-[2px] text-sm font-medium uppercase tracking-wide whitespace-nowrap transition-colors',
activeCheck(item) ? 'bg-[color:var(--color-primary)]' : 'hover:bg-[color:var(--color-chalk)]'
]}
style={activeCheck(item) ? 'border: 2px solid var(--color-ink);' : 'border: 2px solid transparent;'}
aria-current={activeCheck(item) ? 'page' : undefined}
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
@@ -90,13 +89,13 @@ const activeCheck = (item: NavItem) => {
</a>
))}
</nav>
<div class="p-3 border-t border-white/10">
<div class="p-3" style="border-top: 2px solid var(--color-ink);">
<div class="px-3 py-2 text-sm">
<div class="font-medium truncate">{profile?.nombre ?? profile?.email}</div>
<div class="text-white/70 text-xs capitalize">{profile?.rol}</div>
<div class="text-xs uppercase tracking-wide" style="color: var(--color-pencil);">{profile?.rol}</div>
</div>
<form method="POST" action="/api/auth/signout">
<button type="submit" class="w-full text-left px-3 py-2 rounded-lg text-sm hover:bg-white/10 transition-colors flex items-center gap-2">
<button type="submit" class="w-full text-left px-3 py-2 rounded-[2px] text-sm uppercase tracking-wide hover:bg-[color:var(--color-chalk)] transition-colors flex items-center gap-2">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d={ICONS.logout} />
</svg>
@@ -107,39 +106,40 @@ const activeCheck = (item: NavItem) => {
</aside>
<!-- Header mobile mínimo -->
<header class="md:hidden flex items-center justify-between px-4 py-3 bg-[color:var(--color-primary)] text-white">
<a href="/" class="flex items-center gap-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white rounded-lg" aria-label="LabPréstamos — Inicio">
<div class="w-8 h-8 rounded-lg bg-white/15 grid place-items-center p-1.5" aria-hidden="true">
<BrandMark class="w-full h-full text-white" />
<header class="md:hidden flex items-center justify-between px-4 py-3 bg-white" style="border-bottom: 2px solid var(--color-ink);">
<a href="/" class="flex items-center gap-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--color-ink)] rounded-[2px]" aria-label="LabPréstamos — Inicio">
<div class="w-8 h-8 rounded-[2px] bg-[color:var(--color-primary)] grid place-items-center p-1.5" style="border: 2px solid var(--color-ink);" aria-hidden="true">
<BrandMark class="w-full h-full" />
</div>
<span class="font-semibold text-sm">LabPréstamos</span>
<span class="font-semibold text-sm uppercase tracking-wide">LabPréstamos</span>
</a>
<div class="text-xs text-white/80 truncate max-w-[45%] text-right">
<div class="text-xs truncate max-w-[45%] text-right">
<div class="font-medium truncate">{profile?.nombre ?? profile?.email}</div>
<div class="text-white/60 capitalize">{profile?.rol}</div>
<div class="uppercase tracking-wide" style="color: var(--color-pencil);">{profile?.rol}</div>
</div>
</header>
<main id="main" class="flex-1 p-4 md:p-8 pb-28 md:pb-8 md:ml-64 sidebar-aware transition-[margin] duration-200 ease-out">
<main id="main" class="flex-1 p-4 md:p-8 pb-24 md:pb-8 md:ml-64 sidebar-aware transition-[margin] duration-200 ease-out">
<!-- Botón hamburguesa desktop: toggle sidebar -->
<button
type="button"
id="sidebar-toggle"
aria-label="Alternar menú lateral"
aria-controls="app-sidebar"
class="hidden md:inline-flex items-center justify-center w-10 h-10 rounded-lg mb-4 text-[color:var(--color-ink)]/80 hover:bg-[color:var(--color-ink)]/5 transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--color-primary)]"
class="hidden md:inline-flex items-center justify-center w-10 h-10 rounded-[2px] mb-4 hover:bg-[color:var(--color-chalk)] transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--color-ink)]"
style="border: 2px solid var(--color-ink);"
>
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d={ICONS.menu} />
</svg>
</button>
<slot />
</main>
<!-- Dock flotante mobile -->
<!-- Dock mobile: barra inferior fija, plana (sin sombra, como el nav de MotherDuck) -->
<nav
class="md:hidden fixed bottom-4 left-1/2 -translate-x-1/2 z-50 rounded-full px-2 py-2 flex items-center gap-1 backdrop-blur bg-white/95 shadow-[0_10px_30px_-8px_rgba(0,0,0,0.25)] border"
style="border-color: color-mix(in oklab, var(--color-ink) 10%, transparent); padding-bottom: calc(0.5rem + env(safe-area-inset-bottom));"
class="md:hidden fixed bottom-0 left-0 right-0 z-50 bg-white flex items-stretch justify-around"
style="border-top: 2px solid var(--color-ink); padding-bottom: env(safe-area-inset-bottom);"
aria-label="Navegación principal"
>
{nav.map((item) => {
@@ -149,29 +149,33 @@ const activeCheck = (item: NavItem) => {
href={item.href}
aria-label={item.label}
aria-current={active ? 'page' : undefined}
class:list={[
'grid place-items-center rounded-full w-11 h-11 transition-[background-color,color,transform] duration-150 motion-safe:active:scale-95',
active
? 'bg-[color:var(--color-primary)] text-white'
: 'text-[color:var(--color-ink)]/70 hover:text-[color:var(--color-ink)] hover:bg-[color:var(--color-ink)]/5'
]}
class="flex-1 grid place-items-center py-2.5"
>
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d={ICONS[item.icon as keyof typeof ICONS]} />
</svg>
<span
class:list={[
'grid place-items-center rounded-[2px] w-11 h-11 transition-[background-color,transform] duration-100 motion-safe:active:scale-95',
active ? 'bg-[color:var(--color-primary)]' : ''
]}
style={active ? 'border: 2px solid var(--color-ink);' : ''}
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d={ICONS[item.icon as keyof typeof ICONS]} />
</svg>
</span>
</a>
);
})}
<span class="w-px h-6 mx-1" style="background: color-mix(in oklab, var(--color-ink) 15%, transparent);" aria-hidden="true"></span>
<form method="POST" action="/api/auth/signout" class="contents">
<form method="POST" action="/api/auth/signout" class="flex-1">
<button
type="submit"
aria-label="Cerrar sesión"
class="grid place-items-center rounded-full w-11 h-11 text-[color:var(--color-ink)]/70 hover:text-[color:var(--color-danger)] hover:bg-[color:var(--color-danger)]/10 transition-[background-color,color,transform] duration-150 motion-safe:active:scale-95"
class="w-full grid place-items-center py-2.5"
>
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d={ICONS.logout} />
</svg>
<span class="grid place-items-center rounded-[2px] w-11 h-11 transition-[background-color,transform] duration-100 motion-safe:active:scale-95 hover:bg-[color:var(--color-danger)]/15">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d={ICONS.logout} />
</svg>
</span>
</button>
</form>
</nav>
+17 -1
View File
@@ -11,13 +11,29 @@ const { title = 'Sistema de Préstamos — Laboratorio UABC' } = Astro.props;
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#00723F" />
<meta name="theme-color" content="#f4efea" />
<meta name="color-scheme" content="light" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} />
<title>{title}</title>
</head>
<body>
<!--
THESIS: Trade the institutional-green bitácora look for MotherDuck's crayon-terminal-on-cream
neobrutalism, per a fully pinned user brief — hard edges and offset shadow instead of soft,
rounded institutional polish.
OWN-WORLD: Cream Paper canvas, Frost White cards, Charcoal Ink borders/text everywhere, Sky
Crayon as the sole filled-action color, Coral Sketch for danger, Mint Sketch for positive
status, Duck Bill Orange as sparse accent, JetBrains Mono throughout, 2px radius everywhere,
-6px/6px hard offset shadow with press-in hover on buttons and modals.
STORY: alumno/admin still request, approve, and return lab equipment; the interface now reads
like a terminal readout instead of a soft green form.
FIRST VIEWPORT: login card — cream page, white bordered card, charcoal offset shadow, uppercase
mono headline, sky-blue press button.
FORM: user-pinned brief (MotherDuck DESIGN.md) — concept roll skipped per "the brief wins."
FINISH: unreviewed and undocumented is unfinished; this build ends with the finish review, the
verdict, DESIGN.md, and every shipping raster carrying its provenance.
-->
<a href="#main" class="sr-only focus:not-sr-only focus:absolute focus:top-2 focus:left-2 btn btn-primary z-50">
Saltar al contenido
</a>