Compare commits

..
Author SHA1 Message Date
devops 4b2e6477e5 fix(ecommerce): Club ARI y Novedades - ultimos leftovers arcoiris del home
Dos leftovers del esquema original notados de pasada durante las
pruebas de ADR 0008 (fuera del alcance original de logo/buscador/
hero/tiles, pero mismo patron): la etiqueta 'Novedades' en
text-fuchsia-600 y la seccion 'Club ARI' con gradiente
cyan-500/blue-600/violet-600 + badge text-yellow-300.

Ambos pasan a los tokens de marca (ari-coral, ari-marino solido,
ari-coral-claro) -- ya no queda ningun gradiente ni color hardcodeado
del esquema arcoiris original en el home.
2026-08-28 21:09:30 +00:00
devops e5f9ec5f1d chore: release v1.0.202 [skip ci] 2026-08-28 20:04:00 +00:00
devops 11b97b9178 Merge fix/adr0008-tiles-foto-mas-grande into main
Build and Push Frontend / Escaneo de Secretos (Gitleaks) (push) Successful in 19s
Build and Push Frontend / Construir y Subir Imagen (push) Successful in 5m17s
2026-08-28 19:36:39 +00:00
devops 229999fa5a fix(ecommerce): tiles con foto - la imagen llena la tarjeta (object-cover)
Reportado en produccion: con object-contain + margenes grandes
(inset-x-6 top-6 bottom-24) la foto quedaba chica y centrada, con
mucho espacio muerto del pastel alrededor -- no se parecia a la
referencia real de Walmart que mando el usuario, donde la foto
aprovecha casi toda la tarjeta.

Cambia a object-cover (recorta en vez de contener) dentro de un marco
mas delgado (inset-3 en vez de inset-x-6/top-6), dejando solo un
margen fino del pastel visible como marco alrededor de la foto.
2026-08-28 19:36:37 +00:00
devops 8e0053ab84 chore: release v1.0.201 [skip ci] 2026-08-28 18:49:03 +00:00
devops e837c55de8 Merge fix/adr0008-tiles-foto-flotante into main
Build and Push Frontend / Escaneo de Secretos (Gitleaks) (push) Successful in 19s
Build and Push Frontend / Construir y Subir Imagen (push) Successful in 7m33s
2026-08-28 18:41:15 +00:00
devops 4994d063eb fix(ecommerce): tiles con foto - patron real de Walmart (foto flotante)
El usuario mando una captura real de walmart.com: en las tarjetas de
producto la foto no cubre la tarjeta completa, flota contenida sobre
un fondo pastel solido -- exactamente el mismo patron que ya usan las
tarjetas sin foto de esta seccion (Bebes, Educativos, etc.), solo que
Juguetes/Mochilas rompian el patron con la foto a pantalla completa +
una franja de degradado para el texto (el intento anterior, tambien
reportado como no convincente).

Reemplaza fill+object-cover a pantalla completa por una foto
contenida (object-contain) en la mitad superior de la tarjeta, dejando
el fondo pastel visible alrededor -- coherente con el resto de la
seccion. Elimina el campo scrim, ya no hace falta.
2026-08-28 18:41:12 +00:00
devops a974e738eb chore: release v1.0.200 [skip ci] 2026-08-28 18:07:03 +00:00
devops 15483e7a5b Merge fix/adr0008-hero-carousel-ts-build-error into main
Build and Push Frontend / Escaneo de Secretos (Gitleaks) (push) Successful in 18s
Build and Push Frontend / Construir y Subir Imagen (push) Successful in 10m37s
2026-08-28 17:56:11 +00:00
devops a4dd1c444a fix(ecommerce): corrige error de tipos que rompia el build de CI
npm run build fallaba en CI (runs 201 y 202, ambos commits que incluian
hero-carousel.tsx) con 'slide is possibly undefined' -- indexar
slides[index] con TypeScript estricto (noUncheckedIndexedAccess) tipa
el resultado como Slide | undefined aunque el indice siempre sea
valido en runtime (useState(0) + modulo en el autoplay + clicks solo
a indices validos).

Como el build nunca genero una imagen nueva, Argo seguia sirviendo
v1.0.197 (el commit anterior al carrusel) -- el hero y los tiles-scrim
en produccion nunca reflejaron estos dos cambios ya mergeados a main.
Este fix desbloquea ambos builds.
2026-08-28 17:56:09 +00:00
devops a3913ce457 Merge fix/adr0008-tiles-scrim-pastel into main
Build and Push Frontend / Escaneo de Secretos (Gitleaks) (push) Successful in 20s
Build and Push Frontend / Construir y Subir Imagen (push) Failing after 3m28s
2026-08-28 17:41:49 +00:00
devops 503d714ea4 fix(ecommerce): tiles con foto usan franja tintada con su propio pastel
Reportado en produccion: las tarjetas con foto (Juguetes, Mochilas)
usaban una franja de degradado blanca generica para el texto, y se
veian desconectadas de las tarjetas solidas vecinas (rosa, verde,
etc.) de la misma seccion. La franja ahora usa el mismo pastel
asignado a esa categoria (from-amber-100 / from-sky-100) en vez de
blanco, y se agranda ligeramente (h-2/5 -> h-3/5) para mas presencia.
2026-08-28 17:41:47 +00:00
devops afd6991519 Merge fix/adr0008-hero-carrusel-azul into main
Build and Push Frontend / Escaneo de Secretos (Gitleaks) (push) Successful in 19s
Build and Push Frontend / Construir y Subir Imagen (push) Failing after 3m59s
2026-08-28 17:37:44 +00:00
devops 7d27b527e2 feat(ecommerce): ADR 0008 sub-fase 4 - hero carrusel azul con dots
Reemplaza hero-bento.tsx (grid estatico de 3 tarjetas en fucsia/rosa/
rojo/violeta del esquema arcoiris original, nunca migrado -- sub-fase
4 del ADR 0008 pendiente) por hero-carousel.tsx: un slide a la vez
sobre fondo azul de marca (--color-ari-marino) para los tres
mensajes promocionales existentes, con dots de paginacion clicables
abajo (patron pedido explicitamente por el usuario) y autoplay cada
6s (respeta prefers-reduced-motion). Actualiza app/page.tsx para usar
el componente nuevo y elimina hero-bento.tsx.
2026-08-28 17:37:42 +00:00
devops 3ad6951f44 chore: release v1.0.197 [skip ci] 2026-08-28 14:25:28 +00:00
devops 0e57bb04d1 chore: release v1.0.196 [skip ci] 2026-08-28 14:17:20 +00:00
devops e631672804 Merge fix/adr0008-product-purchase-mobile-y-badge into main
Build and Push Frontend / Escaneo de Secretos (Gitleaks) (push) Successful in 30s
Build and Push Frontend / Construir y Subir Imagen (push) Successful in 7m29s
2026-08-28 14:14:25 +00:00
devops ca26c6ebea fix(ecommerce): boton duplicado en mobile y leftovers fucsia en pagina de producto
Reportado en produccion: en mobile aparecian dos botones 'Añadir al
carrito' apilados. ProductPurchase.tsx nunca ocultaba su boton en
mobile, y MobileAddToCart.tsx (barra fija abajo, solo lg:hidden) ya
cubre esa misma accion ahi -- se agrega hidden lg:inline-flex al
primero para que solo uno este visible por breakpoint.

