mirror of
https://github.com/R0m1k3/PleinR.git
synced 2026-10-11 17:27:54 +02:00
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:
1 parent
c199d7a7db
commit
03eca6e9ae
5 files changed
+60
-19
No files matched your search
@@ -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'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>
|
||||
|
||||
@@ -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'offre
|
||||
|
||||
@@ -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
@@ -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'offre
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Promo image that adapts to any aspect ratio.
|
||||
* Sharp `contain` image on top of a blurred `cover` copy of itself,
|
||||
* so the whole image is always visible with no flat letterbox bars.
|
||||
* Render inside a `position: relative` box; place after it any
|
||||
* badges/labels that must sit on top.
|
||||
*/
|
||||
export function PromoImage({ src, alt = "" }: { src: string; alt?: string }) {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
aria-hidden
|
||||
style={{
|
||||
position: "absolute",
|
||||
inset: 0,
|
||||
backgroundImage: `url(${src})`,
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition: "center",
|
||||
filter: "blur(18px) saturate(1.15)",
|
||||
transform: "scale(1.18)",
|
||||
}}
|
||||
/>
|
||||
<div aria-hidden style={{ position: "absolute", inset: 0, background: "rgba(40,30,15,0.16)" }} />
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={src}
|
||||
alt={alt}
|
||||
style={{
|
||||
position: "absolute",
|
||||
inset: 0,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
objectFit: "contain",
|
||||
display: "block",
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user