@salifbiaye/create-boutique 0.4.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/dist/commands.js +98 -7
- package/cli/dist/create.js +56 -10
- package/cli/dist/generate.js +43 -35
- package/cli/dist/guide.js +2 -2
- package/cli/dist/index.js +20 -2
- package/cli/dist/project.js +42 -3
- package/package.json +1 -1
- package/templates/backend/src/admin/i18n/json/en.json +12 -2
- package/templates/backend/src/admin/i18n/json/fr.json +12 -2
- package/templates/backend/src/admin/routes/boutique-campaigns/campaign-drawer.tsx +27 -17
- package/templates/backend/src/admin/routes/boutique-campaigns/page.tsx +49 -6
- package/templates/backend/src/api/admin/active-boutique/route.ts +25 -0
- package/templates/backend/src/api/admin/boutique-campaigns/[salesChannelId]/route.ts +7 -1
- package/templates/backend/src/api/admin/boutique-campaigns/route.ts +6 -0
- package/templates/backend/src/api/middlewares.ts +2 -0
- package/templates/backend/src/lib/active.ts +41 -0
- package/templates/backend/src/lib/project.ts +26 -1
- package/templates/backend/src/scripts/rewrite-file-urls.ts +56 -0
- package/templates/backend/src/scripts/seed.ts +8 -0
- package/templates/storefront/locales/en.json +9 -1
- package/templates/storefront/locales/fr.json +9 -1
- package/templates/storefront/src/app/(main)/page.tsx +32 -0
- package/templates/storefront/src/app/(main)/products/[handle]/page.tsx +4 -1
- package/templates/storefront/src/app/(main)/template.tsx +4 -0
- package/templates/storefront/src/app/layout.tsx +5 -1
- package/templates/storefront/src/lib/boutique/config.ts +71 -23
- package/templates/storefront/src/lib/boutique/schema.ts +5 -2
- package/templates/storefront/src/lib/config.ts +37 -8
- package/templates/storefront/src/lib/data/cart.ts +4 -1
- package/templates/storefront/src/lib/data/cookies.ts +14 -2
- package/templates/storefront/src/modules/checkout/components/shipping-address/index.tsx +6 -1
- package/templates/storefront/src/modules/ub/album.tsx +7 -3
- package/templates/storefront/src/modules/ub/cinema.tsx +2 -0
- package/templates/storefront/src/modules/ub/fx-runtime.ts +272 -0
- package/templates/storefront/src/modules/ub/gala.tsx +2 -0
- package/templates/storefront/src/modules/ub/grimoire.tsx +194 -0
- package/templates/storefront/src/modules/ub/motion.tsx +20 -0
- package/templates/storefront/src/modules/ub/surprise-button.tsx +20 -0
- package/templates/storefront/src/proxy.ts +2 -1
- package/templates/storefront/src/styles/ub/grimoire.css +178 -0
- package/templates/storefront/src/styles/ub/motion.css +105 -0
- package/templates/storefront/src/styles/ub/tech.css +7 -7
- package/templates/storefront/themes/halloween-2/campaign.en.json +2 -2
- package/templates/storefront/themes/halloween-2/campaign.fr.json +2 -2
- package/templates/storefront/themes/halloween-2/theme.json +3 -3
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import Link from "next/link"
|
|
2
|
+
import { preload } from "react-dom"
|
|
3
|
+
import type { Campaign } from "@lib/boutique/campaign"
|
|
4
|
+
import { t } from "@lib/boutique/i18n"
|
|
5
|
+
import { getProductPrice } from "@lib/util/get-product-price"
|
|
6
|
+
import { HttpTypes } from "@medusajs/types"
|
|
7
|
+
import { CampaignCountdown } from "./campaign-blocks"
|
|
8
|
+
import FadeImg from "./fade-img"
|
|
9
|
+
import { Icon } from "./icons"
|
|
10
|
+
import SurpriseButton from "./surprise-button"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Accueil « Grimoire » (Halloween 2, theme.json → "home": "grimoire") : jaune acide sur nuit.
|
|
14
|
+
* Hero plein cadre (le titre « sort de terre »), tirage de tarot (3 cartes qui se retournent),
|
|
15
|
+
* étagères d'apothicaire (cadres en arche, étiquettes N°), cercle de sort avec compte à rebours.
|
|
16
|
+
* CSS : styles/ub/grimoire.css.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
type P = HttpTypes.StoreProduct
|
|
20
|
+
const imageOf = (p: P) => p.thumbnail ?? p.images?.[0]?.url ?? undefined
|
|
21
|
+
const priceOf = (p: P) => {
|
|
22
|
+
const { cheapestPrice } = getProductPrice({ product: p })
|
|
23
|
+
return { price: cheapestPrice, onSale: cheapestPrice?.price_type === "sale" }
|
|
24
|
+
}
|
|
25
|
+
const ARCANA = ["XIII", "XVIII", "XV"]
|
|
26
|
+
|
|
27
|
+
export function GrimoireHero({ campaign }: { campaign: Campaign }) {
|
|
28
|
+
const { hero } = campaign
|
|
29
|
+
const image = hero.image && /^https?:\/\//.test(hero.image) ? hero.image : undefined
|
|
30
|
+
if (image) preload(image, { as: "image", fetchPriority: "high" })
|
|
31
|
+
const hl = hero.highlight && hero.title.includes(hero.highlight) ? hero.highlight : undefined
|
|
32
|
+
const before = hl ? hero.title.slice(0, hero.title.indexOf(hl)).trim() : hero.title
|
|
33
|
+
const after = hl ? hero.title.slice(hero.title.indexOf(hl) + hl.length).trim() : ""
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<section className="ub-gr-hero" style={image ? { ["--gr-img" as string]: `url("${image}")` } : undefined} data-has-image={image ? "" : undefined}>
|
|
37
|
+
<div className="ub-gr-hero__bg" aria-hidden />
|
|
38
|
+
<div className="ub-wrap ub-gr-hero__in">
|
|
39
|
+
{hero.eyebrow && <p className="ub-gr-eyebrow">{hero.eyebrow}</p>}
|
|
40
|
+
<h1 className="ub-gr-title">
|
|
41
|
+
{before && <span className="ub-gr-title__line">{before}</span>}
|
|
42
|
+
{hl && <span className="ub-gr-title__line ub-gr-title__hl">{hl}</span>}
|
|
43
|
+
{after && <span className="ub-gr-title__line">{after}</span>}
|
|
44
|
+
</h1>
|
|
45
|
+
<div className="ub-gr-hero__foot">
|
|
46
|
+
{hero.sub && <p className="ub-gr-sub">{hero.sub}</p>}
|
|
47
|
+
<div className="ub-gr-actions">
|
|
48
|
+
<Link href="/store" className="ub-gr-btn ub-gr-btn--solid">
|
|
49
|
+
{hero.cta} <Icon name="arrowRight" />
|
|
50
|
+
</Link>
|
|
51
|
+
{hero.cta2 && (
|
|
52
|
+
<Link href="/store" className="ub-gr-btn ub-gr-btn--ghost">
|
|
53
|
+
{hero.cta2}
|
|
54
|
+
</Link>
|
|
55
|
+
)}
|
|
56
|
+
<SurpriseButton event="halloween" label={t("fx.surprise.halloween")} />
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</section>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Tirage : 3 cartes de tarot face cachée ; survol / focus / toucher = la carte se retourne sur une offre. */
|
|
65
|
+
export function GrimoireTarot({ products }: { products: P[] }) {
|
|
66
|
+
const cards = products.filter(imageOf).slice(0, 3)
|
|
67
|
+
if (cards.length < 3) return null
|
|
68
|
+
return (
|
|
69
|
+
<section className="ub-gr-section ub-gr-tarot">
|
|
70
|
+
<div className="ub-wrap">
|
|
71
|
+
<div className="ub-gr-head ub-gr-head--center">
|
|
72
|
+
<p className="ub-gr-eyebrow">{t("grimoire.drawEyebrow")}</p>
|
|
73
|
+
<h2 className="ub-gr-h2">{t("grimoire.draw")}</h2>
|
|
74
|
+
<p className="ub-gr-lead">{t("grimoire.drawSub")}</p>
|
|
75
|
+
</div>
|
|
76
|
+
<ul className="ub-gr-deck">
|
|
77
|
+
{cards.map((p, i) => {
|
|
78
|
+
const { price, onSale } = priceOf(p)
|
|
79
|
+
return (
|
|
80
|
+
<li key={p.id}>
|
|
81
|
+
<Link href={`/products/${p.handle}`} className="ub-gr-card" aria-label={p.title}>
|
|
82
|
+
<span className="ub-gr-card__inner">
|
|
83
|
+
<span className="ub-gr-card__back" aria-hidden>
|
|
84
|
+
<span className="ub-gr-card__num">{ARCANA[i]}</span>
|
|
85
|
+
<svg viewBox="0 0 100 100" className="ub-gr-sigil">
|
|
86
|
+
<circle cx="50" cy="50" r="40" />
|
|
87
|
+
<circle cx="50" cy="50" r="28" />
|
|
88
|
+
<path d="M50 10 L61 38 L90 38 L66 56 L75 86 L50 68 L25 86 L34 56 L10 38 L39 38 Z" />
|
|
89
|
+
</svg>
|
|
90
|
+
<span className="ub-gr-card__hint">{t("grimoire.reveal")}</span>
|
|
91
|
+
</span>
|
|
92
|
+
<span className="ub-gr-card__front">
|
|
93
|
+
<span className="ub-gr-card__img">
|
|
94
|
+
<FadeImg src={imageOf(p)} alt="" />
|
|
95
|
+
{onSale && price?.percentage_diff && <b className="ub-gr-off">-{price.percentage_diff}%</b>}
|
|
96
|
+
</span>
|
|
97
|
+
<span className="ub-gr-card__name">{p.title}</span>
|
|
98
|
+
{price && (
|
|
99
|
+
<span className="ub-gr-price">
|
|
100
|
+
<b>{price.calculated_price}</b>
|
|
101
|
+
{onSale && <s>{price.original_price}</s>}
|
|
102
|
+
</span>
|
|
103
|
+
)}
|
|
104
|
+
</span>
|
|
105
|
+
</span>
|
|
106
|
+
</Link>
|
|
107
|
+
</li>
|
|
108
|
+
)
|
|
109
|
+
})}
|
|
110
|
+
</ul>
|
|
111
|
+
</div>
|
|
112
|
+
</section>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Étagères d'apothicaire : cadres en arche posés sur des planches, étiquette « N° ». */
|
|
117
|
+
export function GrimoireShelf({ products, title, sub }: { products: P[]; title: string; sub?: string }) {
|
|
118
|
+
if (!products.length) return null
|
|
119
|
+
return (
|
|
120
|
+
<section className="ub-gr-section">
|
|
121
|
+
<div className="ub-wrap">
|
|
122
|
+
<div className="ub-gr-head">
|
|
123
|
+
<div>
|
|
124
|
+
<p className="ub-gr-eyebrow">{t("grimoire.shelf")}</p>
|
|
125
|
+
<h2 className="ub-gr-h2">{title}</h2>
|
|
126
|
+
</div>
|
|
127
|
+
{sub && <p className="ub-gr-lead">{sub}</p>}
|
|
128
|
+
</div>
|
|
129
|
+
<ul className="ub-gr-shelf">
|
|
130
|
+
{products.map((p, i) => {
|
|
131
|
+
const { price, onSale } = priceOf(p)
|
|
132
|
+
const image = imageOf(p)
|
|
133
|
+
return (
|
|
134
|
+
<li key={p.id}>
|
|
135
|
+
<Link href={`/products/${p.handle}`} className="ub-gr-jar">
|
|
136
|
+
<span className="ub-gr-jar__arch">
|
|
137
|
+
{image ? <FadeImg src={image} alt="" /> : <span className="ub-ph" />}
|
|
138
|
+
{onSale && price?.percentage_diff && <b className="ub-gr-off">-{price.percentage_diff}%</b>}
|
|
139
|
+
</span>
|
|
140
|
+
<span className="ub-gr-jar__label">
|
|
141
|
+
<small>N°{String(i + 1).padStart(2, "0")}</small>
|
|
142
|
+
<span className="ub-gr-jar__name">{p.title}</span>
|
|
143
|
+
{price && (
|
|
144
|
+
<span className="ub-gr-price">
|
|
145
|
+
<b>{price.calculated_price}</b>
|
|
146
|
+
{onSale && <s>{price.original_price}</s>}
|
|
147
|
+
</span>
|
|
148
|
+
)}
|
|
149
|
+
</span>
|
|
150
|
+
</Link>
|
|
151
|
+
</li>
|
|
152
|
+
)
|
|
153
|
+
})}
|
|
154
|
+
</ul>
|
|
155
|
+
</div>
|
|
156
|
+
</section>
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Cercle de sort : runes qui tournent lentement, texte promo et compte à rebours au centre. */
|
|
161
|
+
export function GrimoireSpell({ campaign }: { campaign: Campaign }) {
|
|
162
|
+
const promo = campaign.promo
|
|
163
|
+
return (
|
|
164
|
+
<section className="ub-gr-section">
|
|
165
|
+
<div className="ub-wrap">
|
|
166
|
+
<div className="ub-gr-spell">
|
|
167
|
+
<svg className="ub-gr-spell__ring" viewBox="0 0 400 400" aria-hidden>
|
|
168
|
+
<defs>
|
|
169
|
+
<path id="ub-gr-ring" d="M200 200 m-170 0 a170 170 0 1 1 340 0 a170 170 0 1 1 -340 0" />
|
|
170
|
+
</defs>
|
|
171
|
+
<circle cx="200" cy="200" r="190" />
|
|
172
|
+
<circle cx="200" cy="200" r="150" />
|
|
173
|
+
<text>
|
|
174
|
+
<textPath href="#ub-gr-ring">ᚠ ᚢ ᚦ ᚨ ᚱ ᚲ ᚷ ᚹ ᚺ ᚾ ᛁ ᛃ ᛇ ᛈ ᛉ ᛊ ᛏ ᛒ ᛖ ᛗ ᛚ ᛜ ᛞ ᛟ ᚠ ᚢ ᚦ ᚨ ᚱ ᚲ ᚷ ᚹ ᚺ ᚾ ᛁ ᛃ</textPath>
|
|
175
|
+
</text>
|
|
176
|
+
</svg>
|
|
177
|
+
<div className="ub-gr-spell__text">
|
|
178
|
+
{promo?.eyebrow && <p className="ub-gr-eyebrow">{promo.eyebrow}</p>}
|
|
179
|
+
<h2 className="ub-gr-h2">{promo?.title ?? campaign.hero.title}</h2>
|
|
180
|
+
{promo?.text && <p className="ub-gr-lead">{promo.text}</p>}
|
|
181
|
+
{campaign.showCountdown !== false && (
|
|
182
|
+
<div className="ub-gr-spell__cd">
|
|
183
|
+
<CampaignCountdown endsAt={campaign.endsAt} />
|
|
184
|
+
</div>
|
|
185
|
+
)}
|
|
186
|
+
<Link href="/store" className="ub-gr-btn ub-gr-btn--solid">
|
|
187
|
+
{promo?.cta ?? campaign.hero.cta} <Icon name="arrowRight" />
|
|
188
|
+
</Link>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
</section>
|
|
193
|
+
)
|
|
194
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useEffect } from "react"
|
|
4
|
+
|
|
5
|
+
/** Écran large + souris + animations autorisées : seul cas où les animations desktop sont chargées. */
|
|
6
|
+
export const DESKTOP_FX = "(min-width: 1024px) and (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference)"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Active les animations desktop (styles/ub/motion.css + fx-runtime) après le chargement de la page.
|
|
10
|
+
* Rien n'est téléchargé sur mobile : le code est importé à la demande, seulement si DESKTOP_FX est vrai.
|
|
11
|
+
*/
|
|
12
|
+
export default function Motion({ event }: { event: string }) {
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (!window.matchMedia(DESKTOP_FX).matches) return
|
|
15
|
+
const go = () => import("./fx-runtime").then((m) => m.start(event))
|
|
16
|
+
if ("requestIdleCallback" in window) window.requestIdleCallback(go, { timeout: 1500 })
|
|
17
|
+
else setTimeout(go, 300)
|
|
18
|
+
}, [event])
|
|
19
|
+
return null
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Bouton « surprise » du hero (desktop uniquement, masqué par le CSS sur mobile) :
|
|
5
|
+
* lance quelques secondes de confettis / particules aux couleurs de l'événement, sans bloquer la page.
|
|
6
|
+
*/
|
|
7
|
+
export default function SurpriseButton({ event, label }: { event: string; label: string }) {
|
|
8
|
+
return (
|
|
9
|
+
<button
|
|
10
|
+
type="button"
|
|
11
|
+
className="ub-surprise"
|
|
12
|
+
onClick={(e) => {
|
|
13
|
+
const r = e.currentTarget.getBoundingClientRect()
|
|
14
|
+
import("./fx-runtime").then((m) => m.burst(event, { x: r.left + r.width / 2, y: r.top + r.height / 2 }))
|
|
15
|
+
}}
|
|
16
|
+
>
|
|
17
|
+
<span aria-hidden>✦</span> {label}
|
|
18
|
+
</button>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
@@ -45,7 +45,8 @@ export function proxy(request: NextRequest) {
|
|
|
45
45
|
request.headers.set("content-security-policy", csp)
|
|
46
46
|
request.headers.set("x-nonce", nonce)
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
// Même nom que lib/data/cookies.ts (mode single : « site », commun à toutes les boutiques)
|
|
49
|
+
const cacheCookie = `_medusa_cache_id_${process.env.BOUTIQUE === "auto" ? "site" : process.env.BOUTIQUE}`
|
|
49
50
|
const newCacheId = request.cookies.has(cacheCookie) ? null : crypto.randomUUID()
|
|
50
51
|
if (newCacheId) {
|
|
51
52
|
// Visible dès cette requête par les server components…
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/* Accueil « Grimoire » (Halloween 2) — modules/ub/grimoire.tsx. Jaune acide sur nuit, sorcellerie d'apothicaire.
|
|
2
|
+
Jetons de couleur : styles/ub/cinema.css ([data-variant="halloween-2"]). */
|
|
3
|
+
|
|
4
|
+
.ub-al-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; }
|
|
5
|
+
|
|
6
|
+
[data-variant="halloween-2"] { --gr-acid: #f2e14c; --gr-night: #0b0a0c; --gr-wood: #2a1d14; }
|
|
7
|
+
|
|
8
|
+
/* ---------- Hero : image plein cadre, titre qui « sort de terre » ---------- */
|
|
9
|
+
.ub-gr-hero {
|
|
10
|
+
position: relative;
|
|
11
|
+
isolation: isolate;
|
|
12
|
+
overflow: hidden;
|
|
13
|
+
display: flex;
|
|
14
|
+
align-items: flex-end;
|
|
15
|
+
min-height: clamp(600px, 52vw, 780px);
|
|
16
|
+
background: var(--gr-night);
|
|
17
|
+
color: #fff;
|
|
18
|
+
}
|
|
19
|
+
.ub-gr-hero__bg { position: absolute; inset: 0; z-index: -2; background: var(--gr-img, radial-gradient(60% 60% at 40% 30%, #3a2a14, var(--gr-night))) center 35% / cover no-repeat; }
|
|
20
|
+
.ub-gr-hero::after {
|
|
21
|
+
content: "";
|
|
22
|
+
position: absolute;
|
|
23
|
+
inset: 0;
|
|
24
|
+
z-index: -1;
|
|
25
|
+
background:
|
|
26
|
+
linear-gradient(0deg, var(--gr-night) 2%, rgb(11 10 12 / 0.75) 26%, transparent 60%),
|
|
27
|
+
linear-gradient(90deg, rgb(11 10 12 / 0.55), transparent 55%);
|
|
28
|
+
}
|
|
29
|
+
.ub-gr-hero__in { width: 100%; padding-top: var(--space-16); padding-bottom: var(--space-12); }
|
|
30
|
+
.ub-gr-eyebrow { margin: 0; color: var(--gr-acid); font: 700 13px/18px var(--ub-font-body); letter-spacing: 0.3em; text-transform: uppercase; }
|
|
31
|
+
.ub-gr-title { display: grid; margin: 14px 0 0 !important; font-family: var(--font-oswald); font-weight: 700; text-transform: uppercase; line-height: 0.92; padding-bottom: 0.06em; letter-spacing: -0.01em; }
|
|
32
|
+
.ub-gr-title__line { font-size: clamp(52px, 6.4vw, 104px); }
|
|
33
|
+
/* Mot mis en avant : lettres creuses jaunes */
|
|
34
|
+
.ub-gr-title__hl { color: transparent; -webkit-text-stroke: 2px var(--gr-acid); }
|
|
35
|
+
.ub-gr-hero__foot { display: grid; grid-template-columns: minmax(0, 520px) auto; align-items: end; justify-content: space-between; gap: var(--space-8); margin-top: var(--space-8); }
|
|
36
|
+
.ub-gr-sub { margin: 0; color: #d8d2c6; font-size: 17px; line-height: 27px; }
|
|
37
|
+
.ub-gr-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 12px; }
|
|
38
|
+
.ub-gr-btn { display: inline-flex; align-items: center; gap: 10px; min-height: 52px; padding: 0 26px; border-radius: 4px; font: 700 14px/18px var(--ub-font-body); letter-spacing: 0.08em; text-transform: uppercase; transition: transform 0.2s, background 0.2s; }
|
|
39
|
+
.ub-gr-btn svg { width: 18px; height: 18px; }
|
|
40
|
+
.ub-gr-btn--solid { background: var(--gr-acid); color: var(--gr-night); }
|
|
41
|
+
.ub-gr-btn--solid:hover { transform: translateY(-2px); }
|
|
42
|
+
.ub-gr-btn--ghost { border: 1px solid rgb(255 255 255 / 0.4); color: #fff; }
|
|
43
|
+
.ub-gr-btn--ghost:hover { background: rgb(255 255 255 / 0.08); }
|
|
44
|
+
.ub-gr-hero .ub-surprise { color: var(--gr-acid); }
|
|
45
|
+
|
|
46
|
+
/* Desktop : le titre monte depuis le sol, ligne par ligne */
|
|
47
|
+
@media (min-width: 1024px) and (prefers-reduced-motion: no-preference) {
|
|
48
|
+
.ub-gr-title { overflow: hidden; }
|
|
49
|
+
.ub-gr-title__line { animation: ub-gr-rise 0.9s cubic-bezier(0.2, 0.8, 0.2, 1) both; }
|
|
50
|
+
.ub-gr-title__line:nth-child(2) { animation-delay: 0.12s; }
|
|
51
|
+
.ub-gr-title__line:nth-child(3) { animation-delay: 0.24s; }
|
|
52
|
+
@keyframes ub-gr-rise { from { transform: translate3d(0, 105%, 0); } }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* ---------- Sections ---------- */
|
|
56
|
+
.ub-gr-section { padding-top: var(--space-16); }
|
|
57
|
+
.ub-grimoire > :last-child { padding-bottom: var(--space-16); }
|
|
58
|
+
.ub-gr-head { display: flex; flex-wrap: wrap; align-items: end; justify-content: space-between; gap: var(--space-6); margin-bottom: var(--space-10); }
|
|
59
|
+
.ub-gr-head--center { display: grid; justify-items: center; text-align: center; }
|
|
60
|
+
.ub-gr-h2 { margin: 8px 0 0; font: 700 clamp(34px, 4.2vw, 60px) / 0.95 var(--font-oswald); text-transform: uppercase; }
|
|
61
|
+
.ub-gr-lead { max-width: 36em; margin: 0; color: var(--ink-muted); font-size: 16px; line-height: 25px; }
|
|
62
|
+
.ub-gr-off { position: absolute; top: 10px; left: 10px; padding: 4px 9px; border-radius: 3px; background: var(--gr-acid); color: var(--gr-night); font: 800 13px/16px var(--ub-font-body); }
|
|
63
|
+
.ub-gr-price { display: flex; align-items: baseline; gap: 8px; }
|
|
64
|
+
.ub-gr-price b { color: var(--gr-acid); font: 700 18px/22px var(--font-oswald); }
|
|
65
|
+
.ub-gr-price s { color: var(--ink-muted); font-size: 13px; }
|
|
66
|
+
|
|
67
|
+
/* ---------- Tirage de tarot : cartes qui se retournent ---------- */
|
|
68
|
+
.ub-gr-deck { display: grid; grid-template-columns: repeat(3, minmax(0, 300px)); justify-content: center; gap: clamp(16px, 3vw, 40px); margin: 0; padding: 0; list-style: none; }
|
|
69
|
+
.ub-gr-deck > li:nth-child(1) { transform: rotate(-4deg) translateY(14px); }
|
|
70
|
+
.ub-gr-deck > li:nth-child(3) { transform: rotate(4deg) translateY(14px); }
|
|
71
|
+
.ub-gr-card { display: block; aspect-ratio: 2 / 3.3; perspective: 1200px; color: var(--ink); }
|
|
72
|
+
.ub-gr-card__inner { position: relative; display: block; width: 100%; height: 100%; transform-style: preserve-3d; transition: transform 0.7s cubic-bezier(0.2, 0.7, 0.2, 1); }
|
|
73
|
+
.ub-gr-card:is(:hover, :focus-visible, :active) .ub-gr-card__inner { transform: rotateY(180deg); }
|
|
74
|
+
.ub-gr-card__back,
|
|
75
|
+
.ub-gr-card__front { position: absolute; inset: 0; display: grid; border-radius: 14px; backface-visibility: hidden; -webkit-backface-visibility: hidden; overflow: hidden; }
|
|
76
|
+
.ub-gr-card__back {
|
|
77
|
+
place-items: center;
|
|
78
|
+
grid-template-rows: auto 1fr auto;
|
|
79
|
+
padding: 22px;
|
|
80
|
+
border: 1px solid rgb(242 225 76 / 0.5);
|
|
81
|
+
background:
|
|
82
|
+
repeating-linear-gradient(45deg, rgb(242 225 76 / 0.05) 0 2px, transparent 2px 14px),
|
|
83
|
+
radial-gradient(circle at 50% 45%, #2a2410, var(--gr-night) 75%);
|
|
84
|
+
box-shadow: inset 0 0 0 8px var(--gr-night), inset 0 0 0 9px rgb(242 225 76 / 0.35), 0 24px 50px -24px rgb(0 0 0 / 0.9);
|
|
85
|
+
}
|
|
86
|
+
.ub-gr-card__num { color: var(--gr-acid); font: 700 22px/1 var(--font-oswald); letter-spacing: 0.2em; }
|
|
87
|
+
.ub-gr-sigil { width: 70%; fill: none; stroke: var(--gr-acid); stroke-width: 1.2; opacity: 0.85; }
|
|
88
|
+
.ub-gr-card__hint { color: #cfc6a8; font-size: 12px; letter-spacing: 0.3em; text-transform: uppercase; }
|
|
89
|
+
.ub-gr-card__front { grid-template-rows: 1fr auto auto; gap: 8px; padding: 12px 12px 18px; border: 1px solid var(--gr-acid); background: var(--surface); transform: rotateY(180deg); }
|
|
90
|
+
.ub-gr-card__img { position: relative; overflow: hidden; border-radius: 10px; }
|
|
91
|
+
.ub-gr-card__img img { width: 100%; height: 100%; object-fit: cover; }
|
|
92
|
+
.ub-gr-card__name { padding: 0 6px; font: 700 18px/22px var(--font-oswald); text-transform: uppercase; }
|
|
93
|
+
.ub-gr-card__front .ub-gr-price { padding: 0 6px; }
|
|
94
|
+
|
|
95
|
+
/* ---------- Étagères d'apothicaire ---------- */
|
|
96
|
+
.ub-gr-shelf {
|
|
97
|
+
display: grid;
|
|
98
|
+
grid-template-columns: repeat(4, 1fr);
|
|
99
|
+
column-gap: 24px;
|
|
100
|
+
row-gap: 56px;
|
|
101
|
+
margin: 0;
|
|
102
|
+
padding: 0;
|
|
103
|
+
list-style: none;
|
|
104
|
+
}
|
|
105
|
+
.ub-gr-shelf > li { position: relative; }
|
|
106
|
+
/* Planche sous chaque rangée */
|
|
107
|
+
.ub-gr-shelf > li::after {
|
|
108
|
+
content: "";
|
|
109
|
+
position: absolute;
|
|
110
|
+
left: -12px;
|
|
111
|
+
right: -12px;
|
|
112
|
+
bottom: -22px;
|
|
113
|
+
height: 10px;
|
|
114
|
+
border-radius: 2px;
|
|
115
|
+
background: linear-gradient(180deg, #4a3422, var(--gr-wood));
|
|
116
|
+
box-shadow: 0 12px 20px -8px rgb(0 0 0 / 0.8);
|
|
117
|
+
}
|
|
118
|
+
.ub-gr-jar { display: grid; gap: 12px; color: var(--ink); }
|
|
119
|
+
.ub-gr-jar__arch {
|
|
120
|
+
position: relative;
|
|
121
|
+
display: block;
|
|
122
|
+
aspect-ratio: 3 / 4;
|
|
123
|
+
overflow: hidden;
|
|
124
|
+
border: 1px solid rgb(242 225 76 / 0.35);
|
|
125
|
+
border-radius: 999px 999px 10px 10px;
|
|
126
|
+
background: var(--surface);
|
|
127
|
+
}
|
|
128
|
+
.ub-gr-jar__arch img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.6s cubic-bezier(0.2, 0.7, 0.2, 1); }
|
|
129
|
+
.ub-gr-jar:hover .ub-gr-jar__arch img { transform: scale(1.05); }
|
|
130
|
+
.ub-gr-jar__arch .ub-gr-off { top: auto; bottom: 12px; left: 50%; transform: translateX(-50%); }
|
|
131
|
+
.ub-gr-jar__label { display: grid; justify-items: center; gap: 4px; text-align: center; }
|
|
132
|
+
.ub-gr-jar__label small { color: var(--gr-acid); font: 700 11px/14px var(--ub-font-body); letter-spacing: 0.25em; }
|
|
133
|
+
.ub-gr-jar__name { font: 600 16px/21px var(--ub-font-body); }
|
|
134
|
+
|
|
135
|
+
/* ---------- Cercle de sort ---------- */
|
|
136
|
+
.ub-gr-spell {
|
|
137
|
+
position: relative;
|
|
138
|
+
isolation: isolate;
|
|
139
|
+
display: grid;
|
|
140
|
+
place-items: center;
|
|
141
|
+
min-height: 560px;
|
|
142
|
+
overflow: hidden;
|
|
143
|
+
padding: var(--space-12) var(--space-6);
|
|
144
|
+
border: 1px solid var(--border);
|
|
145
|
+
border-radius: 20px;
|
|
146
|
+
background: radial-gradient(50% 60% at 50% 50%, #221d0c, var(--gr-night) 80%);
|
|
147
|
+
text-align: center;
|
|
148
|
+
}
|
|
149
|
+
.ub-gr-spell__ring { position: absolute; z-index: -1; width: min(640px, 110%); aspect-ratio: 1; fill: none; stroke: rgb(242 225 76 / 0.2); stroke-width: 1; }
|
|
150
|
+
.ub-gr-spell__ring text { fill: rgb(242 225 76 / 0.45); stroke: none; font-size: 18px; letter-spacing: 6px; }
|
|
151
|
+
@media (min-width: 1024px) and (prefers-reduced-motion: no-preference) {
|
|
152
|
+
.ub-gr-spell__ring { animation: ub-gr-turn 90s linear infinite; }
|
|
153
|
+
@keyframes ub-gr-turn { to { transform: rotate(360deg); } }
|
|
154
|
+
}
|
|
155
|
+
.ub-gr-spell__text { display: grid; justify-items: center; gap: 14px; max-width: 560px; }
|
|
156
|
+
.ub-gr-spell__cd .ub-countdown__c { border: 1px solid rgb(242 225 76 / 0.4); background: rgb(11 10 12 / 0.7); color: var(--gr-acid); box-shadow: none; }
|
|
157
|
+
|
|
158
|
+
/* ---------- Responsive ---------- */
|
|
159
|
+
@media (max-width: 1024px) {
|
|
160
|
+
.ub-gr-shelf { grid-template-columns: repeat(3, 1fr); }
|
|
161
|
+
}
|
|
162
|
+
@media (max-width: 900px) {
|
|
163
|
+
.ub-gr-hero { min-height: 560px; }
|
|
164
|
+
.ub-gr-hero__foot { grid-template-columns: 1fr; }
|
|
165
|
+
.ub-gr-actions { justify-content: flex-start; }
|
|
166
|
+
.ub-gr-deck { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
|
|
167
|
+
.ub-gr-deck > li:nth-child(1), .ub-gr-deck > li:nth-child(3) { transform: none; }
|
|
168
|
+
.ub-gr-card__name { font-size: 14px; line-height: 18px; }
|
|
169
|
+
.ub-gr-card__hint { display: none; }
|
|
170
|
+
}
|
|
171
|
+
@media (max-width: 600px) {
|
|
172
|
+
.ub-gr-title__line { font-size: 48px; }
|
|
173
|
+
.ub-gr-shelf { grid-template-columns: repeat(2, 1fr); column-gap: 14px; row-gap: 44px; }
|
|
174
|
+
.ub-gr-spell { min-height: 460px; }
|
|
175
|
+
/* Tirage sur mobile : les cartes défilent, touchées = retournées */
|
|
176
|
+
.ub-gr-deck { display: flex; overflow-x: auto; scroll-snap-type: x mandatory; padding-bottom: 8px; justify-content: flex-start; }
|
|
177
|
+
.ub-gr-deck > li { flex: 0 0 70%; scroll-snap-align: center; }
|
|
178
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/* Animations desktop uniquement (écran ≥ 1024 px, souris, animations autorisées). Le mobile n'est pas concerné :
|
|
2
|
+
toutes les règles sont dans ce media query, ou dépendent de html.ub-motion (ajouté par fx-runtime, jamais sur mobile).
|
|
3
|
+
Uniquement transform / opacity : pas de recalcul de mise en page. */
|
|
4
|
+
|
|
5
|
+
/* Bouton « surprise » du hero : desktop seulement */
|
|
6
|
+
.ub-surprise { display: none; }
|
|
7
|
+
|
|
8
|
+
@media (min-width: 1024px) and (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
|
|
9
|
+
/* ---------- Transition entre les pages (le contenu de chaque page est remonté par app/(main)/template.tsx) ---------- */
|
|
10
|
+
.ub-page { animation: ub-page-in 0.38s cubic-bezier(0.2, 0.7, 0.2, 1) backwards; }
|
|
11
|
+
@keyframes ub-page-in { from { opacity: 0; transform: translate3d(0, 10px, 0); } }
|
|
12
|
+
/* Voile court aux couleurs de l'événement, au-dessus du contenu qui arrive */
|
|
13
|
+
.ub-page::before {
|
|
14
|
+
content: "";
|
|
15
|
+
position: fixed;
|
|
16
|
+
inset: 0;
|
|
17
|
+
z-index: 70;
|
|
18
|
+
pointer-events: none;
|
|
19
|
+
opacity: 0;
|
|
20
|
+
animation: ub-veil 0.45s ease-out;
|
|
21
|
+
}
|
|
22
|
+
@keyframes ub-veil { from { opacity: 1; } }
|
|
23
|
+
html:not([data-event]) .ub-page::before,
|
|
24
|
+
html[data-event="black-friday"] .ub-page::before,
|
|
25
|
+
html[data-event="thanksgiving"] .ub-page::before,
|
|
26
|
+
html[data-event="cyber-monday"] .ub-page::before { display: none; }
|
|
27
|
+
html[data-event="halloween"] .ub-page::before { background: radial-gradient(circle at 50% 40%, transparent 0%, rgb(0 0 0 / 0.85) 70%); }
|
|
28
|
+
html[data-event="noel"] .ub-page::before { background: rgb(255 250 240 / 0.55); animation-duration: 0.5s; }
|
|
29
|
+
html[data-event="nouvel-an"] .ub-page::before { background: linear-gradient(100deg, transparent 30%, rgb(232 199 102 / 0.35) 50%, transparent 70%); background-size: 250% 100%; animation: ub-veil-shine 0.55s ease-out; }
|
|
30
|
+
@keyframes ub-veil-shine { from { opacity: 1; background-position: 100% 0; } to { opacity: 0; background-position: 0 0; } }
|
|
31
|
+
|
|
32
|
+
/* ---------- Apparition au scroll (classes posées par fx-runtime, retirées à la fin) ---------- */
|
|
33
|
+
html.ub-motion .ub-rv {
|
|
34
|
+
opacity: 0;
|
|
35
|
+
transform: translate3d(0, 26px, 0);
|
|
36
|
+
transition: opacity 0.6s ease, transform 0.6s cubic-bezier(0.2, 0.7, 0.2, 1);
|
|
37
|
+
transition-delay: calc(var(--rv-i, 0) * 60ms);
|
|
38
|
+
}
|
|
39
|
+
html.ub-motion .ub-rv.is-in { opacity: 1; transform: none; }
|
|
40
|
+
|
|
41
|
+
/* ---------- Hero : entrée du titre, du texte puis des boutons ---------- */
|
|
42
|
+
:is(.ub-hero, .ub-hv-hero, .ub-gl-hero, .ub-cn-hero, .ub-al-hero, .ub-tc-hero, .ub-gr-hero) :is(h1, .ub-hero__sub, .ub-hv-sub, .ub-gl-sub, .ub-al-sub, .ub-tc-sub, .ub-gr-sub, .ub-cn-panel__text) {
|
|
43
|
+
animation: ub-hero-in 0.8s cubic-bezier(0.2, 0.7, 0.2, 1) both;
|
|
44
|
+
}
|
|
45
|
+
:is(.ub-hero, .ub-hv-hero, .ub-gl-hero, .ub-cn-hero, .ub-al-hero, .ub-tc-hero, .ub-gr-hero) :is(p, .ub-countdown) { animation: ub-hero-in 0.8s 0.15s cubic-bezier(0.2, 0.7, 0.2, 1) both; }
|
|
46
|
+
:is(.ub-hero, .ub-hv-hero, .ub-gl-hero, .ub-cn-hero, .ub-al-hero, .ub-tc-hero, .ub-gr-hero) :is(a, button) { animation: ub-hero-in 0.8s 0.3s cubic-bezier(0.2, 0.7, 0.2, 1) both; }
|
|
47
|
+
@keyframes ub-hero-in { from { opacity: 0; transform: translate3d(0, 18px, 0); } }
|
|
48
|
+
/* Fond du hero : très lente respiration */
|
|
49
|
+
/* Seulement les fonds qui couvrent tout le hero (un visuel posé sur une partie, fondu par un dégradé, laisserait voir son bord) */
|
|
50
|
+
:is(.ub-hv-hero__bg, .ub-gr-hero__bg) { animation: ub-drift 22s ease-in-out infinite alternate; }
|
|
51
|
+
@keyframes ub-drift { to { transform: scale(1.05) translate3d(-1%, -1%, 0); } }
|
|
52
|
+
|
|
53
|
+
/* ---------- Cartes produits : survol ---------- */
|
|
54
|
+
.ub-card { transition: transform 0.3s cubic-bezier(0.2, 0.7, 0.2, 1), box-shadow 0.3s, border-color 0.3s; }
|
|
55
|
+
.ub-card:hover { transform: translateY(-4px); }
|
|
56
|
+
.ub-card__media { overflow: hidden; }
|
|
57
|
+
.ub-card__media img { transition: transform 0.6s cubic-bezier(0.2, 0.7, 0.2, 1); }
|
|
58
|
+
.ub-card:hover .ub-card__media img { transform: scale(1.05); }
|
|
59
|
+
|
|
60
|
+
/* Rangées horizontales : défilement doux (auto-slide) */
|
|
61
|
+
.ub-page :is(ul, ol) { scroll-behavior: smooth; }
|
|
62
|
+
|
|
63
|
+
/* ---------- Bouton surprise ---------- */
|
|
64
|
+
.ub-surprise {
|
|
65
|
+
display: inline-flex;
|
|
66
|
+
align-items: center;
|
|
67
|
+
gap: 8px;
|
|
68
|
+
min-height: 48px;
|
|
69
|
+
padding: 0 20px;
|
|
70
|
+
border: 1px dashed currentColor;
|
|
71
|
+
border-radius: 999px;
|
|
72
|
+
background: transparent;
|
|
73
|
+
color: inherit;
|
|
74
|
+
font: 600 14px/18px var(--ub-font-body);
|
|
75
|
+
letter-spacing: 0.04em;
|
|
76
|
+
cursor: pointer;
|
|
77
|
+
transition: transform 0.2s, background 0.2s;
|
|
78
|
+
}
|
|
79
|
+
.ub-surprise:hover { transform: translateY(-2px) rotate(-1deg); background: rgb(255 255 255 / 0.08); }
|
|
80
|
+
.ub-surprise span { display: inline-block; animation: ub-twinkle 1.6s ease-in-out infinite; }
|
|
81
|
+
@keyframes ub-twinkle { 50% { transform: scale(1.35) rotate(20deg); opacity: 0.7; } }
|
|
82
|
+
|
|
83
|
+
/* ---------- Particules ---------- */
|
|
84
|
+
.ub-fx-canvas { position: absolute; inset: 0; z-index: 1; width: 100%; height: 100%; pointer-events: none; }
|
|
85
|
+
.ub-fx-burst { position: fixed; inset: 0; z-index: 80; width: 100vw; height: 100vh; pointer-events: none; }
|
|
86
|
+
|
|
87
|
+
/* Chauve-souris (Halloween) : traverse l'écran en ondulant, ailes qui battent */
|
|
88
|
+
.ub-bat {
|
|
89
|
+
position: fixed;
|
|
90
|
+
top: var(--y);
|
|
91
|
+
left: 0;
|
|
92
|
+
z-index: 75;
|
|
93
|
+
width: var(--s);
|
|
94
|
+
height: calc(var(--s) / 2);
|
|
95
|
+
color: #0b0a0c;
|
|
96
|
+
filter: drop-shadow(0 0 6px rgb(255 122 26 / 0.35));
|
|
97
|
+
pointer-events: none;
|
|
98
|
+
animation: ub-bat-fly var(--d) linear var(--delay) both;
|
|
99
|
+
}
|
|
100
|
+
.ub-bat > span { display: block; width: 100%; height: 100%; animation: ub-bat-flap 0.22s ease-in-out infinite alternate; }
|
|
101
|
+
.ub-bat--rtl { animation-name: ub-bat-fly-rtl; }
|
|
102
|
+
@keyframes ub-bat-fly { from { transform: translate3d(-10vw, 0, 0); } 50% { transform: translate3d(50vw, var(--dy), 0); } to { transform: translate3d(110vw, calc(var(--dy) * -0.5), 0); } }
|
|
103
|
+
@keyframes ub-bat-fly-rtl { from { transform: translate3d(110vw, 0, 0); } 50% { transform: translate3d(50vw, var(--dy), 0); } to { transform: translate3d(-10vw, calc(var(--dy) * -0.5), 0); } }
|
|
104
|
+
@keyframes ub-bat-flap { to { transform: scaleY(0.45); } }
|
|
105
|
+
}
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
--ink: #0b0b0f; --ink-muted: #5f616c; --border: #e4e4ea;
|
|
25
25
|
--primary: #0b0b0f; --on-primary: #ffffff; --accent: #5b3df5; --on-accent: #ffffff;
|
|
26
26
|
--sale: #e11d48; --star: #f59e0b; --success: #15803d;
|
|
27
|
-
--hero-bg: #
|
|
27
|
+
--hero-bg: #ffffff; --hero-ink: #0b0b0f; --hero-muted: #4d4f59;
|
|
28
28
|
--band-bg: #0b0b0f; --band-ink: #ffffff; --bubble-bg: #0b0b0f; --bubble-ink: #ffffff;
|
|
29
29
|
--topbar-bg: #0b0b0f; --topbar-ink: #ffffff; --focus: #5b3df5;
|
|
30
30
|
--shadow-card: 0 14px 34px -22px rgb(11 11 15 / 0.35);
|
|
@@ -37,14 +37,14 @@
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/* ---------- En-tête et pied de page ---------- */
|
|
40
|
-
[data-theme="cyber-monday"]:not([data-variant]) .ub-nav { background:
|
|
40
|
+
[data-theme="cyber-monday"]:not([data-variant]) .ub-nav { background: #0b0716; border-bottom: 1px solid var(--border); }
|
|
41
41
|
[data-theme="cyber-monday"]:not([data-variant]) .ub-search { background: var(--surface); border-color: var(--border); color: var(--ink); }
|
|
42
42
|
[data-theme="cyber-monday"]:not([data-variant]) .ub-nav__cta { background: var(--primary); color: #fff; box-shadow: var(--tc-glow); }
|
|
43
|
-
[data-variant="cyber-monday-2"] .ub-nav { background:
|
|
43
|
+
[data-variant="cyber-monday-2"] .ub-nav { background: #ffffff; border-bottom: 1px solid var(--border); }
|
|
44
44
|
[data-variant="cyber-monday-2"] .ub-nav__cta { background: var(--ink); color: #fff; }
|
|
45
45
|
[data-theme="cyber-monday"]:not([data-variant]) .ub-footwrap { --foot-bg: #120b24; --foot-ink: var(--ink); --cta-bg: var(--primary); --cta-ink: #ffffff; }
|
|
46
46
|
[data-theme="cyber-monday"]:not([data-variant]) .ub-footwrap .ub-footer { border: 1px solid var(--border); }
|
|
47
|
-
[data-variant="cyber-monday-2"] .ub-footwrap { --foot-bg: #0b0b0f; --foot-ink: #f4f4f6; --cta-bg: #
|
|
47
|
+
[data-variant="cyber-monday-2"] .ub-footwrap { --foot-bg: #0b0b0f; --foot-ink: #f4f4f6; --cta-bg: #f1f1f3; --cta-ink: #0b0b0f; }
|
|
48
48
|
|
|
49
49
|
/* Cartes produits du reste du site (boutique, recherche, produits liés) */
|
|
50
50
|
[data-theme="cyber-monday"]:not([data-variant]) .ub-card { background: var(--tc-card); border: 1px solid var(--border); }
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
/* Cyber Monday 1 : le visuel fait partie du fond (comme Black Friday) : posé à droite sur toute la hauteur,
|
|
94
94
|
fondu dans le noir à gauche, en haut et en bas. Pas de cadre, pas d'ombre. */
|
|
95
95
|
.ub-tc-hero[data-has-image] .ub-tc-hero__beam { display: none; }
|
|
96
|
-
.ub-tc-hero__cover { position: absolute; inset: 0 0 0 34%; z-index: -1; }
|
|
96
|
+
.ub-tc-hero__cover { position: absolute; inset: 0 0 0 34%; z-index: -1; overflow: hidden; }
|
|
97
97
|
.ub-tc-hero__cover img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 62%; animation: ub-tc-fadein 1.2s ease both; }
|
|
98
98
|
.ub-tc-hero__cover::after {
|
|
99
99
|
content: "";
|
|
@@ -351,12 +351,12 @@
|
|
|
351
351
|
/* Le visuel couvre tout le hero ; un voile blanc à gauche garde le texte lisible */
|
|
352
352
|
/* Fond du hero = gris béton clair du visuel : l'image posée à droite se fond dans toute la largeur */
|
|
353
353
|
[data-variant="cyber-monday-2"] .ub-tc-hero__cover { display: block; position: absolute; inset: 0 0 0 30%; z-index: -1; }
|
|
354
|
-
[data-variant="cyber-monday-2"] .ub-tc-hero__cover img { width: 100%; height: 100%; object-fit: cover; object-position: 50%
|
|
354
|
+
[data-variant="cyber-monday-2"] .ub-tc-hero__cover img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 60%; animation: ub-tc-zoom 1.4s cubic-bezier(0.2, 0.7, 0.2, 1) both; }
|
|
355
355
|
[data-variant="cyber-monday-2"] .ub-tc-hero__cover::after {
|
|
356
356
|
content: "";
|
|
357
357
|
position: absolute;
|
|
358
358
|
inset: 0;
|
|
359
|
-
background: linear-gradient(90deg, var(--hero-bg) 0%, rgb(
|
|
359
|
+
background: linear-gradient(90deg, var(--hero-bg) 0%, rgb(255 255 255 / 0.7) 16%, transparent 38%);
|
|
360
360
|
}
|
|
361
361
|
@keyframes ub-tc-zoom { from { transform: scale(1.06); opacity: 0; } }
|
|
362
362
|
[data-variant="cyber-monday-2"] .ub-tc-hero__in { grid-template-columns: minmax(0, 620px); min-height: clamp(580px, 48vw, 760px); }
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
},
|
|
20
20
|
"promo": {
|
|
21
21
|
"eyebrow": "Spell of the day",
|
|
22
|
-
"title": "
|
|
23
|
-
"text": "
|
|
22
|
+
"title": "Up to {discount} off before the cauldron runs dry",
|
|
23
|
+
"text": "Marked-down prices storewide until {endDate}, while stocks last.",
|
|
24
24
|
"cta": "Cast the spell",
|
|
25
25
|
"seal": "Until {endDate}"
|
|
26
26
|
},
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
},
|
|
20
20
|
"promo": {
|
|
21
21
|
"eyebrow": "Sort du jour",
|
|
22
|
-
"title": "
|
|
23
|
-
"text": "
|
|
22
|
+
"title": "Jusqu'à -{discount} avant que le chaudron soit vide",
|
|
23
|
+
"text": "Prix barrés sur toute la boutique jusqu'au {endDate}, dans la limite des stocks.",
|
|
24
24
|
"cta": "Lancer le sort",
|
|
25
25
|
"seal": "Avant le {endDate}"
|
|
26
26
|
},
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "halloween-2",
|
|
3
|
-
"name": "Halloween ·
|
|
4
|
-
"reference": "variante,
|
|
3
|
+
"name": "Halloween · Grimoire",
|
|
4
|
+
"reference": "variante, accueil grimoire",
|
|
5
5
|
"layout": "black-friday",
|
|
6
6
|
"fonts": {
|
|
7
7
|
"head": "oswald",
|
|
8
8
|
"body": "inter"
|
|
9
9
|
},
|
|
10
|
-
"home": "
|
|
10
|
+
"home": "grimoire"
|
|
11
11
|
}
|