Landing del cliente: galería apaisada con lightbox y quita "Entrar"
La galería de trabajos (GaleriaTrabajos) en la landing personalizada del cliente pasa a formato apaisado (3/2) y, al pulsar una foto, se amplía en un lightbox con navegación ‹ ›, Esc y clic fuera para cerrar. Se quita el botón "Entrar" del header de esa landing (TenantBrand sin showLogin): el cliente final no entra al panel. Revierte el cambio anterior en public/b2b.html (era la landing equivocada). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1472,8 +1472,8 @@ h3, h4, h5, h6 {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 1px solid var(--surface-border);
|
border: 1px solid var(--surface-border);
|
||||||
}
|
}
|
||||||
.step .ba figure { position: relative; margin: 0; aspect-ratio: 4 / 3; }
|
.step .ba figure { position: relative; margin: 0; aspect-ratio: 1 / 1; }
|
||||||
.step .ba img { width: 100%; height: 100%; object-fit: cover; display: block; cursor: zoom-in; }
|
.step .ba img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
||||||
.step .ba figcaption {
|
.step .ba figcaption {
|
||||||
position: absolute; top: 6px; left: 6px;
|
position: absolute; top: 6px; left: 6px;
|
||||||
background: rgba(0, 0, 0, 0.6); color: #fff;
|
background: rgba(0, 0, 0, 0.6); color: #fff;
|
||||||
@@ -1482,34 +1482,6 @@ h3, h4, h5, h6 {
|
|||||||
}
|
}
|
||||||
.step .ba figure.after figcaption { background: var(--color-primary-600); }
|
.step .ba figure.after figcaption { background: var(--color-primary-600); }
|
||||||
|
|
||||||
/* Galería de ejemplos (apaisada, ampliable) */
|
|
||||||
.gallery-grid { display: grid; grid-template-columns: 1fr; gap: var(--space-4); margin-top: var(--space-8); }
|
|
||||||
@media (min-width: 640px) { .gallery-grid { grid-template-columns: repeat(2, 1fr); } }
|
|
||||||
@media (min-width: 1024px) { .gallery-grid { grid-template-columns: repeat(3, 1fr); gap: var(--space-6); } }
|
|
||||||
.gcard {
|
|
||||||
position: relative; margin: 0; border-radius: 14px; overflow: hidden;
|
|
||||||
border: 1px solid var(--surface-border); cursor: zoom-in;
|
|
||||||
background: var(--color-neutral-100, #f4f4f5);
|
|
||||||
}
|
|
||||||
.gcard img { width: 100%; aspect-ratio: 16 / 10; object-fit: cover; display: block; transition: transform .45s ease; }
|
|
||||||
.gcard:hover img { transform: scale(1.04); }
|
|
||||||
.gcard figcaption {
|
|
||||||
position: absolute; left: 10px; bottom: 10px;
|
|
||||||
background: rgba(0, 0, 0, 0.62); color: #fff;
|
|
||||||
font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.06em;
|
|
||||||
padding: 3px 8px; border-radius: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Lightbox */
|
|
||||||
#lightbox { position: fixed; inset: 0; z-index: 100; display: none; align-items: center; justify-content: center; padding: 24px; background: rgba(8, 12, 20, 0.92); }
|
|
||||||
#lightbox.open { display: flex; }
|
|
||||||
#lightbox .lb-img { max-width: min(1100px, 94vw); max-height: 86vh; width: auto; height: auto; border-radius: 12px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); }
|
|
||||||
#lightbox .lb-cap { position: absolute; bottom: 18px; left: 0; right: 0; text-align: center; color: rgba(255, 255, 255, 0.85); font-family: var(--font-mono); font-size: 12px; letter-spacing: 0.06em; padding: 0 16px; }
|
|
||||||
#lightbox .lb-close { position: absolute; top: 16px; right: 18px; width: 40px; height: 40px; border: none; border-radius: 50%; background: rgba(255, 255, 255, 0.12); color: #fff; font-size: 22px; line-height: 1; cursor: pointer; }
|
|
||||||
#lightbox .lb-btn { position: absolute; top: 50%; transform: translateY(-50%); width: 46px; height: 46px; border: none; border-radius: 50%; background: rgba(255, 255, 255, 0.12); color: #fff; font-size: 26px; line-height: 1; cursor: pointer; }
|
|
||||||
#lightbox .lb-prev { left: 18px; } #lightbox .lb-next { right: 18px; }
|
|
||||||
#lightbox .lb-close:hover, #lightbox .lb-btn:hover { background: rgba(255, 255, 255, 0.26); }
|
|
||||||
|
|
||||||
/* Bajo reduced-motion: mostrar todo sin animar */
|
/* Bajo reduced-motion: mostrar todo sin animar */
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
html.js .reveal,
|
html.js .reveal,
|
||||||
@@ -1536,6 +1508,7 @@ h3, h4, h5, h6 {
|
|||||||
<a href="#faq">Preguntas</a>
|
<a href="#faq">Preguntas</a>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="topbar-actions">
|
<div class="topbar-actions">
|
||||||
|
<a href="/login" class="btn btn-ghost btn-sm">Entrar</a>
|
||||||
<a href="/signup" class="btn btn-primary btn-sm">Empezar gratis</a>
|
<a href="/signup" class="btn btn-primary btn-sm">Empezar gratis</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1732,8 +1705,8 @@ h3, h4, h5, h6 {
|
|||||||
<h4>Se genera un render orientativo</h4>
|
<h4>Se genera un render orientativo</h4>
|
||||||
<p>A partir de las fotos del cliente, Reformix devuelve 3 propuestas visuales con calidades básica, media y premium. Ya tienes de qué hablar en la visita.</p>
|
<p>A partir de las fotos del cliente, Reformix devuelve 3 propuestas visuales con calidades básica, media y premium. Ya tienes de qué hablar en la visita.</p>
|
||||||
<div class="ba" aria-label="Comparativa antes y después del render">
|
<div class="ba" aria-label="Comparativa antes y después del render">
|
||||||
<figure><img src="/b2b-assets/img/antes.webp" alt="Cocina antes de la reforma" data-zoom data-gallery="ba-cocina" data-zoom-cap="Cocina · antes" loading="lazy" decoding="async"><figcaption>ANTES</figcaption></figure>
|
<figure><img src="/b2b-assets/img/antes.webp" alt="Cocina antes de la reforma" loading="lazy" decoding="async"><figcaption>ANTES</figcaption></figure>
|
||||||
<figure class="after"><img src="/b2b-assets/img/despues.webp" alt="Render de la cocina reformada" data-zoom data-gallery="ba-cocina" data-zoom-cap="Cocina · render orientativo" loading="lazy" decoding="async"><figcaption>DESPUÉS</figcaption></figure>
|
<figure class="after"><img src="/b2b-assets/img/despues.webp" alt="Render de la cocina reformada" loading="lazy" decoding="async"><figcaption>DESPUÉS</figcaption></figure>
|
||||||
</div>
|
</div>
|
||||||
<div class="tag">⌁ Render IA</div>
|
<div class="tag">⌁ Render IA</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1753,33 +1726,6 @@ h3, h4, h5, h6 {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- ============================================
|
|
||||||
Galería de ejemplos
|
|
||||||
============================================ -->
|
|
||||||
<section class="section section-muted" id="ejemplos">
|
|
||||||
<div class="container">
|
|
||||||
<div class="section-head center">
|
|
||||||
<p class="section-kicker">Ejemplos reales</p>
|
|
||||||
<h2 class="section-title">El «después» que tu cliente ve <em>antes de que vayas</em>.</h2>
|
|
||||||
<p class="section-lede">Una foto del espacio y Reformix devuelve el render orientativo. Toca cualquiera para ampliarla.</p>
|
|
||||||
</div>
|
|
||||||
<div class="gallery-grid">
|
|
||||||
<figure class="gcard">
|
|
||||||
<img src="/b2b-assets/img/despues.webp" alt="Render de cocina reformada" data-zoom data-gallery="ejemplos" data-zoom-cap="Cocina · render orientativo" loading="lazy" decoding="async">
|
|
||||||
<figcaption>Cocina</figcaption>
|
|
||||||
</figure>
|
|
||||||
<figure class="gcard">
|
|
||||||
<img src="/b2b-assets/img/despues-bano.webp" alt="Render de baño reformado" data-zoom data-gallery="ejemplos" data-zoom-cap="Baño · render orientativo" loading="lazy" decoding="async">
|
|
||||||
<figcaption>Baño</figcaption>
|
|
||||||
</figure>
|
|
||||||
<figure class="gcard">
|
|
||||||
<img src="/b2b-assets/img/despues-comedor.webp" alt="Render de salón-comedor reformado" data-zoom data-gallery="ejemplos" data-zoom-cap="Salón-comedor · render orientativo" loading="lazy" decoding="async">
|
|
||||||
<figcaption>Salón-comedor</figcaption>
|
|
||||||
</figure>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- ============================================
|
<!-- ============================================
|
||||||
Demo / WhatsApp
|
Demo / WhatsApp
|
||||||
============================================ -->
|
============================================ -->
|
||||||
@@ -2214,66 +2160,4 @@ h3, h4, h5, h6 {
|
|||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- ============================================
|
|
||||||
Lightbox (galería ampliable)
|
|
||||||
============================================ -->
|
|
||||||
<div id="lightbox" aria-hidden="true" role="dialog" aria-modal="true" aria-label="Imagen ampliada">
|
|
||||||
<button class="lb-close" type="button" aria-label="Cerrar">×</button>
|
|
||||||
<button class="lb-btn lb-prev" type="button" aria-label="Anterior">‹</button>
|
|
||||||
<img class="lb-img" src="" alt="">
|
|
||||||
<button class="lb-btn lb-next" type="button" aria-label="Siguiente">›</button>
|
|
||||||
<div class="lb-cap"></div>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
(function () {
|
|
||||||
var lb = document.getElementById('lightbox');
|
|
||||||
if (!lb) return;
|
|
||||||
var imgEl = lb.querySelector('.lb-img');
|
|
||||||
var capEl = lb.querySelector('.lb-cap');
|
|
||||||
var btns = lb.querySelectorAll('.lb-btn');
|
|
||||||
var group = [], idx = 0;
|
|
||||||
|
|
||||||
function render() {
|
|
||||||
var t = group[idx];
|
|
||||||
if (!t) return;
|
|
||||||
imgEl.src = t.currentSrc || t.src;
|
|
||||||
imgEl.alt = t.alt || '';
|
|
||||||
capEl.textContent = t.getAttribute('data-zoom-cap') || t.alt || '';
|
|
||||||
var multi = group.length > 1;
|
|
||||||
Array.prototype.forEach.call(btns, function (b) { b.style.display = multi ? '' : 'none'; });
|
|
||||||
}
|
|
||||||
function open(trigger) {
|
|
||||||
var g = trigger.getAttribute('data-gallery') || 'default';
|
|
||||||
group = Array.prototype.slice.call(document.querySelectorAll('[data-zoom][data-gallery="' + g + '"]'));
|
|
||||||
idx = group.indexOf(trigger);
|
|
||||||
if (idx < 0) idx = 0;
|
|
||||||
render();
|
|
||||||
lb.classList.add('open');
|
|
||||||
lb.setAttribute('aria-hidden', 'false');
|
|
||||||
document.body.style.overflow = 'hidden';
|
|
||||||
}
|
|
||||||
function close() {
|
|
||||||
lb.classList.remove('open');
|
|
||||||
lb.setAttribute('aria-hidden', 'true');
|
|
||||||
document.body.style.overflow = '';
|
|
||||||
}
|
|
||||||
function move(d) { if (group.length) { idx = (idx + d + group.length) % group.length; render(); } }
|
|
||||||
|
|
||||||
document.addEventListener('click', function (e) {
|
|
||||||
var z = e.target.closest && e.target.closest('[data-zoom]');
|
|
||||||
if (z) { e.preventDefault(); open(z); return; }
|
|
||||||
if (!lb.classList.contains('open')) return;
|
|
||||||
if (e.target === lb || (e.target.closest && e.target.closest('.lb-close'))) { close(); return; }
|
|
||||||
if (e.target.closest && e.target.closest('.lb-prev')) { move(-1); return; }
|
|
||||||
if (e.target.closest && e.target.closest('.lb-next')) { move(1); return; }
|
|
||||||
});
|
|
||||||
document.addEventListener('keydown', function (e) {
|
|
||||||
if (!lb.classList.contains('open')) return;
|
|
||||||
if (e.key === 'Escape') close();
|
|
||||||
else if (e.key === 'ArrowRight') move(1);
|
|
||||||
else if (e.key === 'ArrowLeft') move(-1);
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body></html>
|
</body></html>
|
||||||
@@ -46,7 +46,7 @@ export default async function FunnelPage({ params }: { params: Promise<{ slug: s
|
|||||||
className={theme.heading === 'serif' ? 'theme-serif' : undefined}
|
className={theme.heading === 'serif' ? 'theme-serif' : undefined}
|
||||||
style={themeStyle(tenant.themePreset, tenant.themeColor)}
|
style={themeStyle(tenant.themePreset, tenant.themeColor)}
|
||||||
>
|
>
|
||||||
<TenantBrand nombreEmpresa={tenant.nombreEmpresa} logoUrl={tenant.logoUrl} showLogin />
|
<TenantBrand nombreEmpresa={tenant.nombreEmpresa} logoUrl={tenant.logoUrl} />
|
||||||
<main id="main-content">
|
<main id="main-content">
|
||||||
<Hero slug={tenant.slug} />
|
<Hero slug={tenant.slug} />
|
||||||
<ReformaSlider />
|
<ReformaSlider />
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import type { PublicGaleriaFoto } from '@/lib/funnel/public-queries';
|
import type { PublicGaleriaFoto } from '@/lib/funnel/public-queries';
|
||||||
|
|
||||||
type GaleriaTrabajosProps = {
|
type GaleriaTrabajosProps = {
|
||||||
@@ -5,11 +8,37 @@ type GaleriaTrabajosProps = {
|
|||||||
nombreEmpresa: string;
|
nombreEmpresa: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Galería de trabajos del reformista en su landing pública. Solo se muestra si
|
// Galería de trabajos del reformista en su landing pública. Solo se muestra si el reformista ha
|
||||||
// el reformista ha subido fotos desde su panel.
|
// subido fotos desde su panel. Formato apaisado y, al pulsar una foto, se amplía en un lightbox
|
||||||
|
// con navegación entre todas las imágenes.
|
||||||
export default function GaleriaTrabajos({ fotos, nombreEmpresa }: GaleriaTrabajosProps) {
|
export default function GaleriaTrabajos({ fotos, nombreEmpresa }: GaleriaTrabajosProps) {
|
||||||
|
const [idx, setIdx] = useState<number | null>(null);
|
||||||
|
|
||||||
|
const cerrar = useCallback(() => setIdx(null), []);
|
||||||
|
const mover = useCallback(
|
||||||
|
(d: number) => setIdx((cur) => (cur === null ? cur : (cur + d + fotos.length) % fotos.length)),
|
||||||
|
[fotos.length],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (idx === null) return;
|
||||||
|
const onKey = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Escape') cerrar();
|
||||||
|
else if (e.key === 'ArrowRight') mover(1);
|
||||||
|
else if (e.key === 'ArrowLeft') mover(-1);
|
||||||
|
};
|
||||||
|
document.addEventListener('keydown', onKey);
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('keydown', onKey);
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
};
|
||||||
|
}, [idx, cerrar, mover]);
|
||||||
|
|
||||||
if (fotos.length === 0) return null;
|
if (fotos.length === 0) return null;
|
||||||
|
|
||||||
|
const actual = idx !== null ? fotos[idx] : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section id="galeria" className="bg-gray-50 section" aria-label="Galería de trabajos">
|
<section id="galeria" className="bg-gray-50 section" aria-label="Galería de trabajos">
|
||||||
<div className="container">
|
<div className="container">
|
||||||
@@ -24,24 +53,31 @@ export default function GaleriaTrabajos({ fotos, nombreEmpresa }: GaleriaTrabajo
|
|||||||
Reformas que ya hemos hecho
|
Reformas que ya hemos hecho
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-gray-500 mt-3 leading-relaxed">
|
<p className="text-gray-500 mt-3 leading-relaxed">
|
||||||
Una muestra real del trabajo de {nombreEmpresa}. Calidad de acabados, plazos cumplidos.
|
Una muestra real del trabajo de {nombreEmpresa}. Toca cualquier imagen para verla en grande.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-3 md:gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 md:gap-4">
|
||||||
{fotos.map((f) => (
|
{fotos.map((f, i) => (
|
||||||
<figure
|
<figure
|
||||||
key={f.id}
|
key={f.id}
|
||||||
className="group relative aspect-[4/3] overflow-hidden rounded-xl bg-gray-100 border border-gray-200"
|
className="group relative aspect-[3/2] overflow-hidden rounded-xl bg-gray-100 border border-gray-200"
|
||||||
>
|
>
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
<button
|
||||||
<img
|
type="button"
|
||||||
src={f.url}
|
onClick={() => setIdx(i)}
|
||||||
alt={f.titulo ?? `Reforma de ${nombreEmpresa}`}
|
className="block h-full w-full cursor-zoom-in"
|
||||||
className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105"
|
aria-label={`Ampliar ${f.titulo ?? `reforma de ${nombreEmpresa}`}`}
|
||||||
/>
|
>
|
||||||
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
|
<img
|
||||||
|
src={f.url}
|
||||||
|
alt={f.titulo ?? `Reforma de ${nombreEmpresa}`}
|
||||||
|
className="h-full w-full object-cover transition-transform duration-500 group-hover:scale-105"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
{f.titulo && (
|
{f.titulo && (
|
||||||
<figcaption className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/70 to-transparent p-3 text-white text-sm font-semibold opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
<figcaption className="pointer-events-none absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/70 to-transparent p-3 text-white text-sm font-semibold opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
||||||
{f.titulo}
|
{f.titulo}
|
||||||
</figcaption>
|
</figcaption>
|
||||||
)}
|
)}
|
||||||
@@ -49,6 +85,67 @@ export default function GaleriaTrabajos({ fotos, nombreEmpresa }: GaleriaTrabajo
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{actual && (
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 z-[100] flex items-center justify-center bg-black/90 p-4 sm:p-8"
|
||||||
|
onClick={cerrar}
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-label="Imagen ampliada"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={cerrar}
|
||||||
|
aria-label="Cerrar"
|
||||||
|
className="absolute right-4 top-4 flex h-10 w-10 items-center justify-center rounded-full bg-white/15 text-2xl leading-none text-white hover:bg-white/30"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{fotos.length > 1 && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
mover(-1);
|
||||||
|
}}
|
||||||
|
aria-label="Anterior"
|
||||||
|
className="absolute left-3 top-1/2 flex h-11 w-11 -translate-y-1/2 items-center justify-center rounded-full bg-white/15 text-3xl leading-none text-white hover:bg-white/30 sm:left-6"
|
||||||
|
>
|
||||||
|
‹
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
|
<img
|
||||||
|
src={actual.url}
|
||||||
|
alt={actual.titulo ?? `Reforma de ${nombreEmpresa}`}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
className="max-h-[86vh] max-w-[94vw] w-auto rounded-lg shadow-2xl"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{fotos.length > 1 && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
mover(1);
|
||||||
|
}}
|
||||||
|
aria-label="Siguiente"
|
||||||
|
className="absolute right-3 top-1/2 flex h-11 w-11 -translate-y-1/2 items-center justify-center rounded-full bg-white/15 text-3xl leading-none text-white hover:bg-white/30 sm:right-6"
|
||||||
|
>
|
||||||
|
›
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{actual.titulo && (
|
||||||
|
<div className="pointer-events-none absolute inset-x-0 bottom-5 text-center text-sm font-medium text-white/85">
|
||||||
|
{actual.titulo}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user