De paso, dos leftovers del esquema arcoiris original en la misma
pagina, vistos en la misma sesion de pruebas: el badge de variante
seleccionada (border-fuchsia-500 bg-fuchsia-50 text-fuchsia-700 -> azul
de marca) y la etiqueta de categoria/coleccion (text-fuchsia-600 ->
ari-coral).
2026-08-28 14:14:23 +00:00
devops 12d3c692d3 Merge fix/adr0008-logo-circular-header into main
Build and Push Frontend / Escaneo de Secretos (Gitleaks) (push) Successful in 18s
Build and Push Frontend / Construir y Subir Imagen (push) Successful in 8m27s
2026-08-28 14:08:37 +00:00
devops 9c441032de feat(ecommerce): logo circular en el header (pedido del usuario)
Reemplaza el lockup rectangular (logo-ari.svg) por un isotipo circular
nuevo (logo-ari-circular.svg) con el icono mariposa + wordmark 'ARI
shopping' integrados dentro del mismo circulo azul -- el formato que
tenia la version previa a ADR 0008, preferido sobre el rectangulo
segun mockup de referencia del usuario. El footer conserva el lockup
rectangular, donde si tiene sentido como bloque de color autocontenido
sobre el fondo oscuro.
2026-08-28 14:08:35 +00:00
devops 603f4b963d chore: release v1.0.195 [skip ci] 2026-08-28 06:32:19 +00:00
devops ac37774245 Merge fix/adr0008-tiles-pastel into main
Build and Push Frontend / Escaneo de Secretos (Gitleaks) (push) Successful in 18s
Build and Push Frontend / Construir y Subir Imagen (push) Successful in 7m1s
2026-08-28 06:25:03 +00:00
devops 7aa9f40abc feat(ecommerce): ADR 0008 sub-fase 4 - tiles de categoria pastel
category-tiles.tsx (PromoTile, compartido entre home y grid de
catalogo) nunca se toco en ninguna sub-fase de ADR 0008 -- la sub-fase
4 ('Home: hero, tiles, secciones') quedo pendiente de implementar
(confirmado: no existe merge fix/adr0008-*-subfase4-* en el historial).
Seguia con gradientes saturados rojo/violeta/magenta/cian del esquema
arcoiris original, confirmado visualmente en produccion.

Reemplaza los gradientes por fondos pastel solidos (amber/sky/rose/
emerald/orange/violet-100, patron Walmart-inspirado), texto oscuro
sobre el pastel, boton circular de accion en azul de marca
(--color-ari-marino) en vez de blanco/negro generico, y los acentos
fucsia/cian del encabezado de seccion por los tokens de marca.
2026-08-28 06:22:47 +00:00
devops 617a69d58a chore: release v1.0.194 [skip ci] 2026-08-28 06:15:43 +00:00
devops 09a8698f7e Merge fix/adr0008-buscador-un-solo-foco into main
Build and Push Frontend / Escaneo de Secretos (Gitleaks) (push) Successful in 21s
Build and Push Frontend / Construir y Subir Imagen (push) Successful in 6m22s
2026-08-28 06:09:08 +00:00
devops 23700c5b1d fix(ecommerce): buscador usa un solo indicador de foco (no dos)
El outline de accesibilidad global (input:focus-visible, azul de
marca, globals.css) y el indicador de foco propio del wrapper
(focus-within:border-ari-marino + ring amarillo) se mostraban ambos a
la vez -- redondear el outline (commit anterior) evito el rectangulo
recto pero seguia leyendose como dos bordes anidados, reportado en
produccion (v1.0.193).

focus-visible:outline-none en el input iguala la especificidad de la
regla global (mismo patron que ya usa buttonVariants en button.tsx) y
deja el indicador de foco del wrapper como el unico visible -- no se
pierde accesibilidad, se elimina la duplicacion.
2026-08-28 06:09:06 +00:00
devops 5c173d5f40 chore: release v1.0.193 [skip ci] 2026-08-28 06:02:07 +00:00
devops d0a3554959 Merge fix/adr0008-buscador-outline-a11y-redondeado into main
Build and Push Frontend / Escaneo de Secretos (Gitleaks) (push) Successful in 18s
Build and Push Frontend / Construir y Subir Imagen (push) Successful in 8m32s
2026-08-28 05:53:17 +00:00
devops 75dedc4e67 fix(ecommerce): outline de accesibilidad del buscador sigue el pill
globals.css define input:focus-visible { outline: 3px solid
rgb(0 85 184 / .5); ... } como indicador de foco por teclado global,
intencional (no se toca). Le gana en especificidad CSS a la utilidad
outline-none de Tailwind, asi que seguia mostrandose sobre el input
propio del buscador (commit anterior) -- pero como ese input no tenia
border-radius, se veia como un rectangulo de esquinas rectas
superpuesto al pill redondeado, reportado en produccion (v1.0.192).

Agrega rounded-full al input para que el mismo outline (que se debe
conservar) siga el contorno del pill en vez de cortarlo.
2026-08-28 05:53:15 +00:00
devops 801db95aa8 chore: release v1.0.192 [skip ci] 2026-08-28 05:45:57 +00:00
devops 6df24abcb9 Merge fix/adr0008-buscador-ring-input-propio into main
Build and Push Frontend / Escaneo de Secretos (Gitleaks) (push) Successful in 18s
Build and Push Frontend / Construir y Subir Imagen (push) Successful in 7m49s
2026-08-28 05:37:51 +00:00
devops a6291ffe14 fix(ecommerce): buscador usa input propio en vez del <Input> compartido
El fix anterior (focus:border-0 focus:ring-0) no elimino de forma
confiable el focus:ring-4 focus:ring-cyan-300/30 hardcodeado del
componente <Input> compartido dentro de este contenedor compuesto
(input + boton circular en el mismo wrapper) -- reportado en
produccion (v1.0.191): un segundo ring amarillo se superponia y
cortaba visualmente el boton de la lupa al hacer click.

En vez de seguir peleando el override via tailwind-merge, el buscador
deja de usar el <Input> compartido y usa un <input> nativo con sus
propias clases minimas, sin nada que sobreescribir.
2026-08-28 05:37:50 +00:00
devops 030bf2712a chore: release v1.0.191 [skip ci] 2026-08-28 05:20:14 +00:00
devops f5ddb63c8a Merge fix/adr0008-buscador-doble-ring into main
Build and Push Frontend / Escaneo de Secretos (Gitleaks) (push) Successful in 19s
Build and Push Frontend / Construir y Subir Imagen (push) Successful in 8m3s
2026-08-28 05:11:56 +00:00
devops d536b0bb8b fix(ecommerce): quita el doble ring de foco del buscador
El Input base (components/ui/input.tsx) trae focus:border-cyan-500 y
focus:ring-4 focus:ring-cyan-300/30 hardcodeados. La correccion
anterior del buscador solo anulaba focus-visible:ring-0, una variante
distinta a :focus -- el ring cian del componente base seguia
disparandose al hacer click, superpuesto al ring amarillo del wrapper
nuevo (dos anillos visibles a la vez, reportado por el usuario en
produccion tras el deploy de v1.0.190).

