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:
AgendaPro Dev
2026-07-28 14:25:58 -06:00
co-authored by Claude Opus 5
parent 0b466f33f3
commit 4c19244df9
53 changed files with 7310 additions and 525 deletions
+78
View File
@@ -43,3 +43,81 @@
3. Replace local overlapsRange with imported overlaps.
4. Hoist per-slot employee-name SELECT out of slots loop.
5. MonthCalendar nav buttons → h-11 w-11 (44px touch target).
== DEMO EMAIL DOMAIN TASK ==
- Task 1: complete — source constants, README credentials, active fixtures, and executable plan login example updated. Commits 6355d18..50dea33; review clean.
- Task 2: complete — local data/agendapro.db migrated 2 users; second update changed 0; appointments 161 and clients 15 preserved; review clean.
- Task 3: complete with limitation — typecheck/build/unit/admin passed; auth paths passed; e2e/booking slot assertions remain blocked by pre-existing null working-hours data, not the email change; review approved.
- Task 4: complete — committed source/docs through 3e056a0, pushed to GitHub and Gitea, Coolify deployment wbnbq9b0kneba67sznu7p1qf finished, runtime container healthy.
- Task 5: complete — production health/page passed, new admin/owner logins returned 200, old-domain logins returned 401; active DB was fresh and seeded with new emails, so targeted SQL was a no-op.
- Final review: complete — final-fix review approved; regex acceptance fix review approved.
== AGENDAMAX PWA INSTALLABILITY TASK ==
- Task 1: complete — manifest, iOS metadata, reproducible Playwright-generated 192/512 PNG icons, and package scripts. Commits 9d2ab39..de947f9; review clean.
- Task 2: complete — versioned shell/static service worker and production-only registration. Commits de947f9..65233f2; review clean.
- Task 3: complete — install prompt hook/component, iOS Safari guide, safe-area styling, and Login/AppShell/AdminShell integration; review fixes added prompt guard, appinstalled precedence, close-button label, and compact mobile layout. Commits 65233f2..9ded129; review clean.
- Task 4: complete — production-server PWA endpoint/browser tests, online/offline API bypass checks, three-shell coverage, and install documentation; review clean. Commits 9ded129..43f5d13.
- Final fix wave: complete — credential-aware cache policy, AgendaMax-scoped cleanup, session dismissal persistence, configurable PWA test credentials, dialog semantics, and dismissal regression coverage. Commit 0b466f3; final whole-feature review approved.
== LANDING PÚBLICA + ACCESO DE UN CLIC (2026-07-28) ==
Spec: docs/superpowers/specs/2026-07-28-landing-funnel-bi-design.md
Plan: docs/superpowers/plans/2026-07-28-landing-funnel-bi.md
Ruteo: `/` sirve la landing (pública, React.lazy); el login pasa a `/login`. `homePathFor(user)`
centraliza el destino por rol. `/b/:slug` intacto, fuera del AuthProvider.
Dos iteraciones de diseño a pedido del usuario:
1. Primera versión oscura/sobria (criterio Apple). Entregada y verificada.
2. Rediseño a claro, vívido y animado, con copy humano para dueñas de salón y público de 50+
(guía de dolores: Base de Conocimiento - IA Negocios y Dev/03-Preguntas-Guia.md, Q15/Q72/Q87:
hablar de lo que gana el cliente, no de lo que hace el sistema). Se eliminó todo vocabulario de
sistema del copy visible: ni «business intelligence», ni «datos», ni «métricas», ni «panel».
3. Paleta rebasada sobre los colores REALES del panel a pedido del usuario: las 8 muestras del
abanico del hero son PIE_COLORS de DashboardPage.tsx; la marca sale de public/favicon.svg vía
src/components/BrandMark.tsx (única fuente); CTA = brand-500→brand-700 como .btn-primary.
Defectos encontrados y corregidos durante la verificación (no estaban en el plan):
- El chunk de entrada arrastraba framer-motion (39 KB gz) a TODA carga del panel, porque LoginPage
era eager. Se hizo lazy junto con la landing.
- La landing descargaba recharts (111 KB gz) y FullCalendar (76 KB gz) sin usarlos, porque
DashboardPage y CalendarPage eran eager. Ambas a lazy, con Suspense en el <Outlet> de AppShell.
- `clsx` lo comparten lib/format.ts y recharts; Rollup lo asignaba al chunk `charts`, así que el
entry importaba 111 KB para una utilidad de 200 bytes. Fijado al chunk `react`.
- LOGIN CON EL TEXTO PEGADO AL BORDE en móvil: `.safe-x` está fuera de @layer y gana a `px-5`,
dejando el padding en 0 con inset 0 (todo lo que no sea iPhone landscape). Nueva clase `.ld-gutter`.
Ningún check existente lo detectaba (no hay desborde: hay cero margen) → añadido check `gutter`.
- `shell-height` del audit daba falso positivo en toda página que scrollea: comparaba #root contra
el viewport vía el fallback a body.firstElementChild. Acotado a [data-app-shell]; a cambio se
añadió el check estático `raw-viewport-unit` sobre las fuentes públicas (verificado inyectando
`min-h-screen` en ProofSection: dispara).
- Rótulo «Como una de tus muchachas» en /login con Diego Castillo en la lista debajo. Reescrito sin
asumir género («Como alguien de tu equipo»); igual en ObjectionsSection.
- Anclas bajo el nav fijo (scroll-mt-24); degradado del cierre invisible tras `-z-10`; etiquetas del
abanico ilegibles (eliminadas); avisos flotantes solapando el abanico (reubicados a las 3 zonas
libres) y detalle truncado (acortado).
Audits ajustados por el cambio de ruteo (`/``/login`): visual-audit.mjs, responsive-audit.mjs y
los SIETE `goto(baseUrl)` de pwa-e2e.mjs que esperan InstallAppPrompt. La landing se añadió a las
listas de páginas de los dos audits.
VERIFICACIÓN FINAL (todo en verde):
- typecheck: 0 errores
- test:landing (NUEVO, WebKit): 13/13 — ruteo, acceso de un clic, sin desborde a 390px y
prefers-reduced-motion sin contenido invisible en las 7 secciones
- audit:responsive: 0 hallazgos (9 dispositivos × 10 páginas)
- audit:visual: 62 pantallas, 0 fallos, 0 desborde, 0 errores de consola
- test:pwa sobre el build: passed
- test:unit 39/39 · test:e2e 33/33 · test:admin 17/17 · test:booking 12/12
- build: el chunk de entrada NO importa charts, framer ni calendar (verificado en dist/)
- WebKit a 390/440/744/820/1024/1440 en `/` y `/login`: sin desborde, sin campos <16px, sin
objetivos <40px, sin errores
Sin commits (política del repo). `npm run lint` sigue roto por falta de eslint.config.js (preexistente).
FOLLOW-UPS DIFERIDOS:
1. `/login` en desktop deja un vacío bajo la tarjeta del formulario (columnas de altura muy distinta).
No es un defecto medible; revisar si molesta en uso real.
2. La landing no tiene contenido de precios ni captura de leads: en fase demo la conversión es entrar
a la demo. Habrá que decidirlo antes de un lanzamiento real.
3. Seguridad sin cambios: `/login` expone contraseñas de cuentas existentes bajo la bandera DEMO.
Autorizado solo para esta fase; es lo único que separa esto de una fuga si se publica.