Rediseño impeccable: brand mark, páginas 403/404, DESIGN.md + PRODUCT.md
Rediseño hecho en sesión aparte con el skill impeccable. Se registran las especificaciones y ajustes visuales al sistema ya en producción. Añade: - BrandMark.astro con el escudo/logo del laboratorio. - Favicon actualizado (svg + ico). - Páginas propias 403 y 404 consistentes con el sistema visual. - DESIGN.md y PRODUCT.md documentando decisiones. - .impeccable/design.json con el design system tokenizado. Ajusta componentes existentes (modales, autocomplete, layout, login, solicitudes, mis-préstamos, middleware) para alinearse a los tokens del design system. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
---
|
||||
interface Props {
|
||||
class?: string;
|
||||
}
|
||||
const { class: className = '', ...rest } = Astro.props;
|
||||
---
|
||||
<svg viewBox="0 0 24 24" class={className} fill="currentColor" fill-rule="evenodd" aria-hidden="true" {...rest}>
|
||||
<path d="M2 12 L9 4 H16.5 A2.5 2.5 0 0 1 19 6.5 V17.5 A2.5 2.5 0 0 1 16.5 20 H9 Z M7.3 12 a1.3 1.3 0 1 0 -2.6 0 a1.3 1.3 0 1 0 2.6 0 Z" />
|
||||
</svg>
|
||||
@@ -53,11 +53,9 @@ export default function EliminarMaterial({ id, nombre, tienePrestamos }: Props)
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-ghost transition-colors duration-150 hover:!text-white"
|
||||
className="btn btn-ghost hover:!bg-[color:var(--color-danger)] hover:!text-white"
|
||||
style={{ minHeight: '36px', paddingBlock: '0.25rem' }}
|
||||
onClick={open}
|
||||
onMouseEnter={(e) => (e.currentTarget.style.background = 'var(--color-danger)')}
|
||||
onMouseLeave={(e) => (e.currentTarget.style.background = 'transparent')}
|
||||
>
|
||||
Eliminar
|
||||
</button>
|
||||
|
||||
@@ -143,9 +143,11 @@ export default function MaterialForm({ mode, material, categorias }: Props) {
|
||||
type="button"
|
||||
onClick={close}
|
||||
aria-label="Cerrar"
|
||||
className="rounded-lg p-1 hover:bg-black/5 transition-colors leading-none text-xl"
|
||||
className="rounded-lg p-1.5 hover:bg-black/5 transition-colors"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true">
|
||||
<path d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -126,7 +126,9 @@ export function Autocomplete({ name, endpoint, label, placeholder, initialValue,
|
||||
/>
|
||||
{(text || value) && (
|
||||
<button type="button" className="btn btn-ghost px-3" onClick={clear} aria-label="Limpiar">
|
||||
<span aria-hidden="true">×</span>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true">
|
||||
<path d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -134,7 +136,7 @@ export function Autocomplete({ name, endpoint, label, placeholder, initialValue,
|
||||
<ul
|
||||
id={listId}
|
||||
role="listbox"
|
||||
className="absolute z-10 mt-1 w-full bg-white rounded-lg shadow-lg max-h-64 overflow-auto"
|
||||
className="absolute z-10 mt-1 w-full bg-white rounded-lg max-h-64 overflow-auto"
|
||||
style={{ border: '1px solid color-mix(in oklab, var(--color-ink) 15%, transparent)' }}
|
||||
>
|
||||
{loading && items.length === 0 && (
|
||||
|
||||
@@ -173,11 +173,8 @@ function RechazarDialog({ prestamo }: { prestamo: Prestamo }) {
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-ghost"
|
||||
className="btn btn-ghost hover:!text-[color:var(--color-danger)]"
|
||||
onClick={dlg.open}
|
||||
style={{ ['--h' as any]: 'var(--color-danger)' }}
|
||||
onMouseEnter={(e) => (e.currentTarget.style.color = 'var(--color-danger)')}
|
||||
onMouseLeave={(e) => (e.currentTarget.style.color = '')}
|
||||
>
|
||||
Rechazar
|
||||
</button>
|
||||
|
||||
@@ -55,7 +55,11 @@ export default function VerDetalles({ prestamoId }: { prestamoId: number }) {
|
||||
<div className="p-6">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<h2 className="text-lg font-semibold">Detalle del préstamo</h2>
|
||||
<button type="button" className="btn btn-ghost" onClick={() => ref.current?.close()} aria-label="Cerrar">×</button>
|
||||
<button type="button" className="rounded-lg p-1.5 hover:bg-black/5 transition-colors" onClick={() => ref.current?.close()} aria-label="Cerrar">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true">
|
||||
<path d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{loading && <p className="mt-4 text-sm opacity-70">Cargando…</p>}
|
||||
|
||||
@@ -106,9 +106,11 @@ export default function SolicitarModal({ material }: Props) {
|
||||
type="button"
|
||||
onClick={close}
|
||||
aria-label="Cerrar"
|
||||
className="rounded-lg p-1 hover:bg-black/5 transition-colors leading-none text-xl"
|
||||
className="rounded-lg p-1.5 hover:bg-black/5 transition-colors"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true">
|
||||
<path d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
import Layout from './Layout.astro';
|
||||
import Toaster from '@/components/Toaster.tsx';
|
||||
import BrandMark from '@/components/BrandMark.astro';
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
@@ -62,7 +63,9 @@ const activeCheck = (item: NavItem) => {
|
||||
<!-- 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-[color:var(--color-secondary)] grid place-items-center font-bold" aria-hidden="true">L</div>
|
||||
<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" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-semibold leading-tight">LabPréstamos</div>
|
||||
<div class="text-xs text-white/70">UABC · Lab. Sistemas</div>
|
||||
@@ -106,7 +109,9 @@ const activeCheck = (item: NavItem) => {
|
||||
<!-- 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-[color:var(--color-secondary)] grid place-items-center font-bold text-sm" aria-hidden="true">L</div>
|
||||
<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" />
|
||||
</div>
|
||||
<span class="font-semibold text-sm">LabPréstamos</span>
|
||||
</a>
|
||||
<div class="text-xs text-white/80 truncate max-w-[45%] text-right">
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ export const onRequest = defineMiddleware(async (context, next) => {
|
||||
}
|
||||
|
||||
if (pathname.startsWith('/admin') && context.locals.profile?.rol !== 'admin') {
|
||||
return new Response('Acceso denegado', { status: 403 });
|
||||
return context.rewrite('/403');
|
||||
}
|
||||
|
||||
return next();
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
import Layout from '@/layouts/Layout.astro';
|
||||
import BrandMark from '@/components/BrandMark.astro';
|
||||
|
||||
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>
|
||||
<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>
|
||||
<a href="/" class="btn btn-primary mt-6 inline-flex">Volver al inicio</a>
|
||||
</div>
|
||||
</main>
|
||||
</Layout>
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
import Layout from '@/layouts/Layout.astro';
|
||||
import BrandMark from '@/components/BrandMark.astro';
|
||||
|
||||
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>
|
||||
<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>
|
||||
<a href="/" class="btn btn-primary mt-6 inline-flex">Volver al inicio</a>
|
||||
</div>
|
||||
</main>
|
||||
</Layout>
|
||||
@@ -59,7 +59,9 @@ const tabs = [
|
||||
|
||||
{filas.length === 0 ? (
|
||||
<div class="card text-center py-12">
|
||||
<div class="text-4xl mb-3" aria-hidden="true">✓</div>
|
||||
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="var(--color-primary)" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="mx-auto mb-3" aria-hidden="true">
|
||||
<path d="M4.5 12.75l6 6 9-13.5" />
|
||||
</svg>
|
||||
<p class="font-medium">No hay solicitudes por revisar.</p>
|
||||
<p class="text-sm opacity-70 mt-1">Todo al día.</p>
|
||||
</div>
|
||||
|
||||
@@ -129,7 +129,9 @@ const badgeStyle = (e: Estado) =>
|
||||
<section>
|
||||
<details open={historial.length <= 5}>
|
||||
<summary class="text-lg font-semibold cursor-pointer list-none flex items-center gap-2">
|
||||
<span aria-hidden="true">›</span>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M9 6l6 6-6 6" />
|
||||
</svg>
|
||||
Historial <span class="text-sm opacity-60" style="font-variant-numeric: tabular-nums;">({historial.length})</span>
|
||||
</summary>
|
||||
{historial.length === 0 ? (
|
||||
@@ -174,9 +176,8 @@ const badgeStyle = (e: Estado) =>
|
||||
|
||||
<style>
|
||||
details > summary::-webkit-details-marker { display: none; }
|
||||
details[open] > summary > span[aria-hidden] { transform: rotate(90deg); }
|
||||
details > summary > span[aria-hidden] {
|
||||
display: inline-block;
|
||||
details[open] > summary > svg { transform: rotate(90deg); }
|
||||
details > summary > svg {
|
||||
transition: transform 150ms ease-out;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
import Layout from '@/layouts/Layout.astro';
|
||||
import BrandMark from '@/components/BrandMark.astro';
|
||||
|
||||
const url = Astro.url;
|
||||
const errorParam = url.searchParams.get('error');
|
||||
@@ -15,7 +16,9 @@ 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)] text-white font-bold text-3xl mb-4 motion-safe:animate-[fadein_.4s_ease-out]">L</div>
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user