focus:border-0 focus:ring-0 anula correctamente ambas utilidades del
Input base via tailwind-merge (mismo variant+propiedad).
2026-08-28 05:10:22 +00:00
devops 260c7076e2 chore: release v1.0.190 [skip ci] 2026-08-28 05:06:07 +00:00
devops 59024cd6e5 Merge fix/adr0008-buscador-azul-amarillo into main
Build and Push Frontend / Escaneo de Secretos (Gitleaks) (push) Successful in 20s
Build and Push Frontend / Construir y Subir Imagen (push) Successful in 7m20s
2026-08-28 04:58:28 +00:00
devops ebd341c060 feat(ecommerce): ADR 0008 - buscador azul & amarillo (nunca migrado)
predictive-search.tsx nunca estuvo en el alcance de ninguna sub-fase
de ADR 0008 (sub-fase 2 solo tocaba navbar.tsx/footer.tsx), asi que
seguia con el borde degradado cian/violeta/fucsia y acentos violeta
del esquema arcoiris original -- confirmado visualmente en produccion
(captura del usuario) y en el codigo.

Reemplaza el wrapper de gradiente por un input con borde solido y un
boton circular azul (--color-ari-marino) con la lupa a la derecha, en
vez del icono flotando a la izquierda sobre el degradado. Foco usa
ring amarillo (--color-ari-coral-claro), igual que el resto de
componentes interactivos del sitio (button.tsx). Hover/foco del panel
de resultados y del boton 'Buscar en todo el catalogo' pasan de
violeta a los tokens de marca.
2026-08-28 04:57:43 +00:00
devops e7349f34c3 chore: release v1.0.189 [skip ci] 2026-08-28 04:52:12 +00:00
devops c94ad5f651 Merge fix/adr0008-logo-cache-busting into main
Build and Push Frontend / Escaneo de Secretos (Gitleaks) (push) Successful in 22s
Build and Push Frontend / Construir y Subir Imagen (push) Successful in 9m39s
2026-08-28 04:42:10 +00:00
devops ce929188ac fix(ecommerce): baja TTL de cache a 5min para /images/brand/*
El header de /images/:path* (7 dias, pensado para fotos de producto
que no cambian) tambien alcanzaba a los assets de marca (logo,
favicon), que si cambian con cada iteracion de identidad visual.

Confirmado en vivo contra shop.cruzcloud.net: Cloudflare seguia
sirviendo el logo de la iteracion anterior 'Navy & Coral'
(fill #122844/#D85A30) mas de 14h despues de que el pod ya corria
v1.0.185 con el logo 'Azul & Amarillo' (#0055B8/#FFC220) --
cf-cache-status: HIT, age: 51378s, last-modified anterior al redeploy.

No es un bug del logo.svg en si (el contenido del archivo ya era
correcto desde la sub-fase 1 de ADR 0008) sino de una politica de
cache compartida con assets que no deberian tener el mismo TTL.
2026-08-28 04:35:18 +00:00
gitea-actions 98e0d331e9 chore(gitops): deploy Docs Portal v1.0.188 [skip ci] 2026-08-28 04:10:37 +00:00
devops d299fc1201 Merge fix/docs-tmux-persistir-trust-claude-json into main
Build and Push Docs Portal / Escaneo de Secretos (Gitleaks) (push) Successful in 20s
Build and Push Docs Portal / Construir y publicar Docs Portal (push) Successful in 5m37s
Merge local directo (sin PR de Gitea) por acuerdo del usuario; ver
feedback_git_fix_branches en memoria del proyecto para el porque de
este flujo estandar.
2026-08-27 23:04:40 -05:00
12 changed files with 213 additions and 75 deletions

No files matched your search

@@ -25,7 +25,7 @@ spec:
runAsNonRoot: false runAsNonRoot: false
containers: containers:
- name: app - name: app
image: gitea.cruzcloud.net/devops/docs-portal:v1.0.187 image: gitea.cruzcloud.net/devops/docs-portal:v1.0.188
# El base saca TODAS las capabilities. Probado en vivo y # El base saca TODAS las capabilities. Probado en vivo y
# revertido: con drop: ALL (aunque se agregue NET_BIND_SERVICE # revertido: con drop: ALL (aunque se agregue NET_BIND_SERVICE
# de vuelta) el propio entrypoint de nginx:1.27-alpine falla al # de vuelta) el propio entrypoint de nginx:1.27-alpine falla al
+2 -2
View File
@@ -1,7 +1,7 @@
import Link from "next/link"; import Link from "next/link";
import { ArrowRight } from "lucide-react"; import { ArrowRight } from "lucide-react";
import { CategoryTiles } from "@/components/home/category-tiles"; import { CategoryTiles } from "@/components/home/category-tiles";
import { HeroBento } from "@/components/home/hero-bento"; import { HeroCarousel } from "@/components/home/hero-carousel";
import { TrustBar } from "@/components/home/trust-bar"; import { TrustBar } from "@/components/home/trust-bar";
import { ProductCarousel } from "@/components/product/product-carousel"; import { ProductCarousel } from "@/components/product/product-carousel";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@@ -17,5 +17,5 @@ export const dynamic = "force-dynamic";
export default async function HomePage() { export default async function HomePage() {
const [products, categories] = await Promise.all([getProducts(), getCategories()]); const [products, categories] = await Promise.all([getProducts(), getCategories()]);
const treasures = products.filter((product) => product.isNew || product.featured).slice(0, 8); const treasures = products.filter((product) => product.isNew || product.featured).slice(0, 8);
return <><HeroBento /><TrustBar /><div className="content-auto"><CategoryTiles categories={categories} /></div><section className="content-auto mx-auto max-w-7xl px-4 py-14"><div className="mb-8 flex items-end justify-between gap-5"><div><span className="text-xs font-black uppercase tracking-[.18em] text-fuchsia-600">Novedades</span><h2 className="mt-2 text-4xl font-black tracking-tight sm:text-5xl">Tesoros recién llegados</h2><p className="mt-3 text-slate-500">Desliza y encuentra nuevos favoritos.</p></div><Button asChild variant="outline" className="hidden sm:inline-flex"><Link href="/catalog">Ver catálogo <ArrowRight className="size-4" /></Link></Button></div><ProductCarousel products={treasures} /></section><section className="content-auto mx-auto max-w-7xl px-4 pb-20"><div className="grid overflow-hidden rounded-[2rem] bg-gradient-to-r from-cyan-500 via-blue-600 to-violet-600 text-white shadow-2xl lg:grid-cols-[1fr_.7fr]"><div className="p-8 sm:p-12"><span className="text-xs font-black uppercase tracking-[.18em] text-yellow-300">Club ARI</span><h2 className="mt-3 text-4xl font-black tracking-tight sm:text-6xl">Promociones, lanzamientos y mucha diversión.</h2><p className="mt-5 max-w-xl text-white/80">Únete a nuestra comunidad y recibe primero las novedades del catálogo.</p><Button variant="yellow" size="lg" className="mt-7">Quiero unirme</Button></div><div className="min-h-64 bg-[url('/images/promo/promo-backpacks.webp')] bg-cover bg-center" /></div></section></>; return <><HeroCarousel /><TrustBar /><div className="content-auto"><CategoryTiles categories={categories} /></div><section className="content-auto mx-auto max-w-7xl px-4 py-14"><div className="mb-8 flex items-end justify-between gap-5"><div><span className="text-xs font-black uppercase tracking-[.18em] text-ari-coral">Novedades</span><h2 className="mt-2 text-4xl font-black tracking-tight sm:text-5xl">Tesoros recién llegados</h2><p className="mt-3 text-slate-500">Desliza y encuentra nuevos favoritos.</p></div><Button asChild variant="outline" className="hidden sm:inline-flex"><Link href="/catalog">Ver catálogo <ArrowRight className="size-4" /></Link></Button></div><ProductCarousel products={treasures} /></section><section className="content-auto mx-auto max-w-7xl px-4 pb-20"><div className="grid overflow-hidden rounded-[2rem] bg-ari-marino text-white shadow-2xl lg:grid-cols-[1fr_.7fr]"><div className="p-8 sm:p-12"><span className="text-xs font-black uppercase tracking-[.18em] text-ari-coral-claro">Club ARI</span><h2 className="mt-3 text-4xl font-black tracking-tight sm:text-6xl">Promociones, lanzamientos y mucha diversión.</h2><p className="mt-5 max-w-xl text-white/80">Únete a nuestra comunidad y recibe primero las novedades del catálogo.</p><Button variant="yellow" size="lg" className="mt-7">Quiero unirme</Button></div><div className="min-h-64 bg-[url('/images/promo/promo-backpacks.webp')] bg-cover bg-center" /></div></section></>;
} }
@@ -33,5 +33,5 @@ export default async function ProductPage({ params }: ProductPageProps) {
if (!product) notFound(); if (!product) notFound();
const products = await getProducts(); const products = await getProducts();
const recommended = products.filter((candidate) => candidate.id !== product.id && (candidate.category === product.category || candidate.collection === product.collection)).slice(0, 8); const recommended = products.filter((candidate) => candidate.id !== product.id && (candidate.category === product.category || candidate.collection === product.collection)).slice(0, 8);
return <><section className="mx-auto grid max-w-7xl gap-10 px-4 py-10 lg:grid-cols-[1.08fr_.92fr] lg:py-16"><ProductGallery images={product.images} name={product.name} /><div><span className="text-xs font-black uppercase tracking-[.18em] text-fuchsia-600">{product.category} · {product.collection}</span><h1 className="mt-3 text-4xl font-black tracking-tight sm:text-6xl">{product.name}</h1><p className="mt-5 text-lg leading-8 text-slate-600">{product.description}</p><div className="mt-8"><ProductPurchase product={product} /></div><Accordion type="single" collapsible className="mt-8"><AccordionItem value="details"><AccordionTrigger>Detalles del producto</AccordionTrigger><AccordionContent>Marca: {product.brand}. Edad recomendada: {product.ageRange}. La disponibilidad y presentación final se confirman con Ari Shopping.</AccordionContent></AccordionItem><AccordionItem value="shipping"><AccordionTrigger>Envíos y entregas</AccordionTrigger><AccordionContent>Coordinamos envíos nacionales y entrega según ubicación. Los tiempos se confirman durante la cotización.</AccordionContent></AccordionItem></Accordion></div></section>{recommended.length > 0 && <section className="mx-auto max-w-7xl px-4 pb-24"><h2 className="mb-7 text-3xl font-black">También te puede gustar</h2><ProductCarousel products={recommended} /></section>}<MobileAddToCart product={product} /></>; return <><section className="mx-auto grid max-w-7xl gap-10 px-4 py-10 lg:grid-cols-[1.08fr_.92fr] lg:py-16"><ProductGallery images={product.images} name={product.name} /><div><span className="text-xs font-black uppercase tracking-[.18em] text-ari-coral">{product.category} · {product.collection}</span><h1 className="mt-3 text-4xl font-black tracking-tight sm:text-6xl">{product.name}</h1><p className="mt-5 text-lg leading-8 text-slate-600">{product.description}</p><div className="mt-8"><ProductPurchase product={product} /></div><Accordion type="single" collapsible className="mt-8"><AccordionItem value="details"><AccordionTrigger>Detalles del producto</AccordionTrigger><AccordionContent>Marca: {product.brand}. Edad recomendada: {product.ageRange}. La disponibilidad y presentación final se confirman con Ari Shopping.</AccordionContent></AccordionItem><AccordionItem value="shipping"><AccordionTrigger>Envíos y entregas</AccordionTrigger><AccordionContent>Coordinamos envíos nacionales y entrega según ubicación. Los tiempos se confirman durante la cotización.</AccordionContent></AccordionItem></Accordion></div></section>{recommended.length > 0 && <section className="mx-auto max-w-7xl px-4 pb-24"><h2 className="mb-7 text-3xl font-black">También te puede gustar</h2><ProductCarousel products={recommended} /></section>}<MobileAddToCart product={product} /></>;
} }
@@ -10,16 +10,20 @@ type TileContent = { subtitle: string; image?: string; color: string };
// (ver ADR 0005) -- solo se mapean imagen/subtitulo para las que ya tienen // (ver ADR 0005) -- solo se mapean imagen/subtitulo para las que ya tienen
// un asset en public/images/categories/. Las demas caen al tile de solo // un asset en public/images/categories/. Las demas caen al tile de solo
// gradiente en vez de mostrar una imagen que no les corresponde. // gradiente en vez de mostrar una imagen que no les corresponde.
// Fondos pastel solidos (patron Walmart-inspirado, ADR 0008 sub-fase 4)
// en vez de los gradientes saturados originales del esquema arcoiris --
// un tono pastel distinto por categoria, no una escala de marca (serian
// todos casi iguales), pero coordinados en la misma temperatura suave.
const tileContent: Record<string, TileContent> = { const tileContent: Record<string, TileContent> = {
juguetes: { subtitle: "Personajes que inspiran", image: "/images/categories/figuras.webp", color: "from-yellow-300 to-orange-400" }, juguetes: { subtitle: "Personajes que inspiran", image: "/images/categories/figuras.webp", color: "bg-amber-100" },
"mochilas-y-morrales": { subtitle: "Estilo para cada aventura", image: "/images/categories/mochilas.webp", color: "from-cyan-400 to-blue-500" }, "mochilas-y-morrales": { subtitle: "Estilo para cada aventura", image: "/images/categories/mochilas.webp", color: "bg-sky-100" },
"bebes-y-primera-infancia": { subtitle: "Primeros descubrimientos", color: "from-pink-300 to-fuchsia-400" }, "bebes-y-primera-infancia": { subtitle: "Primeros descubrimientos", color: "bg-rose-100" },
educativos: { subtitle: "Aprender jugando", color: "from-emerald-400 to-teal-600" }, educativos: { subtitle: "Aprender jugando", color: "bg-emerald-100" },
"accion-y-aventura": { subtitle: "Para explorar sin límites", color: "from-red-500 to-rose-700" }, "accion-y-aventura": { subtitle: "Para explorar sin límites", color: "bg-orange-100" },
variedades: { subtitle: "Un poco de todo", color: "from-violet-400 to-purple-600" }, variedades: { subtitle: "Un poco de todo", color: "bg-violet-100" },
}; };
const defaultTile: TileContent = { subtitle: "Explora esta categoría", color: "from-slate-400 to-slate-600" }; const defaultTile: TileContent = { subtitle: "Explora esta categoría", color: "bg-slate-100" };
// Tile individual reutilizado tanto por la seccion de mundos del home // Tile individual reutilizado tanto por la seccion de mundos del home
// como por el grid del catalogo (tiles promocionales intercalados, // como por el grid del catalogo (tiles promocionales intercalados,
@@ -31,20 +35,24 @@ export function PromoTile({ category, className = "" }: { category: Category; cl
return ( return (
<Link <Link
href={`/catalog?category=${encodeURIComponent(category.name)}`} href={`/catalog?category=${encodeURIComponent(category.name)}`}
className={cn(`group relative min-h-64 overflow-hidden rounded-[2rem] bg-gradient-to-br ${content.color} shadow-xl`, className)} className={cn(`group relative min-h-64 overflow-hidden rounded-[2rem] ${content.color} shadow-sm ring-1 ring-slate-900/5`, className)}
> >
{/* Patron Walmart: foto recortada (object-cover) llenando casi toda
la tarjeta, con solo un margen delgado del pastel como marco --
no flotando chica con espacio muerto alrededor (object-contain,
intento anterior, reportado como "no aprovecha el espacio"), ni
a pantalla completa sin marco (el intento antes de ese). */}
{content.image && ( {content.image && (
<> <div className="absolute inset-3 bottom-24 overflow-hidden rounded-2xl">
<Image src={content.image} alt={category.name} fill sizes="(max-width: 640px) 100vw, (max-width: 1024px) 33vw, 16vw" className="object-cover transition duration-500 group-hover:scale-105" /> <Image src={content.image} alt="" fill sizes="(max-width: 640px) 45vw, (max-width: 1024px) 25vw, 14vw" className="object-cover transition duration-500 group-hover:scale-105" />
<div className="absolute inset-0 bg-gradient-to-t from-slate-950/80 via-transparent to-white/5" /> </div>
</>
)} )}
<div className="absolute inset-x-0 bottom-0 flex items-end justify-between p-6 text-white"> <div className="absolute inset-x-0 bottom-0 flex items-end justify-between p-6 text-slate-900">
<div> <div>
<h3 className="text-xl font-black">{category.name}</h3> <h3 className="text-xl font-black">{category.name}</h3>
<p className="mt-1 text-sm font-semibold text-white/80">{content.subtitle}</p> <p className="mt-1 text-sm font-semibold text-slate-600">{content.subtitle}</p>
</div> </div>
<span className="grid size-11 shrink-0 place-items-center rounded-full bg-white text-slate-950 transition group-hover:rotate-45"> <span className="grid size-11 shrink-0 place-items-center rounded-full bg-ari-marino text-white transition group-hover:rotate-45">
<ArrowUpRight /> <ArrowUpRight />
</span> </span>
</div> </div>
@@ -61,10 +69,10 @@ export function CategoryTiles({ categories }: { categories: Category[] }) {
<section className="mx-auto max-w-7xl px-4 py-14"> <section className="mx-auto max-w-7xl px-4 py-14">
<div className="mb-8 flex items-end justify-between gap-4"> <div className="mb-8 flex items-end justify-between gap-4">
<div> <div>
<span className="text-xs font-black uppercase tracking-[.18em] text-fuchsia-600">Explora por mundos</span> <span className="text-xs font-black uppercase tracking-[.18em] text-ari-coral">Explora por mundos</span>
<h2 className="mt-2 text-4xl font-black tracking-tight sm:text-5xl">Encuentra tu próximo tesoro</h2> <h2 className="mt-2 text-4xl font-black tracking-tight sm:text-5xl">Encuentra tu próximo tesoro</h2>
</div> </div>
<Link href="/catalog" className="hidden font-black text-cyan-700 sm:block">Ver todo →</Link> <Link href="/catalog" className="hidden font-black text-ari-marino sm:block">Ver todo →</Link>
</div> </div>
<div className="grid gap-5 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6"> <div className="grid gap-5 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6">
{roots.map((category) => ( {roots.map((category) => (
@@ -1,42 +0,0 @@
"use client";
import Image from "next/image";
import Link from "next/link";
import { ArrowRight, BadgePercent, Sparkles } from "lucide-react";
import { motion, useReducedMotion } from "motion/react";
import { Button } from "@/components/ui/button";
export function HeroBento() {
const reduceMotion = useReducedMotion();
const initial = reduceMotion ? false : { opacity: 0, y: 20 };
return (
<section className="mx-auto max-w-7xl px-4 py-7 lg:py-10">
<div className="grid gap-5 lg:grid-cols-[1.55fr_.8fr]">
<motion.article initial={initial} animate={{ opacity: 1, y: 0 }} className="grid min-h-[470px] overflow-hidden rounded-[2rem] bg-fuchsia-500 text-white shadow-2xl lg:min-h-[560px] lg:grid-cols-[.82fr_1.18fr]">
<div className="relative z-10 flex flex-col justify-center bg-gradient-to-br from-fuchsia-600 via-pink-500 to-rose-500 p-7 lg:p-11">
<div className="mb-5 inline-flex w-fit items-center gap-2 rounded-full bg-yellow-300 px-4 py-2 text-xs font-black uppercase tracking-wider text-slate-950"><Sparkles className="size-4" />Celebración ARI</div>
<h1 className="text-5xl font-black leading-[.92] tracking-[-.06em] sm:text-6xl lg:text-7xl"><span className="block">Hasta</span><span className="text-yellow-300">50% OFF</span><span className="mt-3 block text-3xl tracking-tight sm:text-4xl">en tesoros seleccionados</span></h1>
<p className="mt-6 max-w-md text-base font-semibold text-white/95 sm:text-lg">Personajes, mochilas y regalos que convierten cualquier día en una celebración.</p>
<Button asChild variant="yellow" size="lg" className="mt-8 w-fit"><Link href="/catalog">Comprar ahora <ArrowRight className="size-5" /></Link></Button>
</div>
<div className="relative min-h-[360px] bg-white lg:min-h-full">
<Image src="/images/promo/hero-vibrant.webp" alt="Promoción principal de Ari Shopping" fill priority sizes="(max-width: 1024px) 100vw, 42vw" className="object-cover object-center" />
</div>
</motion.article>
<div className="grid gap-5 sm:grid-cols-2 lg:grid-cols-1">
<motion.article initial={reduceMotion ? false : { opacity: 0, x: 18 }} animate={{ opacity: 1, x: 0 }} transition={{ delay: .08 }} className="grid min-h-60 overflow-hidden rounded-[2rem] bg-red-600 text-white shadow-xl sm:grid-cols-[.9fr_1.1fr] lg:min-h-0">
<div className="relative z-10 flex flex-col justify-center bg-gradient-to-br from-red-600 to-rose-700 p-6"><span className="text-xs font-black uppercase tracking-wider text-yellow-300">Héroes favoritos</span><h2 className="mt-2 text-3xl font-black leading-none">Acción y aventura</h2><Button asChild variant="yellow" size="sm" className="mt-5 w-fit"><Link href="/catalog?collection=Marvel">Comprar ahora</Link></Button></div>
<div className="relative min-h-56 bg-white"><Image src="/images/promo/promo-marvel.webp" alt="Colección Marvel" fill sizes="(max-width: 1024px) 50vw, 18vw" className="object-cover object-center" /></div>
</motion.article>
<motion.article initial={reduceMotion ? false : { opacity: 0, x: 18 }} animate={{ opacity: 1, x: 0 }} transition={{ delay: .14 }} className="grid min-h-60 overflow-hidden rounded-[2rem] bg-violet-600 text-white shadow-xl sm:grid-cols-[.9fr_1.1fr] lg:min-h-0">
<div className="relative z-10 flex flex-col justify-center bg-gradient-to-br from-violet-600 to-cyan-600 p-6"><div className="inline-flex w-fit items-center gap-1 rounded-full bg-white px-3 py-1 text-xs font-black text-fuchsia-600"><BadgePercent className="size-4" />Novedad</div><h2 className="mt-3 text-3xl font-black leading-none">Tesoros para regalar</h2><Button asChild variant="outline" size="sm" className="mt-5 w-fit border-white bg-white"><Link href="/catalog">Descubrir</Link></Button></div>
<div className="relative min-h-56 bg-white"><Image src="/images/promo/promo-characters.webp" alt="Personajes y peluches" fill sizes="(max-width: 1024px) 50vw, 18vw" className="object-cover object-center" /></div>
</motion.article>
</div>
</div>
</section>
);
}
@@ -0,0 +1,114 @@
"use client";
import Image from "next/image";
import Link from "next/link";
import { ArrowRight, BadgePercent, Sparkles } from "lucide-react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
import { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
// ADR 0008 sub-fase 4: hero azul con dots de paginacion (carrusel), en vez
// del bento estatico de 3 tarjetas en fucsia/rojo/violeta del esquema
// arcoiris original. Un slide a la vez, fondo azul de marca en los tres,
// para leerse como una sola pieza de marca en vez de 3 bloques de colores
// distintos.
const slides = [
{
id: "celebracion",
badge: "Celebración ARI",
icon: Sparkles,
title: "Hasta 50% OFF",
subtitle: "en tesoros seleccionados",
description: "Personajes, mochilas y regalos que convierten cualquier día en una celebración.",
href: "/catalog",
image: "/images/promo/hero-vibrant.webp",
imageAlt: "Promoción principal de Ari Shopping",
},
{
id: "marvel",
badge: "Héroes favoritos",
icon: BadgePercent,
title: "Acción y aventura",
subtitle: "colección Marvel",
description: "Figuras, mochilas y sets listos para la próxima misión.",
href: "/catalog?collection=Marvel",
image: "/images/promo/promo-marvel.webp",
imageAlt: "Colección Marvel",
},
{
id: "regalos",
badge: "Novedad",
icon: Sparkles,
title: "Tesoros para regalar",
subtitle: "",
description: "Personajes y peluches para sorprender en cualquier ocasión.",
href: "/catalog",
image: "/images/promo/promo-characters.webp",
imageAlt: "Personajes y peluches",
},
];
const AUTOPLAY_MS = 6000;
export function HeroCarousel() {
const reduceMotion = useReducedMotion();
const [index, setIndex] = useState(0);
useEffect(() => {
if (reduceMotion) return;
const timer = setInterval(() => setIndex((current) => (current + 1) % slides.length), AUTOPLAY_MS);
return () => clearInterval(timer);
}, [reduceMotion]);
const slide = slides[index];
if (!slide) return null;
const Icon = slide.icon;
return (
<section className="mx-auto max-w-7xl px-4 py-7 lg:py-10">
<div className="relative overflow-hidden rounded-[2rem] bg-ari-marino text-white shadow-2xl">
<AnimatePresence mode="wait">
<motion.article
key={slide.id}
initial={reduceMotion ? false : { opacity: 0 }}
animate={{ opacity: 1 }}
exit={reduceMotion ? undefined : { opacity: 0 }}
transition={{ duration: 0.4 }}
className="grid min-h-[420px] lg:min-h-[500px] lg:grid-cols-2"
>
<div className="relative z-10 flex flex-col justify-center p-7 lg:p-14">
<div className="mb-5 inline-flex w-fit items-center gap-2 rounded-full bg-ari-coral-claro px-4 py-2 text-xs font-black uppercase tracking-wider text-ari-marino">
<Icon className="size-4" />
{slide.badge}
</div>
<h1 className="text-5xl font-black leading-[.95] tracking-[-.04em] sm:text-6xl">{slide.title}</h1>
{slide.subtitle && <p className="mt-2 text-2xl font-bold text-ari-coral-claro">{slide.subtitle}</p>}
<p className="mt-6 max-w-md text-base font-semibold text-white/90 sm:text-lg">{slide.description}</p>
<Button asChild variant="yellow" size="lg" className="mt-8 w-fit">
<Link href={slide.href}>
Comprar ahora <ArrowRight className="size-5" />
</Link>
</Button>
</div>
<div className="relative min-h-[280px] bg-white lg:min-h-full">
<Image src={slide.image} alt={slide.imageAlt} fill priority={index === 0} sizes="(max-width: 1024px) 100vw, 50vw" className="object-cover object-center" />
</div>
</motion.article>
</AnimatePresence>
<div className="absolute inset-x-0 bottom-5 z-20 flex justify-center gap-2">
{slides.map((item, itemIndex) => (
<button
key={item.id}
type="button"
aria-label={`Ir a la diapositiva ${itemIndex + 1}: ${item.badge}`}
aria-current={itemIndex === index ? "true" : undefined}
onClick={() => setIndex(itemIndex)}
className={`h-2.5 rounded-full transition-all ${itemIndex === index ? "w-7 bg-ari-coral-claro" : "w-2.5 bg-white/50 hover:bg-white/80"}`}
/>
))}
</div>
</div>
</section>
);
}
@@ -52,9 +52,13 @@ export function Navbar({ products, categories }: { products: Product[]; categori
<div className="relative overflow-visible bg-white"> <div className="relative overflow-visible bg-white">
<div className="pointer-events-none absolute inset-x-0 top-0 h-1 bg-ari-coral-claro" /> <div className="pointer-events-none absolute inset-x-0 top-0 h-1 bg-ari-coral-claro" />
<div className="mx-auto grid max-w-7xl grid-cols-[auto_1fr_auto] items-center gap-4 px-4 py-4 lg:gap-8"> <div className="mx-auto grid max-w-7xl grid-cols-[auto_1fr_auto] items-center gap-4 px-4 py-4 lg:gap-8">
{/* Isotipo circular con icono + wordmark integrados (como en la
version previa a ADR 0008), pedido explicito del usuario en
vez del lockup rectangular. logo-ari.svg (rectangular) se
mantiene para el footer. */}
<Link href="/" aria-label="ARI shopping" className="shrink-0"> <Link href="/" aria-label="ARI shopping" className="shrink-0">
{/* eslint-disable-next-line @next/next/no-img-element -- vector propio, next/image no optimiza SVG sin habilitar dangerouslyAllowSVG */} {/* eslint-disable-next-line @next/next/no-img-element -- vector propio, next/image no optimiza SVG sin habilitar dangerouslyAllowSVG */}
<img src="/images/brand/logo-ari.svg" alt="ARI shopping" className="h-11 w-auto lg:h-14" width={200} height={64} /> <img src="/images/brand/logo-ari-circular.svg" alt="ARI shopping" className="size-16 lg:size-20" width={200} height={200} />
</Link> </Link>
<PredictiveSearch /> <PredictiveSearch />
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@@ -18,5 +18,5 @@ export function ProductPurchase({ product }: { product: Product }) {
selectedVariant.inventoryQuantity <= LOW_STOCK_THRESHOLD selectedVariant.inventoryQuantity <= LOW_STOCK_THRESHOLD
? selectedVariant.inventoryQuantity ? selectedVariant.inventoryQuantity
: null; : null;
return <div><div className="mb-5 flex flex-wrap gap-2">{product.variants.map((variant) => <button key={variant.id} onClick={() => setVariantId(variant.id)} className={`rounded-full border px-4 py-2 text-sm font-bold ${variantId === variant.id ? "border-fuchsia-500 bg-fuchsia-50 text-fuchsia-700" : "border-slate-200"}`}>{variant.value}</button>)}</div><div className="mb-6 flex items-end gap-3"><strong className="text-3xl font-black">{formatMoney(product.price, product.currency)}</strong>{product.compareAtPrice && <del className="text-slate-400">{formatMoney(product.compareAtPrice, product.currency)}</del>}</div><Button variant="primary" size="lg" className="w-full" onClick={() => addItem(product, variantId)}><ShoppingBag />Añadir al carrito</Button><div className="mt-5 grid gap-3 rounded-2xl bg-slate-50 p-4 text-sm">{lowStock !== null ? <span className="flex items-center gap-2 font-bold text-amber-600"><TriangleAlert className="size-4" />Quedan {lowStock} unidades</span> : <span className="flex items-center gap-2 font-bold"><CheckCircle2 className="size-4 text-emerald-500" />Disponibilidad por confirmar</span>}<span className="flex items-center gap-2 font-bold"><Truck className="size-4 text-cyan-600" />Envíos nacionales</span></div></div>; return <div><div className="mb-5 flex flex-wrap gap-2">{product.variants.map((variant) => <button key={variant.id} onClick={() => setVariantId(variant.id)} className={`rounded-full border px-4 py-2 text-sm font-bold ${variantId === variant.id ? "border-ari-marino bg-ari-marino text-white" : "border-slate-200"}`}>{variant.value}</button>)}</div><div className="mb-6 flex items-end gap-3"><strong className="text-3xl font-black">{formatMoney(product.price, product.currency)}</strong>{product.compareAtPrice && <del className="text-slate-400">{formatMoney(product.compareAtPrice, product.currency)}</del>}</div>{/* oculto en mobile: MobileAddToCart (barra fija abajo, lg:hidden) ya cubre esa misma accion ahi -- sin el hidden aqui, ambos se mostraban a la vez. */}<Button variant="primary" size="lg" className="hidden w-full lg:inline-flex" onClick={() => addItem(product, variantId)}><ShoppingBag />Añadir al carrito</Button><div className="mt-5 grid gap-3 rounded-2xl bg-slate-50 p-4 text-sm">{lowStock !== null ? <span className="flex items-center gap-2 font-bold text-amber-600"><TriangleAlert className="size-4" />Quedan {lowStock} unidades</span> : <span className="flex items-center gap-2 font-bold"><CheckCircle2 className="size-4 text-emerald-500" />Disponibilidad por confirmar</span>}<span className="flex items-center gap-2 font-bold"><Truck className="size-4 text-cyan-600" />Envíos nacionales</span></div></div>;
} }
@@ -5,7 +5,6 @@ import Link from "next/link";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { Search, X } from "lucide-react"; import { Search, X } from "lucide-react";
import { FormEvent, useEffect, useState } from "react"; import { FormEvent, useEffect, useState } from "react";
import { Input } from "@/components/ui/input";
import type { Product } from "@/lib/headless/types"; import type { Product } from "@/lib/headless/types";
export function PredictiveSearch() { export function PredictiveSearch() {
@@ -54,12 +53,34 @@ export function PredictiveSearch() {
return ( return (
<form onSubmit={submit} className="relative w-full"> <form onSubmit={submit} className="relative w-full">
<div className="rounded-full bg-gradient-to-r from-cyan-400 via-violet-500 to-fuchsia-500 p-[2px] shadow-lg shadow-violet-500/10"> <div className="flex items-center rounded-full border-2 border-slate-200 bg-white pl-5 pr-1.5 shadow-sm transition focus-within:border-ari-marino focus-within:ring-4 focus-within:ring-ari-coral-claro/30">
<div className="relative rounded-full bg-white"> {/* input propio (no el <Input> compartido) -- ese componente trae su
<Search className="pointer-events-none absolute left-5 top-1/2 z-10 size-5 -translate-y-1/2 text-violet-600" /> propio focus:ring-4 focus:ring-cyan-300/30 hardcodeado que no se
<Input value={query} onChange={(event) => setQuery(event.target.value)} placeholder="Busca juguetes, mochilas, personajes…" className="h-[52px] border-0 bg-transparent pl-[52px] pr-12 shadow-none focus-visible:ring-violet-300/30" /> anulaba de forma confiable via tailwind-merge dentro de este
{query && <button type="button" aria-label="Limpiar búsqueda" className="absolute right-4 top-1/2 -translate-y-1/2 rounded-full p-1 text-slate-400 hover:bg-slate-100" onClick={() => setQuery("")}><X className="size-4" /></button>} contenedor compuesto (input + boton circular), causando un
</div> segundo ring visible al hacer foco. Mas simple y robusto no
heredar clases de foco que hay que pelear despues.
focus-visible:outline-none (no outline-none a secas): globals.css
define un outline de accesibilidad global por teclado
(input:focus-visible, azul de marca) que le gana en
especificidad a un outline-none plano. El wrapper de este
componente ya tiene su propio indicador de foco visible
(focus-within:border-ari-marino + ring amarillo, mismo patron
que button.tsx) que cumple el mismo proposito de accesibilidad
sin duplicarlo -- variante focus-visible: en vez de la base
iguala la especificidad de la regla global (mismo truco que ya
usa button.tsx) y evita el doble indicador de foco. */}
<input
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder="Busca juguetes, mochilas, personajes…"
className="h-[48px] w-full border-0 bg-transparent p-0 text-sm text-slate-950 focus-visible:outline-none"
/>
{query && <button type="button" aria-label="Limpiar búsqueda" className="mr-1 shrink-0 rounded-full p-1.5 text-slate-400 hover:bg-slate-100" onClick={() => setQuery("")}><X className="size-4" /></button>}
<button type="submit" aria-label="Buscar" className="grid size-10 shrink-0 place-items-center rounded-full bg-ari-marino text-white transition hover:brightness-110">
<Search className="size-4.5" />
</button>
</div> </div>
{loading && normalized.length >= 2 && matches.length === 0 && ( {loading && normalized.length >= 2 && matches.length === 0 && (
<div className="absolute left-0 right-0 top-[calc(100%+10px)] z-50 rounded-2xl border border-slate-200 bg-white p-4 text-center text-sm text-slate-500 shadow-2xl"> <div className="absolute left-0 right-0 top-[calc(100%+10px)] z-50 rounded-2xl border border-slate-200 bg-white p-4 text-center text-sm text-slate-500 shadow-2xl">
@@ -69,12 +90,12 @@ export function PredictiveSearch() {
{matches.length > 0 && ( {matches.length > 0 && (
<div className="absolute left-0 right-0 top-[calc(100%+10px)] z-50 overflow-hidden rounded-2xl border border-slate-200 bg-white p-2 shadow-2xl"> <div className="absolute left-0 right-0 top-[calc(100%+10px)] z-50 overflow-hidden rounded-2xl border border-slate-200 bg-white p-2 shadow-2xl">
{matches.map((product) => ( {matches.map((product) => (
<Link key={product.id} href={`/product/${product.slug}`} prefetch={false} className="grid grid-cols-[54px_1fr] items-center gap-3 rounded-xl p-2 hover:bg-violet-50" onClick={() => setQuery("")}> <Link key={product.id} href={`/product/${product.slug}`} prefetch={false} className="grid grid-cols-[54px_1fr] items-center gap-3 rounded-xl p-2 hover:bg-ari-marino/5" onClick={() => setQuery("")}>
<div className="relative aspect-square overflow-hidden rounded-lg bg-slate-100"><Image src={product.thumbnail} alt="" fill sizes="54px" className="object-cover" /></div> <div className="relative aspect-square overflow-hidden rounded-lg bg-slate-100"><Image src={product.thumbnail} alt="" fill sizes="54px" className="object-cover" /></div>
<div><strong className="line-clamp-1 text-sm">{product.name}</strong><span className="text-xs text-slate-500">{product.category} · {product.collection}</span></div> <div><strong className="line-clamp-1 text-sm">{product.name}</strong><span className="text-xs text-slate-500">{product.category} · {product.collection}</span></div>
</Link> </Link>
))} ))}
<button type="submit" className="mt-1 w-full rounded-xl bg-slate-950 px-4 py-3 text-sm font-black text-white hover:bg-violet-700">Buscar “{query.trim()}” en todo el catálogo</button> <button type="submit" className="mt-1 w-full rounded-xl bg-ari-marino px-4 py-3 text-sm font-black text-white hover:brightness-110">Buscar “{query.trim()}” en todo el catálogo</button>
</div> </div>
)} )}
</form> </form>
@@ -25,7 +25,7 @@ spec:
runAsUser: 1001 runAsUser: 1001
containers: containers:
- name: app - name: app
image: gitea.cruzcloud.net/devops/ecommerce-frontend:v1.0.185 image: gitea.cruzcloud.net/devops/ecommerce-frontend:v1.0.202
env: env:
- name: HEADLESS_PROVIDER - name: HEADLESS_PROVIDER
value: medusa value: medusa
+16
View File
@@ -42,6 +42,22 @@ const nextConfig: NextConfig = {
}, },
], ],
}, },
// Assets de marca (logo, favicon) cambian con cada iteracion de
// identidad visual, a diferencia de las fotos de producto -- el
// cache de 7 dias de la regla anterior dejaba servido el logo
// viejo en Cloudflare/navegador durante toda una semana despues de
// cada deploy (ver ADR 0008). Next.js aplica el ultimo header que
// matchee un mismo path, asi que esta regla, al ir despues,
// sobreescribe el Cache-Control solo para /images/brand/*.
{
source: "/images/brand/:path*",
headers: [
{
key: "Cache-Control",
value: "public, max-age=300, must-revalidate",
},
],
},
]; ];
}, },
}; };
@@ -0,0 +1,17 @@
<!-- ADR 0008, paleta "Azul & Amarillo". Isotipo circular (formato de la
version previa a ADR 0008, pedido explicito del usuario en vez del
lockup rectangular de logo-ari.svg): mariposa geometrica de 4 elipses
+ wordmark "ARI shopping", todo dentro del mismo circulo azul.
Ver docs-portal/docs/decisiones/0008-identidad-navy-coral.md -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" role="img" aria-label="ARI shopping">
<circle cx="100" cy="100" r="98" fill="#0055B8"/>
<g transform="translate(100 68) scale(2.6) translate(-17 -11)">
<ellipse cx="10" cy="8" rx="9" ry="7" transform="rotate(-18 10 8)" fill="#FFC220"/>
<ellipse cx="24" cy="8" rx="9" ry="7" transform="rotate(18 24 8)" fill="#FFC220"/>
<ellipse cx="12" cy="16" rx="6" ry="5" transform="rotate(-10 12 16)" fill="#FFDD73"/>
<ellipse cx="22" cy="16" rx="6" ry="5" transform="rotate(10 22 16)" fill="#FFDD73"/>
<rect x="16" y="3" width="2" height="18" rx="1" fill="#0055B8"/>
</g>
<text x="100" y="132" text-anchor="middle" font-family="Poppins, ui-sans-serif, system-ui, sans-serif" font-weight="700" font-size="38" fill="#FFFFFF">ARI</text>
<text x="100" y="153" text-anchor="middle" font-family="Poppins, ui-sans-serif, system-ui, sans-serif" font-weight="700" font-size="14" letter-spacing="3" fill="#FFC220">shopping</text>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB