feat: public landing page with magic login, brand palette and demo build flag
Adds a public funnel landing at `/` and moves the login to `/login`, rebuilt around the panel's own colors instead of an invented palette. Landing (`src/components/landing/`, one section per file, no props): - Seven funnel sections composed by `LandingPage`. The hero's eight swatches are literally `PIE_COLORS` from `DashboardPage`, the same hex values the seed hands to avatars and services, so "your colors become your numbers" is literal. - `BrandMark` becomes the single source for the logo, replicating `public/favicon.svg`. Blue is the action surface, orange only ever marks. - `NotebookVisual` is the one deliberate exception to the palette: it is what the product replaces. - Copy drops all system vocabulary; motion comes from `src/lib/motion.ts` with a single easing, and reduced-motion resolves `initial` to the final state so a never-firing `whileInView` cannot leave a section invisible forever. Routing and bundle: - `homePathFor` is the single definition of each role's destination. - Landing, login, dashboard and calendar load lazily. Eager, the login dragged framer-motion (~40 KB gz) into every panel load and the landing downloaded recharts + FullCalendar (~187 KB gz) without charting anything. `clsx` is pinned to the `react` chunk because Rollup otherwise assigns it to `charts`, making the entry import 111 KB gz for a 200-byte utility. Responsiveness (iPhone/iPad), verified with `npm run audit:responsive`: - No touch form field below 16px, `dvh` height utilities, safe-area insets, and 40px touch targets keyed off `pointer: coarse` rather than `sm:`. - New `.ld-gutter`: `.safe-x` lives outside `@layer` and beats Tailwind's `px-*`, so it left the login's side padding at 0 on anything but an iPhone in landscape. No overflow check could see it — there was no overflow, just zero margin. The audit now guards it with a `gutter` check. - `shell-height` no longer fires on pages that legitimately scroll; the static `raw-viewport-unit` scan covers those instead. Production build: - The Dockerfile now sets `VITE_DEMO_UI=1` as a build arg. `DEMO` is a build-time constant, so without it Vite eliminated the magic login and the "Ver como…" switcher: the deployed landing promised "no registration" and led to an empty form. Verified by building both ways and diffing the bundle. - Service worker cache bumped to v2 so the orphaned pre-landing chunks get purged from returning visitors' caches. Verified: typecheck clean; unit 39/39, e2e 33/33, admin 17/17, booking 12/12, landing 13/13 (WebKit), PWA passed against the real production build. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
0b466f33f3
commit
4c19244df9
+397
-26
@@ -4,18 +4,39 @@
|
||||
|
||||
:root {
|
||||
color-scheme: light;
|
||||
/* Insets del sistema (Dynamic Island, notch, home indicator). Con
|
||||
viewport-fit=cover el contenido se dibuja bajo esas zonas, así que el
|
||||
chrome de la app tiene que descontarlas explícitamente. */
|
||||
--safe-top: env(safe-area-inset-top, 0px);
|
||||
--safe-bottom: env(safe-area-inset-bottom, 0px);
|
||||
--safe-left: env(safe-area-inset-left, 0px);
|
||||
--safe-right: env(safe-area-inset-right, 0px);
|
||||
}
|
||||
|
||||
* {
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
html {
|
||||
/* iOS reescala el texto al rotar a landscape si no se fija. */
|
||||
-webkit-text-size-adjust: 100%;
|
||||
text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* El shell del panel vive en un #root de altura fija; la landing, no. Se relaja
|
||||
solo cuando la landing está montada, así el panel conserva su altura fija.
|
||||
:has() está soportado en Safari 15.4+, dentro del rango de iOS objetivo. */
|
||||
body:has(.landing) #root {
|
||||
height: auto;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Inter, system-ui, sans-serif;
|
||||
@@ -23,25 +44,41 @@ body {
|
||||
color: #0f172a;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
/* Red de seguridad: ningún hijo debe poder desplazar la página en horizontal. */
|
||||
overflow-x: hidden;
|
||||
overscroll-behavior-y: none;
|
||||
}
|
||||
|
||||
/* Custom scrollbars */
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
/* Evita el zoom por doble toque y el retardo de 300 ms en controles. */
|
||||
button,
|
||||
a,
|
||||
[role="button"],
|
||||
label,
|
||||
summary {
|
||||
touch-action: manipulation;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #d1d5db;
|
||||
border-radius: 9999px;
|
||||
border: 2px solid transparent;
|
||||
background-clip: padding-box;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #9ca3af;
|
||||
background-clip: padding-box;
|
||||
|
||||
/* Scrollbars visibles solo con puntero fino. En táctil deben ser overlay
|
||||
(como en iOS nativo): un scrollbar de 10 px roba ancho al layout y
|
||||
descuadra el cálculo de 100% en pantallas estrechas. */
|
||||
@media (pointer: fine) {
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #d1d5db;
|
||||
border-radius: 9999px;
|
||||
border: 2px solid transparent;
|
||||
background-clip: padding-box;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #9ca3af;
|
||||
background-clip: padding-box;
|
||||
}
|
||||
}
|
||||
|
||||
/* FullCalendar overrides */
|
||||
@@ -161,6 +198,24 @@ body {
|
||||
.fc .fc-timegrid-slot {
|
||||
height: 2.4rem;
|
||||
}
|
||||
/* En táctil los slots se ajustan por tamaño de pantalla: la grilla de 08:00 a
|
||||
21:00 son 26 slots, así que una altura fija de 2.4rem produce ~1000 px y
|
||||
desborda cualquier teléfono. El calendario scrollea internamente (como
|
||||
Google Calendar) en lugar de estirar la página. */
|
||||
@media (max-width: 640px) {
|
||||
.fc .fc-timegrid-slot {
|
||||
height: 2.1rem;
|
||||
}
|
||||
}
|
||||
@media (min-width: 641px) and (max-width: 1024px) {
|
||||
.fc .fc-timegrid-slot {
|
||||
height: 2.25rem;
|
||||
}
|
||||
}
|
||||
/* El scroller interno del timegrid necesita arrastre fluido en iOS. */
|
||||
.fc .fc-scroller {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
.fc-timegrid-event {
|
||||
overflow: hidden;
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.35);
|
||||
@@ -204,24 +259,67 @@ body {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Mobile: prevent squished week/month columns by giving them a min-width.
|
||||
The card wrapper has overflow-x-auto, so wider grids scroll horizontally. */
|
||||
/* Móvil: la vista Semana necesita un ancho mínimo o las 7 columnas quedan
|
||||
ilegibles; el card la envuelve en overflow-auto, así que scrollea dentro de
|
||||
la tarjeta sin arrastrar la página. La vista Mes sí cabe en un teléfono
|
||||
(7 columnas de ~50 px), así que no se le fuerza ancho mínimo. */
|
||||
@media (max-width: 640px) {
|
||||
.fc-timeGridWeek-view .fc-scrollgrid-section > table,
|
||||
.fc-timeGridWeek-view .fc-scrollgrid {
|
||||
min-width: 640px;
|
||||
}
|
||||
.fc-dayGridMonth-view .fc-scrollgrid-section > table,
|
||||
.fc-dayGridMonth-view .fc-scrollgrid {
|
||||
min-width: 560px;
|
||||
}
|
||||
/* bigger touch targets for events */
|
||||
/* Objetivos táctiles más grandes para los eventos */
|
||||
.fc-event {
|
||||
min-height: 28px;
|
||||
min-height: 30px;
|
||||
padding: 3px 6px !important;
|
||||
}
|
||||
.fc .fc-button {
|
||||
padding: 0.4rem 0.6rem;
|
||||
padding: 0.45rem 0.6rem;
|
||||
}
|
||||
/* Etiquetas de hora más estrechas: en 390 px cada píxel del eje cuenta. */
|
||||
.fc .fc-timegrid-axis,
|
||||
.fc .fc-timegrid-slot-label {
|
||||
font-size: 0.65rem;
|
||||
}
|
||||
.fc .fc-timegrid-axis-frame,
|
||||
.fc .fc-timegrid-slot-label-frame {
|
||||
padding-right: 2px;
|
||||
}
|
||||
.fc .fc-col-header-cell-cushion {
|
||||
padding: 0.45rem 0;
|
||||
font-size: 0.65rem;
|
||||
}
|
||||
/* La vista Lista es la más cómoda en teléfono: más aire al tocar. */
|
||||
.fc .fc-list-event {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.fc .fc-list-event td {
|
||||
padding: 0.7rem 0.6rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tablet (iPad portrait y mini): la semana completa cabe sin scroll lateral,
|
||||
solo hay que evitar que herede el ancho mínimo del teléfono. */
|
||||
@media (min-width: 641px) and (max-width: 1024px) {
|
||||
.fc-timeGridWeek-view .fc-scrollgrid,
|
||||
.fc-dayGridMonth-view .fc-scrollgrid {
|
||||
min-width: 0;
|
||||
}
|
||||
.fc .fc-toolbar-title {
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
.fc .fc-button {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Toolbar del calendario tocable en cualquier pantalla táctil. Va por tipo de
|
||||
puntero y no por ancho: un iPad en landscape mide 1180px (tier desktop) pero
|
||||
se sigue tocando con el dedo. No se hereda el min-height de .btn (44px) para
|
||||
no disparar el alto de la barra de navegación del calendario. */
|
||||
@media (pointer: coarse) {
|
||||
.fc .fc-button {
|
||||
min-height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,8 +379,45 @@ body {
|
||||
.btn-danger:hover:not(:disabled) {
|
||||
background: #fecaca;
|
||||
}
|
||||
/* ---- Safe areas y alturas de viewport ---- */
|
||||
|
||||
.safe-area-bottom {
|
||||
padding-bottom: max(0.75rem, env(safe-area-inset-bottom));
|
||||
padding-bottom: max(0.75rem, var(--safe-bottom));
|
||||
}
|
||||
/* Chrome superior (headers fijos): descuenta la Dynamic Island / notch. */
|
||||
.safe-top {
|
||||
padding-top: var(--safe-top);
|
||||
}
|
||||
/* Landscape con notch: el contenido no debe quedar bajo la esquina redondeada. */
|
||||
.safe-x {
|
||||
padding-left: var(--safe-left);
|
||||
padding-right: var(--safe-right);
|
||||
}
|
||||
/* Altura de viewport estable en iOS. 100vh NO descuenta la barra de URL
|
||||
dinámica de Safari, así que el shell queda más alto que el área visible y
|
||||
la página se puede desplazar. dvh sí la descuenta; vh queda como fallback. */
|
||||
.h-screen-safe {
|
||||
height: 100vh;
|
||||
height: 100dvh;
|
||||
}
|
||||
.max-h-screen-safe {
|
||||
max-height: 100vh;
|
||||
max-height: 100dvh;
|
||||
}
|
||||
.min-h-screen-safe {
|
||||
min-height: 100vh;
|
||||
min-height: 100dvh;
|
||||
}
|
||||
/* Franjas con scroll lateral (chips de filtro) sin barra visible: en móvil la
|
||||
barra roba alto y el gesto de arrastre ya comunica que hay más contenido. */
|
||||
.scrollbar-none {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
.scrollbar-none::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
@layer components {
|
||||
@@ -305,6 +440,242 @@ body {
|
||||
border-color: #3b66ff;
|
||||
box-shadow: 0 0 0 3px rgba(59, 102, 255, 0.15);
|
||||
}
|
||||
|
||||
/* ---- Landing pública ----
|
||||
Scope propio porque el panel vive en #f6f7fb y la landing tiene su propia
|
||||
identidad. Va DENTRO de la capa (el default del repo) para que cualquier
|
||||
utilidad de Tailwind aplicada en el JSX gane por especificidad de capa.
|
||||
|
||||
La paleta NO se inventa: son los colores que la plataforma ya usa. `--ld-azul` y
|
||||
`--ld-naranja` son exactamente el cuadrado y el punto del logo
|
||||
(`public/favicon.svg`), la tinta y los fondos son los del panel, y los seis tonos
|
||||
de acento son los que el seed asigna a los avatares de empleados y a los
|
||||
servicios (`server/lib/templates.ts`).
|
||||
Por eso el hilo de la página —«sus colores se vuelven sus números»— es literal:
|
||||
el abanico del hero trae los mismos hex que la dueña verá en sus fichas. */
|
||||
.landing {
|
||||
/* Marca */
|
||||
--ld-azul: #3b66ff; /* brand-500: primario del logo y de .btn-primary */
|
||||
--ld-azul-hondo: #1c36dc; /* brand-700: el hover que ya usa el panel */
|
||||
--ld-naranja: #f17616; /* accent-500: el punto del logo */
|
||||
/* Acentos: los colores de avatares y servicios del panel, sin cambiar un dígito */
|
||||
--ld-indigo: #6366f1;
|
||||
--ld-purpura: #a855f7;
|
||||
--ld-rosa: #ec4899;
|
||||
--ld-ambar: #f59e0b;
|
||||
--ld-esmeralda: #10b981;
|
||||
--ld-turquesa: #14b8a6;
|
||||
/* Estructura: idéntica a la del panel */
|
||||
--ld-tinta: #0f172a;
|
||||
--ld-papel: #ffffff;
|
||||
--ld-lienzo: #f6f7fb; /* el fondo del body de la app */
|
||||
--ld-nube: #eef4ff; /* brand-50: el mismo lavado del "hoy" en el calendario */
|
||||
--ld-crema: #fff7ed; /* orange-50, ya presente en el panel */
|
||||
--ld-muted: #64748b;
|
||||
--ld-hairline: #eef0f4;
|
||||
min-height: 100vh;
|
||||
min-height: 100dvh;
|
||||
overflow-x: clip;
|
||||
font-family: Inter, system-ui, sans-serif;
|
||||
color: var(--ld-tinta);
|
||||
background: var(--ld-papel);
|
||||
}
|
||||
/* Lavados de sección. Los tres salen del panel, así que pasar de la landing al
|
||||
producto no se siente como cambiar de sitio. */
|
||||
.landing-papel {
|
||||
background: var(--ld-papel);
|
||||
}
|
||||
.landing-nube {
|
||||
background: var(--ld-nube);
|
||||
}
|
||||
.landing-crema {
|
||||
background: var(--ld-lienzo);
|
||||
}
|
||||
|
||||
/* Display: Fraunces con el eje WONK abierto. Es un serif suave y con carácter,
|
||||
no una Didone de alto contraste: a 56px+ se lee cálido en vez de editorial-frío,
|
||||
que es lo que este público necesita. */
|
||||
.ld-display {
|
||||
font-family: Fraunces, Georgia, "Times New Roman", serif;
|
||||
font-variation-settings: "SOFT" 40, "WONK" 1;
|
||||
font-size: clamp(2.5rem, 7.2vw, 5.5rem);
|
||||
line-height: 1;
|
||||
letter-spacing: -0.022em;
|
||||
font-weight: 800;
|
||||
}
|
||||
.ld-title {
|
||||
font-family: Fraunces, Georgia, "Times New Roman", serif;
|
||||
font-variation-settings: "SOFT" 40, "WONK" 1;
|
||||
font-size: clamp(1.875rem, 4.4vw, 3.15rem);
|
||||
line-height: 1.08;
|
||||
letter-spacing: -0.015em;
|
||||
font-weight: 800;
|
||||
}
|
||||
/* Cuerpo en Inter, la misma tipografía del panel, a 17px mínimo y 1.6 de
|
||||
interlínea: el público incluye personas de 50+ y la legibilidad pesa más que la
|
||||
densidad. */
|
||||
.ld-lead {
|
||||
font-size: clamp(1.0625rem, 1.9vw, 1.3125rem);
|
||||
line-height: 1.6;
|
||||
font-weight: 500;
|
||||
}
|
||||
.ld-body {
|
||||
font-size: 1.0625rem;
|
||||
line-height: 1.65;
|
||||
}
|
||||
.ld-eyebrow {
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.ld-section {
|
||||
padding-block: clamp(4.5rem, 10vh, 9rem);
|
||||
padding-inline: max(1.25rem, var(--safe-left)) max(1.25rem, var(--safe-right));
|
||||
}
|
||||
.ld-wrap {
|
||||
width: 100%;
|
||||
max-width: 72rem;
|
||||
margin-inline: auto;
|
||||
}
|
||||
/* Canaleta lateral que además descuenta el notch en landscape.
|
||||
NO combines `.safe-x` con `px-*` en el mismo elemento: `.safe-x` vive fuera de
|
||||
`@layer` y por tanto gana a las utilidades de Tailwind, así que deja el padding
|
||||
en 0 siempre que el inset del sistema sea 0 — es decir, en todo lo que no sea un
|
||||
iPhone en landscape. El resultado es contenido pegado al borde en móvil. Esta
|
||||
clase resuelve las dos cosas en una sola declaración, como ya hace `.ld-section`. */
|
||||
.ld-gutter {
|
||||
padding-inline: max(1.25rem, var(--safe-left)) max(1.25rem, var(--safe-right));
|
||||
}
|
||||
/* Números que se interpolan frame a frame. Sin tabular-nums cada dígito cambia
|
||||
de ancho y la línea entera relayoutea en cada frame. Inter porque Fraunces no
|
||||
tiene cifras tabulares. */
|
||||
.ld-num {
|
||||
font-family: Inter, system-ui, sans-serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-feature-settings: "tnum";
|
||||
}
|
||||
/* Botón principal: el mismo azul de `.btn-primary` del panel, con el degradado
|
||||
brand-500 → brand-700 (los dos tonos que la app ya usa en normal y hover). El
|
||||
naranja del logo no se usa como fondo de acción: en el favicon es el punto que
|
||||
marca, no la superficie que trabaja, y en la app tampoco es un color de botón. */
|
||||
.ld-cta {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
min-height: 56px;
|
||||
padding-inline: 2rem;
|
||||
border-radius: 9999px;
|
||||
font-weight: 800;
|
||||
font-size: 1rem;
|
||||
color: #fff;
|
||||
background: linear-gradient(100deg, var(--ld-azul), var(--ld-azul-hondo));
|
||||
box-shadow: 0 12px 30px -10px rgba(59, 102, 255, 0.5);
|
||||
transition: transform 0.2s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.2s;
|
||||
}
|
||||
.ld-cta:hover:not(:disabled) {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 18px 40px -12px rgba(59, 102, 255, 0.58);
|
||||
}
|
||||
.ld-cta:disabled {
|
||||
opacity: 0.55;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.ld-cta-ghost {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
min-height: 56px;
|
||||
padding-inline: 1.75rem;
|
||||
border-radius: 9999px;
|
||||
font-weight: 700;
|
||||
font-size: 1rem;
|
||||
color: var(--ld-tinta);
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
border: 1.5px solid #e5e7eb;
|
||||
transition: background 0.2s, border-color 0.2s;
|
||||
}
|
||||
.ld-cta-ghost:hover {
|
||||
background: #fff;
|
||||
border-color: #d1d5db;
|
||||
}
|
||||
/* Foco visible propio: el degradado del CTA tapa el outline por defecto. */
|
||||
.landing a:focus-visible,
|
||||
.landing button:focus-visible {
|
||||
outline: 3px solid var(--ld-azul);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
/* Titular con degradado: naranja→ámbar. Es el papel que el naranja tiene en el
|
||||
logo —el punto que marca el renglón importante— trasladado a la frase que carga
|
||||
la promesa. El color sólido va primero como fallback: `color: transparent` sin
|
||||
`background-clip: text` deja el texto invisible, y ese fallo se llevaría media
|
||||
frase del hero. */
|
||||
.ld-gradient-text {
|
||||
color: var(--ld-naranja);
|
||||
}
|
||||
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
|
||||
.ld-gradient-text {
|
||||
background-image: linear-gradient(100deg, var(--ld-naranja), var(--ld-ambar));
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Anti auto-zoom de iOS (fuera de @layer para ganar a las utilidades) ----
|
||||
Mobile Safari hace zoom al enfocar cualquier campo con font-size < 16px y no
|
||||
revierte al desenfocar: la vista queda escalada y corrida. Ese es el origen
|
||||
del "arranca con zoom y desplazada" al tocar el campo de correo en el login.
|
||||
Se fuerzan 16px en todo puntero grueso (iPhone y iPad) en lugar de bloquear
|
||||
el zoom con maximum-scale=1, que rompería el pinch-zoom de accesibilidad.
|
||||
`select` incluido: iOS también zoomea al abrir un desplegable pequeño. */
|
||||
@media (pointer: coarse) {
|
||||
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]),
|
||||
select,
|
||||
textarea,
|
||||
.input,
|
||||
.select,
|
||||
.textarea {
|
||||
font-size: 16px;
|
||||
}
|
||||
/* Objetivos táctiles cómodos (Apple recomienda 44 pt). */
|
||||
.btn,
|
||||
.input,
|
||||
.select,
|
||||
.textarea {
|
||||
min-height: 44px;
|
||||
}
|
||||
/* Para controles compactos (chips de filtro, grupos segmentados) donde 44px
|
||||
rompería la densidad de la barra. Se decide por tipo de puntero y no por
|
||||
ancho de pantalla: un iPad se toca con el dedo igual que un iPhone, así que
|
||||
un breakpoint `sm:` dejaría fuera justamente a las tablets. */
|
||||
.tap-target {
|
||||
min-height: 40px;
|
||||
}
|
||||
/* Botones de solo icono (editar, borrar, cerrar, menú): con p-1.5 miden 28px,
|
||||
por debajo de lo tocable con el pulgar. Se marcan con `.icon-btn` en lugar de
|
||||
detectarlos por selector: se intentó `button:has(> svg:only-child)` y es
|
||||
engañoso, porque `:only-child` solo mira hijos ELEMENTO y un botón con
|
||||
etiqueta ("<Plus/> Nueva cita") también encaja — el texto es un nodo de
|
||||
texto. Esa regla, al fijar `display`, convirtió los NavLink del menú lateral
|
||||
en inline-flex y los repartió en dos columnas. */
|
||||
.icon-btn {
|
||||
min-height: 40px;
|
||||
min-width: 40px;
|
||||
}
|
||||
/* Se centra el icono, no se cambia el `display` del botón. El preflight de
|
||||
Tailwind pone `svg { display: block }`, así que el icono ignora `text-align`
|
||||
y `margin-inline: auto` es lo que lo centra en la caja ampliada. Tocar el
|
||||
display aquí sería contraproducente: esta regla está fuera de @layer y
|
||||
ganaría a las utilidades, de modo que un `hidden`/`lg:block` dejaría de
|
||||
funcionar y el botón de colapsar aparecería en el móvil. */
|
||||
.icon-btn > svg {
|
||||
margin-inline: auto;
|
||||
}
|
||||
}
|
||||
.label {
|
||||
display: block;
|
||||
|
||||
Reference in New Issue
Block a user