feat: adaptive promo images with blurred fill

Replace cover-crop background with PromoImage component: sharp
contain image over a blurred cover copy. Whole image visible on
any aspect ratio, no flat letterbox bars. Applied to accueil,
adherent profile, backend promotions, member space preview +
upload dropzone.

Also retheme adherent logo frame (cream gradient + warm border)
instead of flat white.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MichaelandClaude Opus 4.8 committed 2026-06-27 14:59:46 +02:00
1 parent c199d7a7db
commit 03eca6e9ae
5 files changed
+60 -19

No files matched your search

+6 -3
View File
@@ -5,6 +5,7 @@ import { Sparkle } from "@/components/Sparkle";
import { SiteHeader } from "@/components/SiteHeader";
import { SiteFooter } from "@/components/SiteFooter";
import { VitrineImage } from "@/components/VitrineImage";
import { PromoImage } from "@/components/PromoImage";
import { getMemberLivePromotions, getPublicMember } from "@/lib/queries";
export const dynamic = "force-dynamic";
@@ -112,8 +113,9 @@ export default async function FicheAdherentPage({
width: 118,
height: 118,
borderRadius: 22,
background: "#fff",
border: "4px solid #fff",
background: "linear-gradient(160deg, #fffdf8 0%, #f6f2e8 100%)",
border: "1px solid #e6dcc6",
outline: "4px solid #fff",
marginTop: -54,
flexShrink: 0,
display: "flex",
@@ -265,7 +267,8 @@ export default async function FicheAdherentPage({
) : (
promos.map((p, idx) => (
<article key={p.id} className="lift" style={{ border: "1px solid #ece3d0", borderRadius: 16, overflow: "hidden", marginBottom: idx === promos.length - 1 ? 0 : 14 }}>
<div style={{ position: "relative", height: 120, background: p.imageUrl ? `url(${p.imageUrl})` : STRIPE_WARM, backgroundSize: "cover", backgroundPosition: "center", display: "flex", alignItems: "center", justifyContent: "center" }}>
<div style={{ position: "relative", height: 120, background: STRIPE_WARM, overflow: "hidden", display: "flex", alignItems: "center", justifyContent: "center" }}>
{p.imageUrl && <PromoImage src={p.imageUrl} alt={p.title} />}
{!p.imageUrl && (<span style={{ fontSize: 10, letterSpacing: "0.12em", color: "#a99c82", textTransform: "uppercase" }}>photo de l&apos;offre</span>)}
{p.badge && (
<span className="font-display" style={{ position: "absolute", top: 0, right: 0, background: badgeColor(p.badge), color: "#fff", fontWeight: 800, fontSize: 16, padding: "6px 13px", borderRadius: "0 0 0 14px" }}>{p.badge}</span>
+7 -6
View File
@@ -3,6 +3,7 @@
import { useRef, useState } from "react";
import { useRouter } from "next/navigation";
import { publishPromo } from "../actions";
import { PromoImage } from "@/components/PromoImage";
const STRIPE_WARM =
"repeating-linear-gradient(45deg,#efe9da,#efe9da 12px,#e6ddc9 12px,#e6ddc9 24px)";
@@ -81,11 +82,10 @@ export function MemberSpaceForm({
<label style={{ display: "block", cursor: "pointer", marginBottom: 18 }}>
<div
style={{
position: "relative",
border: "2px dashed #d8cdb4",
borderRadius: 14,
background: hasImg ? `url(${imgData})` : "#faf7ef",
backgroundSize: "cover",
backgroundPosition: "center",
background: "#faf7ef",
height: 170,
display: "flex",
flexDirection: "column",
@@ -96,6 +96,7 @@ export function MemberSpaceForm({
overflow: "hidden",
}}
>
{hasImg && <PromoImage src={imgData} alt="" />}
{!hasImg && (
<>
<span style={{ width: 38, height: 38, borderRadius: 10, background: "#f0e8d6", display: "flex", alignItems: "center", justifyContent: "center" }}>
@@ -188,14 +189,14 @@ export function MemberSpaceForm({
style={{
position: "relative",
height: 175,
background: hasImg ? `url(${imgData})` : STRIPE_WARM,
backgroundSize: "cover",
backgroundPosition: "center",
background: STRIPE_WARM,
overflow: "hidden",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
{hasImg && <PromoImage src={imgData} alt={title} />}
{!hasImg && (
<span style={{ fontSize: 10.5, letterSpacing: "0.12em", color: "#a99c82", textTransform: "uppercase" }}>
photo de l&apos;offre
+4 -7
View File
@@ -5,6 +5,7 @@ import { db } from "@/db";
import { members, promotions } from "@/db/schema";
import { can } from "@/lib/rbac";
import { moderatePromo } from "../actions";
import { PromoImage } from "@/components/PromoImage";
export const dynamic = "force-dynamic";
@@ -75,18 +76,14 @@ export default async function PromotionsPage() {
style={{
position: "relative",
height: 150,
background: p.imageUrl
? `url(${p.imageUrl})`
: isPending
? STRIPE_COOL
: STRIPE_WARM,
backgroundSize: "cover",
backgroundPosition: "center",
background: isPending ? STRIPE_COOL : STRIPE_WARM,
overflow: "hidden",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
{p.imageUrl && <PromoImage src={p.imageUrl} alt={p.title ?? ""} />}
{p.category && (
<span style={{ position: "absolute", top: 10, left: 10, background: "#9a6638", color: "#fff", borderRadius: 999, padding: "4px 11px", fontSize: 11, fontWeight: 700 }}>
{p.category}
+4 -3
View File
@@ -6,6 +6,7 @@ import { SiteFooter } from "@/components/SiteFooter";
import { MembershipModalButton } from "@/components/MembershipModalButton";
import { MembersCarousel } from "@/components/MembersCarousel";
import { StarField } from "@/components/StarField";
import { PromoImage } from "@/components/PromoImage";
import { getLivePromotions, getRotatingActiveMembers } from "@/lib/queries";
export const dynamic = "force-dynamic";
@@ -233,14 +234,14 @@ export default async function AccueilPage() {
style={{
position: "relative",
height: 168,
background: p.imageUrl ? `url(${p.imageUrl})` : STRIPE_WARM,
backgroundSize: "cover",
backgroundPosition: "center",
background: STRIPE_WARM,
overflow: "hidden",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
{p.imageUrl && <PromoImage src={p.imageUrl} alt={p.title ?? ""} />}
{!p.imageUrl && (
<span style={{ fontSize: 10.5, letterSpacing: "0.12em", color: "#a99c82", textTransform: "uppercase" }}>
photo de l&apos;offre