add global btn and page layout stylings
This commit is contained in:
parent
8960ffd844
commit
365fcf0cae
@ -22,14 +22,23 @@ function CartToast() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="cart-toast">
|
<div className="cart-toast" role="status" aria-live="polite">
|
||||||
<button className="cart-toast-close" type="button" onClick={dismissToast}>
|
<button
|
||||||
x
|
type="button"
|
||||||
|
className="atmos-btn atmos-btn--outline atmos-btn--icon atmos-btn--sm cart-toast-close"
|
||||||
|
onClick={dismissToast}
|
||||||
|
aria-label="Hinweis schliessen"
|
||||||
|
>
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
</button>
|
</button>
|
||||||
<strong>{cartToast.title}</strong>
|
<strong>{cartToast.title}</strong>
|
||||||
<p>{cartToast.message}</p>
|
<p>{cartToast.message}</p>
|
||||||
{cartToast.actionLabel && (
|
{cartToast.actionLabel && (
|
||||||
<button type="button" onClick={runAction}>
|
<button
|
||||||
|
type="button"
|
||||||
|
className="atmos-btn atmos-btn--primary atmos-btn--block atmos-btn--sm"
|
||||||
|
onClick={runAction}
|
||||||
|
>
|
||||||
{cartToast.actionLabel}
|
{cartToast.actionLabel}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -362,67 +362,9 @@
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.purchase-discovery-note a {
|
/* Buy / restock / discovery / review CTAs use the global .atmos-btn system
|
||||||
display: inline-flex;
|
(see src/style/buttons.css). The .restock-button class is kept on the
|
||||||
align-items: center;
|
element only as a hook for the mobile breakpoint that hides it. */
|
||||||
justify-content: center;
|
|
||||||
min-height: 44px;
|
|
||||||
padding: 0 1rem;
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
background: var(--theme-accent-fill);
|
|
||||||
color: var(--theme-accent-contrast);
|
|
||||||
text-decoration: none;
|
|
||||||
white-space: nowrap;
|
|
||||||
font-size: var(--text-sm);
|
|
||||||
transition:
|
|
||||||
transform var(--duration-med) var(--ease-out),
|
|
||||||
box-shadow var(--duration-med) var(--ease-out);
|
|
||||||
}
|
|
||||||
|
|
||||||
.purchase-discovery-note a:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 16px 36px rgba(0, 0, 0, 0.24);
|
|
||||||
}
|
|
||||||
|
|
||||||
.buy-button,
|
|
||||||
.restock-button,
|
|
||||||
.detail-bottom-actions button,
|
|
||||||
.review-toggle,
|
|
||||||
.review-write-button {
|
|
||||||
min-height: 48px;
|
|
||||||
border: 1px solid var(--theme-border);
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
cursor: pointer;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.12em;
|
|
||||||
transition:
|
|
||||||
transform var(--duration-med) var(--ease-out),
|
|
||||||
border-color var(--duration-med) var(--ease-out),
|
|
||||||
background-color var(--duration-med) var(--ease-out),
|
|
||||||
box-shadow var(--duration-med) var(--ease-out);
|
|
||||||
}
|
|
||||||
|
|
||||||
.buy-button {
|
|
||||||
border-color: var(--theme-accent-fill);
|
|
||||||
background: var(--theme-accent-fill);
|
|
||||||
color: var(--theme-accent-contrast);
|
|
||||||
}
|
|
||||||
|
|
||||||
.restock-button {
|
|
||||||
background: transparent;
|
|
||||||
color: var(--theme-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.buy-button:hover,
|
|
||||||
.restock-button:hover,
|
|
||||||
.detail-bottom-actions button:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: var(--theme-shadow-soft);
|
|
||||||
}
|
|
||||||
|
|
||||||
.restock-button:hover {
|
|
||||||
border-color: rgba(var(--theme-accent-rgb) / 0.68);
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-story-grid,
|
.product-story-grid,
|
||||||
.product-meta-section {
|
.product-meta-section {
|
||||||
@ -834,21 +776,7 @@
|
|||||||
margin-top: var(--gap-sm);
|
margin-top: var(--gap-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.review-toggle {
|
/* .review-toggle is rendered as `.atmos-btn--outline --block --split`. */
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--gap-sm);
|
|
||||||
padding: 0.9rem 1rem;
|
|
||||||
background: transparent;
|
|
||||||
color: var(--theme-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.review-toggle:hover {
|
|
||||||
border-color: rgba(var(--theme-accent-rgb) / 0.6);
|
|
||||||
}
|
|
||||||
|
|
||||||
.review-toggle-icon {
|
.review-toggle-icon {
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
@ -896,12 +824,6 @@
|
|||||||
background: var(--theme-accent);
|
background: var(--theme-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.review-write-button {
|
|
||||||
background: var(--theme-paper);
|
|
||||||
color: var(--theme-text-muted);
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-bottom-cta {
|
.detail-bottom-cta {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(0, 1fr) auto;
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
@ -926,29 +848,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.detail-bottom-actions {
|
.detail-bottom-actions {
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: var(--gap-xs);
|
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-bottom-actions button {
|
|
||||||
padding: 0 clamp(1rem, 2vw, 1.35rem);
|
|
||||||
border: 0;
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
background: #fff;
|
|
||||||
color: var(--theme-accent-fill-strong);
|
|
||||||
box-shadow: 0 18px 42px rgba(0, 0, 0, 0.18);
|
|
||||||
font-size: var(--text-sm);
|
|
||||||
letter-spacing: 0;
|
|
||||||
text-transform: none;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-bottom-actions button:active {
|
|
||||||
transform: translateY(0) scale(0.98);
|
|
||||||
}
|
|
||||||
|
|
||||||
.recommendation-heading {
|
.recommendation-heading {
|
||||||
display: block;
|
display: block;
|
||||||
max-width: 74rem;
|
max-width: 74rem;
|
||||||
@ -1250,7 +1152,7 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.buy-button,
|
.purchase-actions .atmos-btn,
|
||||||
.restock-button {
|
.restock-button {
|
||||||
min-height: 38px;
|
min-height: 38px;
|
||||||
font-size: 0.72rem;
|
font-size: 0.72rem;
|
||||||
@ -1273,11 +1175,6 @@
|
|||||||
width: min(100%, 24rem);
|
width: min(100%, 24rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
.buy-button,
|
|
||||||
.restock-button {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.review-score-block {
|
.review-score-block {
|
||||||
justify-items: start;
|
justify-items: start;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -119,7 +119,7 @@ function ProductPurchasePanel({
|
|||||||
|
|
||||||
<div className="purchase-actions">
|
<div className="purchase-actions">
|
||||||
<button
|
<button
|
||||||
className="buy-button"
|
className="atmos-btn atmos-btn--primary atmos-btn--block atmos-btn--uppercase"
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
addToCart(
|
addToCart(
|
||||||
@ -133,7 +133,7 @@ function ProductPurchasePanel({
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="restock-button"
|
className="atmos-btn atmos-btn--outline atmos-btn--block atmos-btn--uppercase atmos-btn--sm restock-button"
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => subscribeToProduct(selectedProductId, "restock").catch(() => {})}
|
onClick={() => subscribeToProduct(selectedProductId, "restock").catch(() => {})}
|
||||||
>
|
>
|
||||||
@ -155,7 +155,12 @@ function ProductPurchasePanel({
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Link to="/discovery-set">Zum Set</Link>
|
<Link
|
||||||
|
to="/discovery-set"
|
||||||
|
className="atmos-btn atmos-btn--outline atmos-btn--sm"
|
||||||
|
>
|
||||||
|
Zum Set
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
@ -429,7 +434,7 @@ function ProductReviews({
|
|||||||
<div className={`review-panel ${showReviewDetails ? "is-open" : ""}`} data-reveal="fade">
|
<div className={`review-panel ${showReviewDetails ? "is-open" : ""}`} data-reveal="fade">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="review-toggle"
|
className="atmos-btn atmos-btn--outline atmos-btn--block atmos-btn--split atmos-btn--uppercase review-toggle"
|
||||||
onClick={() => setShowReviewDetails((prev) => !prev)}
|
onClick={() => setShowReviewDetails((prev) => !prev)}
|
||||||
aria-expanded={showReviewDetails}
|
aria-expanded={showReviewDetails}
|
||||||
>
|
>
|
||||||
@ -454,7 +459,11 @@ function ProductReviews({
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<button type="button" className="review-write-button" disabled>
|
<button
|
||||||
|
type="button"
|
||||||
|
className="atmos-btn atmos-btn--outline atmos-btn--block atmos-btn--uppercase"
|
||||||
|
disabled
|
||||||
|
>
|
||||||
Bewertung schreiben
|
Bewertung schreiben
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -466,7 +475,7 @@ function ProductReviews({
|
|||||||
|
|
||||||
function ProductTestingCTA({ perfume, addToCart }) {
|
function ProductTestingCTA({ perfume, addToCart }) {
|
||||||
return (
|
return (
|
||||||
<section className="detail-bottom-cta" data-reveal-group>
|
<section className="detail-bottom-cta" data-reveal-group data-on-accent>
|
||||||
<div>
|
<div>
|
||||||
<span className="eyebrow" data-reveal="fade">Lieber erst testen?</span>
|
<span className="eyebrow" data-reveal="fade">Lieber erst testen?</span>
|
||||||
<h2 data-reveal="lines">Sample oder Discovery Set.</h2>
|
<h2 data-reveal="lines">Sample oder Discovery Set.</h2>
|
||||||
@ -477,22 +486,24 @@ function ProductTestingCTA({ perfume, addToCart }) {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="detail-bottom-actions" data-reveal="fade">
|
<div className="detail-bottom-actions atmos-btn-row atmos-btn-row--responsive" data-reveal="fade">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
className="atmos-btn atmos-btn--primary"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
addToCart(`${perfume.slug}-sample`, 1, `${perfume.name} Sample added.`).catch(
|
addToCart(`${perfume.slug}-sample`, 1, `${perfume.name} Sample added.`).catch(
|
||||||
() => {}
|
() => {}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Sample bestellen - {perfume.prices.sample}
|
Sample bestellen — {perfume.prices.sample}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
className="atmos-btn atmos-btn--secondary"
|
||||||
onClick={() => addToCart("discovery-set", 1, "Discovery Set added.").catch(() => {})}
|
onClick={() => addToCart("discovery-set", 1, "Discovery Set added.").catch(() => {})}
|
||||||
>
|
>
|
||||||
Discovery Set - CHF 48
|
Discovery Set — CHF 48
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -5,8 +5,8 @@ import { useShop } from "../shop/useShop";
|
|||||||
import "./ShopDrawer.css";
|
import "./ShopDrawer.css";
|
||||||
|
|
||||||
const paymentMethods = [
|
const paymentMethods = [
|
||||||
{ key: "Bill", badge: "BILL", label: "Bill" },
|
{ key: "Bill", badge: "BILL", label: "Rechnung" },
|
||||||
{ key: "Card", badge: "CARD", label: "Card" },
|
{ key: "Card", badge: "CARD", label: "Karte" },
|
||||||
{ key: "Twint", badge: "TW", label: "Twint" },
|
{ key: "Twint", badge: "TW", label: "Twint" },
|
||||||
{ key: "PayPal", badge: "PP", label: "PayPal" },
|
{ key: "PayPal", badge: "PP", label: "PayPal" },
|
||||||
];
|
];
|
||||||
@ -38,7 +38,7 @@ function Field({
|
|||||||
const fieldId = id || `shop-field-${fieldName}-${generatedId}`;
|
const fieldId = id || `shop-field-${fieldName}-${generatedId}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<label className="shop-field" htmlFor={fieldId}>
|
<label className="drawer-field" htmlFor={fieldId}>
|
||||||
<span>{label}</span>
|
<span>{label}</span>
|
||||||
<input
|
<input
|
||||||
id={fieldId}
|
id={fieldId}
|
||||||
@ -76,62 +76,72 @@ function AuthPanel() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="drawer-section auth-panel" onSubmit={submit}>
|
<form className="drawer-stack" onSubmit={submit}>
|
||||||
<span className="drawer-kicker">{mode === "login" ? "LOGIN" : "REGISTER"}</span>
|
<section className="drawer-section">
|
||||||
<h2>{mode === "login" ? "Welcome back." : "Create your atmos account."}</h2>
|
<span className="drawer-eyebrow">{mode === "login" ? "Login" : "Registrieren"}</span>
|
||||||
|
<h2 className="drawer-heading">
|
||||||
|
{mode === "login" ? "Willkommen zurück." : "Konto erstellen."}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="drawer-form">
|
||||||
|
{mode === "register" && (
|
||||||
|
<div className="drawer-grid drawer-grid--two">
|
||||||
|
<Field
|
||||||
|
label="Name"
|
||||||
|
name="first_name"
|
||||||
|
value={form.first_name}
|
||||||
|
autoComplete="given-name"
|
||||||
|
onChange={(value) => update("first_name", value)}
|
||||||
|
/>
|
||||||
|
<Field
|
||||||
|
label="Nachname"
|
||||||
|
name="surname"
|
||||||
|
value={form.surname}
|
||||||
|
autoComplete="family-name"
|
||||||
|
onChange={(value) => update("surname", value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{mode === "register" && (
|
|
||||||
<div className="drawer-grid drawer-grid--two">
|
|
||||||
<Field
|
<Field
|
||||||
label="Name"
|
id="auth-email"
|
||||||
name="first_name"
|
label="E-Mail"
|
||||||
value={form.first_name}
|
name="email"
|
||||||
autoComplete="given-name"
|
type="email"
|
||||||
onChange={(value) => update("first_name", value)}
|
value={form.email}
|
||||||
|
autoComplete="email"
|
||||||
|
inputMode="email"
|
||||||
|
onChange={(value) => update("email", value)}
|
||||||
/>
|
/>
|
||||||
<Field
|
<Field
|
||||||
label="Surname"
|
id="auth-password"
|
||||||
name="surname"
|
label="Passwort"
|
||||||
value={form.surname}
|
name="password"
|
||||||
autoComplete="family-name"
|
type="password"
|
||||||
onChange={(value) => update("surname", value)}
|
value={form.password}
|
||||||
|
autoComplete={mode === "login" ? "current-password" : "new-password"}
|
||||||
|
onChange={(value) => update("password", value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{error && <p className="drawer-error">{error}</p>}
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="atmos-btn atmos-btn--primary atmos-btn--block"
|
||||||
|
type="submit"
|
||||||
|
disabled={busy}
|
||||||
|
>
|
||||||
|
{mode === "login" ? "Login" : "Registrieren"}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="atmos-btn atmos-btn--ghost atmos-btn--block atmos-btn--sm"
|
||||||
|
type="button"
|
||||||
|
onClick={() => setMode((current) => (current === "login" ? "register" : "login"))}
|
||||||
|
>
|
||||||
|
{mode === "login" ? "Konto erstellen" : "Bestehenden Account nutzen"}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</section>
|
||||||
|
|
||||||
<Field
|
|
||||||
id="auth-email"
|
|
||||||
label="Email address"
|
|
||||||
name="email"
|
|
||||||
type="email"
|
|
||||||
value={form.email}
|
|
||||||
autoComplete="email"
|
|
||||||
inputMode="email"
|
|
||||||
onChange={(value) => update("email", value)}
|
|
||||||
/>
|
|
||||||
<Field
|
|
||||||
id="auth-password"
|
|
||||||
label="Password"
|
|
||||||
name="password"
|
|
||||||
type="password"
|
|
||||||
value={form.password}
|
|
||||||
autoComplete={mode === "login" ? "current-password" : "new-password"}
|
|
||||||
onChange={(value) => update("password", value)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{error && <p className="drawer-error">{error}</p>}
|
|
||||||
|
|
||||||
<button className="drawer-primary" type="submit" disabled={busy}>
|
|
||||||
{mode === "login" ? "Login" : "Register"}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
className="drawer-secondary"
|
|
||||||
type="button"
|
|
||||||
onClick={() => setMode((current) => (current === "login" ? "register" : "login"))}
|
|
||||||
>
|
|
||||||
{mode === "login" ? "Create account" : "Use existing account"}
|
|
||||||
</button>
|
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -165,30 +175,32 @@ function CartPanel() {
|
|||||||
return (
|
return (
|
||||||
<form className="drawer-stack" onSubmit={submit}>
|
<form className="drawer-stack" onSubmit={submit}>
|
||||||
<section className="drawer-section">
|
<section className="drawer-section">
|
||||||
<span className="drawer-kicker">CART</span>
|
<span className="drawer-eyebrow">Warenkorb</span>
|
||||||
{cart.items.length === 0 ? (
|
{cart.items.length === 0 ? (
|
||||||
<p className="drawer-muted">Your cart is empty.</p>
|
<p className="drawer-muted">Dein Warenkorb ist leer.</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="cart-items">
|
<div className="cart-items">
|
||||||
{cart.items.map((item) => (
|
{cart.items.map((item) => (
|
||||||
<article className="cart-item" key={item.product_id}>
|
<article className="cart-item" key={item.product_id}>
|
||||||
<div>
|
<div className="cart-item-info">
|
||||||
<h3>{item.product.name}</h3>
|
<h3>{item.product.name}</h3>
|
||||||
<p>
|
<p>
|
||||||
{item.product.size_label} · {formatChf(item.product.price_cents)}
|
{item.product.size_label} · {formatChf(item.product.price_cents)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="cart-controls">
|
<div className="cart-controls" role="group" aria-label="Menge anpassen">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
aria-label="Menge verringern"
|
||||||
onClick={() => updateCartQuantity(item.product_id, item.quantity - 1)}
|
onClick={() => updateCartQuantity(item.product_id, item.quantity - 1)}
|
||||||
>
|
>
|
||||||
-
|
−
|
||||||
</button>
|
</button>
|
||||||
<span>{item.quantity}</span>
|
<span>{item.quantity}</span>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
aria-label="Menge erhöhen"
|
||||||
onClick={() => updateCartQuantity(item.product_id, item.quantity + 1)}
|
onClick={() => updateCartQuantity(item.product_id, item.quantity + 1)}
|
||||||
>
|
>
|
||||||
+
|
+
|
||||||
@ -196,11 +208,11 @@ function CartPanel() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="cart-remove"
|
className="atmos-btn atmos-btn--ghost atmos-btn--sm"
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => removeCartItem(item.product_id)}
|
onClick={() => removeCartItem(item.product_id)}
|
||||||
>
|
>
|
||||||
Remove
|
Entfernen
|
||||||
</button>
|
</button>
|
||||||
</article>
|
</article>
|
||||||
))}
|
))}
|
||||||
@ -209,56 +221,77 @@ function CartPanel() {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="drawer-section">
|
<section className="drawer-section">
|
||||||
<span className="drawer-kicker">SHIPPING</span>
|
<span className="drawer-eyebrow">Versand</span>
|
||||||
<div className="drawer-grid drawer-grid--two">
|
<div className="drawer-grid drawer-grid--two">
|
||||||
<Field label="Street Name" value={address.street_name} onChange={(value) => updateAddress("street_name", value)} />
|
<Field
|
||||||
<Field label="House Number" value={address.house_number} onChange={(value) => updateAddress("house_number", value)} />
|
label="Strasse"
|
||||||
<Field label="ZIP Code" value={address.zip_code} onChange={(value) => updateAddress("zip_code", value)} />
|
value={address.street_name}
|
||||||
<Field label="City" value={address.city} onChange={(value) => updateAddress("city", value)} />
|
onChange={(value) => updateAddress("street_name", value)}
|
||||||
|
/>
|
||||||
|
<Field
|
||||||
|
label="Nr."
|
||||||
|
value={address.house_number}
|
||||||
|
onChange={(value) => updateAddress("house_number", value)}
|
||||||
|
/>
|
||||||
|
<Field
|
||||||
|
label="PLZ"
|
||||||
|
value={address.zip_code}
|
||||||
|
onChange={(value) => updateAddress("zip_code", value)}
|
||||||
|
/>
|
||||||
|
<Field
|
||||||
|
label="Ort"
|
||||||
|
value={address.city}
|
||||||
|
onChange={(value) => updateAddress("city", value)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="drawer-section">
|
<section className="drawer-section">
|
||||||
<span className="drawer-kicker">PAYMENT</span>
|
<span className="drawer-eyebrow">Zahlung</span>
|
||||||
<div className="payment-grid">
|
<div className="payment-grid" role="radiogroup" aria-label="Zahlungsmethode">
|
||||||
{paymentMethods.map((method) => (
|
{paymentMethods.map((method) => {
|
||||||
<button
|
const active = paymentMethod === method.key;
|
||||||
type="button"
|
return (
|
||||||
className={`payment-card ${paymentMethod === method.key ? "active" : ""}`}
|
<button
|
||||||
key={method.key}
|
type="button"
|
||||||
onClick={() => setPaymentMethod(method.key)}
|
role="radio"
|
||||||
>
|
aria-checked={active}
|
||||||
<span>{method.badge}</span>
|
className={`payment-card ${active ? "is-active" : ""}`}
|
||||||
<strong>{method.label}</strong>
|
key={method.key}
|
||||||
</button>
|
onClick={() => setPaymentMethod(method.key)}
|
||||||
))}
|
>
|
||||||
|
<span className="payment-card__badge">{method.badge}</span>
|
||||||
|
<strong>{method.label}</strong>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="drawer-section totals-box">
|
<section className="drawer-section drawer-totals">
|
||||||
<div>
|
<div className="drawer-totals__row">
|
||||||
<span>Subtotal</span>
|
<span>Subtotal</span>
|
||||||
<strong>{formatChf(cart.subtotal_cents)}</strong>
|
<strong>{formatChf(cart.subtotal_cents)}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="drawer-totals__row">
|
||||||
<span>Rabatte</span>
|
<span>Rabatte</span>
|
||||||
<strong>-{formatChf(cart.discount_cents)}</strong>
|
<strong>−{formatChf(cart.discount_cents)}</strong>
|
||||||
</div>
|
</div>
|
||||||
{cart.discounts?.length > 0 && (
|
{cart.discounts?.length > 0 && (
|
||||||
<div className="discount-explainer">
|
<div className="drawer-totals__explainer">
|
||||||
<span>Applied automatically</span>
|
<span className="drawer-eyebrow">Automatisch angewendet</span>
|
||||||
{cart.discounts.map((discount) => (
|
{cart.discounts.map((discount) => (
|
||||||
<p key={`${discount.type}-${discount.creditId}`}>
|
<p key={`${discount.type}-${discount.creditId}`}>
|
||||||
<strong>{formatChf(discount.amount_cents)}</strong>
|
<strong>{formatChf(discount.amount_cents)}</strong>
|
||||||
{" - "}
|
{" — "}
|
||||||
{discount.type === "discovery"
|
{discount.type === "discovery"
|
||||||
? "Discovery Set credit for a full-size bottle"
|
? "Discovery Set Credit für eine Full Size"
|
||||||
: `Sample credit for ${discount.slug}`}
|
: `Sample Credit für ${discount.slug}`}
|
||||||
</p>
|
</p>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="total-row">
|
<div className="drawer-totals__row drawer-totals__row--total">
|
||||||
<span>Total</span>
|
<span>Total</span>
|
||||||
<strong>{formatChf(cart.total_cents)}</strong>
|
<strong>{formatChf(cart.total_cents)}</strong>
|
||||||
</div>
|
</div>
|
||||||
@ -266,8 +299,12 @@ function CartPanel() {
|
|||||||
|
|
||||||
{error && <p className="drawer-error">{error}</p>}
|
{error && <p className="drawer-error">{error}</p>}
|
||||||
|
|
||||||
<button className="drawer-primary" type="submit" disabled={busy || cart.items.length === 0}>
|
<button
|
||||||
Checkout
|
className="atmos-btn atmos-btn--primary atmos-btn--block atmos-btn--lg"
|
||||||
|
type="submit"
|
||||||
|
disabled={busy || cart.items.length === 0}
|
||||||
|
>
|
||||||
|
Jetzt bezahlen
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
@ -275,9 +312,9 @@ function CartPanel() {
|
|||||||
|
|
||||||
function RequirementRow({ label, met, children }) {
|
function RequirementRow({ label, met, children }) {
|
||||||
return (
|
return (
|
||||||
<div className="requirement-row">
|
<div className={`drawer-requirement ${met ? "is-met" : ""}`}>
|
||||||
<span>{label}</span>
|
<span>{label}</span>
|
||||||
<strong className={met ? "met" : ""}>{children || (met ? "met" : "open")}</strong>
|
<strong>{children || (met ? "Erfüllt" : "Offen")}</strong>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -330,69 +367,84 @@ function ProfilePanel() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="drawer-stack">
|
<div className="drawer-stack">
|
||||||
<section className="drawer-section profile-head">
|
<section className="drawer-section drawer-profile-head">
|
||||||
<span className="drawer-kicker">PROFILE</span>
|
<div>
|
||||||
<h2>Hi, {user.first_name}</h2>
|
<span className="drawer-eyebrow">Profil</span>
|
||||||
<button className="drawer-secondary" type="button" onClick={logout}>
|
<h2 className="drawer-heading">Hi, {user.first_name}.</h2>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className="atmos-btn atmos-btn--outline atmos-btn--sm"
|
||||||
|
type="button"
|
||||||
|
onClick={logout}
|
||||||
|
>
|
||||||
Logout
|
Logout
|
||||||
</button>
|
</button>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="drawer-section">
|
<section className="drawer-section">
|
||||||
<div className="profile-section-header">
|
<header className="drawer-section-head">
|
||||||
<span className="drawer-kicker">PROFILE INFORMATION</span>
|
<span className="drawer-eyebrow">Profil-Informationen</span>
|
||||||
{!editing && (
|
{!editing && (
|
||||||
<button className="drawer-secondary" type="button" onClick={() => setEditing(true)}>
|
<button
|
||||||
Profil bearbeiten
|
className="atmos-btn atmos-btn--ghost atmos-btn--sm"
|
||||||
|
type="button"
|
||||||
|
onClick={() => setEditing(true)}
|
||||||
|
>
|
||||||
|
Bearbeiten
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</header>
|
||||||
|
|
||||||
{editing ? (
|
{editing ? (
|
||||||
<>
|
<>
|
||||||
<div className="drawer-grid drawer-grid--two">
|
<div className="drawer-grid drawer-grid--two">
|
||||||
<Field label="Name" value={form.first_name || ""} onChange={(value) => update("first_name", value)} />
|
<Field label="Name" value={form.first_name || ""} onChange={(value) => update("first_name", value)} />
|
||||||
<Field label="Surname" value={form.surname || ""} onChange={(value) => update("surname", value)} />
|
<Field label="Nachname" value={form.surname || ""} onChange={(value) => update("surname", value)} />
|
||||||
<Field label="Street Name" value={form.street_name || ""} onChange={(value) => update("street_name", value)} />
|
<Field label="Strasse" value={form.street_name || ""} onChange={(value) => update("street_name", value)} />
|
||||||
<Field label="House Number" value={form.house_number || ""} onChange={(value) => update("house_number", value)} />
|
<Field label="Nr." value={form.house_number || ""} onChange={(value) => update("house_number", value)} />
|
||||||
<Field label="ZIP Code" value={form.zip_code || ""} onChange={(value) => update("zip_code", value)} />
|
<Field label="PLZ" value={form.zip_code || ""} onChange={(value) => update("zip_code", value)} />
|
||||||
<Field label="City" value={form.city || ""} onChange={(value) => update("city", value)} />
|
<Field label="Ort" value={form.city || ""} onChange={(value) => update("city", value)} />
|
||||||
<Field label="Birthdate" type="date" value={form.birthdate || ""} onChange={(value) => update("birthdate", value)} />
|
<Field label="Geburtsdatum" type="date" value={form.birthdate || ""} onChange={(value) => update("birthdate", value)} />
|
||||||
</div>
|
</div>
|
||||||
<div className="drawer-actions">
|
<div className="atmos-btn-row">
|
||||||
<button className="drawer-primary" type="button" disabled={busy} onClick={save}>
|
<button
|
||||||
Save profile
|
className="atmos-btn atmos-btn--primary"
|
||||||
|
type="button"
|
||||||
|
disabled={busy}
|
||||||
|
onClick={save}
|
||||||
|
>
|
||||||
|
Profil speichern
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="drawer-secondary"
|
className="atmos-btn atmos-btn--outline"
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setForm(user);
|
setForm(user);
|
||||||
setEditing(false);
|
setEditing(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Cancel
|
Abbrechen
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="profile-read-grid">
|
<div className="profile-read-grid">
|
||||||
<ReadBlock label="Name" value={user.first_name} />
|
<ReadBlock label="Name" value={user.first_name} />
|
||||||
<ReadBlock label="Surname" value={user.surname} />
|
<ReadBlock label="Nachname" value={user.surname} />
|
||||||
<ReadBlock label="Street Name" value={user.street_name || "-"} />
|
<ReadBlock label="Strasse" value={user.street_name || "—"} />
|
||||||
<ReadBlock label="House Number" value={user.house_number || "-"} />
|
<ReadBlock label="Nr." value={user.house_number || "—"} />
|
||||||
<ReadBlock label="ZIP Code" value={user.zip_code || "-"} />
|
<ReadBlock label="PLZ" value={user.zip_code || "—"} />
|
||||||
<ReadBlock label="City" value={user.city || "-"} />
|
<ReadBlock label="Ort" value={user.city || "—"} />
|
||||||
<ReadBlock label="Birthdate" value={user.birthdate || "-"} />
|
<ReadBlock label="Geburtsdatum" value={user.birthdate || "—"} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="drawer-section">
|
<section className="drawer-section">
|
||||||
<span className="drawer-kicker">DISCOUNT STATUS</span>
|
<span className="drawer-eyebrow">Discount Status</span>
|
||||||
<div className="status-box">{user.discoveryStatus}</div>
|
<div className="drawer-status-box">{user.discoveryStatus}</div>
|
||||||
{user.sampleCredits?.length > 0 && (
|
{user.sampleCredits?.length > 0 && (
|
||||||
<div className="sample-credit-list">
|
<div className="drawer-credit-list">
|
||||||
{user.sampleCredits.map((credit) => (
|
{user.sampleCredits.map((credit) => (
|
||||||
<span key={`${credit.slug}-${credit.created_at}`}>
|
<span key={`${credit.slug}-${credit.created_at}`}>
|
||||||
{credit.slug}: {credit.status}
|
{credit.slug}: {credit.status}
|
||||||
@ -403,37 +455,42 @@ function ProfilePanel() {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="drawer-section">
|
<section className="drawer-section">
|
||||||
<span className="drawer-kicker">DROP / RESTOCK PREFERENCES</span>
|
<span className="drawer-eyebrow">Drop / Restock Preferences</span>
|
||||||
<div className="toggle-grid">
|
<div className="drawer-toggle-grid">
|
||||||
{notificationLabels.map(([key, label]) => (
|
{notificationLabels.map(([key, label]) => {
|
||||||
<button
|
const active = !!notifications[key];
|
||||||
key={key}
|
return (
|
||||||
type="button"
|
<button
|
||||||
className={`pref-toggle ${notifications[key] ? "active" : ""}`}
|
key={key}
|
||||||
onClick={() => togglePreference(key)}
|
type="button"
|
||||||
>
|
aria-pressed={active}
|
||||||
<span>{label}</span>
|
className={`drawer-toggle ${active ? "is-active" : ""}`}
|
||||||
<strong>{notifications[key] ? "Active" : "Inactive"}</strong>
|
onClick={() => togglePreference(key)}
|
||||||
</button>
|
>
|
||||||
))}
|
<span>{label}</span>
|
||||||
|
<strong>{active ? "Active" : "Inactive"}</strong>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="subscription-list">
|
<div className="drawer-subscription-list">
|
||||||
<span className="subscription-list-title">Subscribed Restocks</span>
|
<span className="drawer-eyebrow">Abonnierte Restocks</span>
|
||||||
{restockSubscriptions.length === 0 ? (
|
{restockSubscriptions.length === 0 ? (
|
||||||
<p className="drawer-muted">No product-specific restock updates yet.</p>
|
<p className="drawer-muted">Noch keine produktbezogenen Restock-Updates.</p>
|
||||||
) : (
|
) : (
|
||||||
restockSubscriptions.map((subscription) => (
|
restockSubscriptions.map((subscription) => (
|
||||||
<article className="subscription-row" key={subscription.id}>
|
<article className="drawer-subscription-row" key={subscription.id}>
|
||||||
<div>
|
<div>
|
||||||
<strong>{subscription.name}</strong>
|
<strong>{subscription.name}</strong>
|
||||||
<span>{subscription.size_label}</span>
|
<span>{subscription.size_label}</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
className="atmos-btn atmos-btn--ghost atmos-btn--sm"
|
||||||
onClick={() => removeProductSubscription(subscription.id).catch(() => {})}
|
onClick={() => removeProductSubscription(subscription.id).catch(() => {})}
|
||||||
>
|
>
|
||||||
Delete
|
Entfernen
|
||||||
</button>
|
</button>
|
||||||
</article>
|
</article>
|
||||||
))
|
))
|
||||||
@ -442,20 +499,22 @@ function ProfilePanel() {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="drawer-section">
|
<section className="drawer-section">
|
||||||
<span className="drawer-kicker">SMALL BATCH ACCESS</span>
|
<span className="drawer-eyebrow">Small Batch Access</span>
|
||||||
<div className="status-box">{loyalty.unlocked ? "Unlocked" : "Locked"}</div>
|
<div className="drawer-status-box">
|
||||||
<div className="requirements">
|
{loyalty.unlocked ? "Freigeschaltet" : "Noch gesperrt"}
|
||||||
|
</div>
|
||||||
|
<div className="drawer-requirements">
|
||||||
<RequirementRow label="Discovery Set" met={loyalty.hasDiscoverySet} />
|
<RequirementRow label="Discovery Set" met={loyalty.hasDiscoverySet} />
|
||||||
<RequirementRow label="Full Size" met={loyalty.hasFullSize} />
|
<RequirementRow label="Full Size" met={loyalty.hasFullSize} />
|
||||||
<RequirementRow label="Purchases" met={loyalty.purchases >= 3}>
|
<RequirementRow label="Bestellungen" met={loyalty.purchases >= 3}>
|
||||||
{loyalty.purchases}/3 Purchases
|
{loyalty.purchases}/3
|
||||||
</RequirementRow>
|
</RequirementRow>
|
||||||
<RequirementRow label="Spend" met={loyalty.spent_cents > 50000}>
|
<RequirementRow label="Umsatz" met={loyalty.spent_cents > 50000}>
|
||||||
{formatChf(loyalty.spent_cents)} / CHF 500+
|
{formatChf(loyalty.spent_cents)} / CHF 500+
|
||||||
</RequirementRow>
|
</RequirementRow>
|
||||||
</div>
|
</div>
|
||||||
<Link
|
<Link
|
||||||
className="drawer-primary drawer-link-primary"
|
className="atmos-btn atmos-btn--secondary atmos-btn--block"
|
||||||
to="/small-batch"
|
to="/small-batch"
|
||||||
onClick={closePanel}
|
onClick={closePanel}
|
||||||
>
|
>
|
||||||
@ -464,19 +523,19 @@ function ProfilePanel() {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="drawer-section">
|
<section className="drawer-section">
|
||||||
<span className="drawer-kicker">PURCHASES</span>
|
<span className="drawer-eyebrow">Bestellungen</span>
|
||||||
{orders.length === 0 ? (
|
{orders.length === 0 ? (
|
||||||
<p className="drawer-muted">No orders yet.</p>
|
<p className="drawer-muted">Noch keine Bestellungen.</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="order-list">
|
<div className="drawer-order-list">
|
||||||
{orders.map((order) => (
|
{orders.map((order) => (
|
||||||
<article className="order-card" key={order.id}>
|
<article className="drawer-order-card" key={order.id}>
|
||||||
<div>
|
<header>
|
||||||
<strong>Order #{order.id}</strong>
|
<strong>Bestellung #{order.id}</strong>
|
||||||
<span>{new Date(order.created_at).toLocaleDateString("de-CH")}</span>
|
<span>{new Date(order.created_at).toLocaleDateString("de-CH")}</span>
|
||||||
</div>
|
</header>
|
||||||
<p>{order.items.map((item) => `${item.quantity} x ${item.product.name}`).join(", ")}</p>
|
<p>{order.items.map((item) => `${item.quantity} × ${item.product.name}`).join(", ")}</p>
|
||||||
<strong>{formatChf(order.total_cents)}</strong>
|
<strong className="drawer-order-card__total">{formatChf(order.total_cents)}</strong>
|
||||||
</article>
|
</article>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -490,7 +549,7 @@ function ProfilePanel() {
|
|||||||
|
|
||||||
function ReadBlock({ label, value }) {
|
function ReadBlock({ label, value }) {
|
||||||
return (
|
return (
|
||||||
<div className="read-block">
|
<div className="drawer-read-block">
|
||||||
<span>{label}</span>
|
<span>{label}</span>
|
||||||
<strong>{value}</strong>
|
<strong>{value}</strong>
|
||||||
</div>
|
</div>
|
||||||
@ -500,23 +559,30 @@ function ReadBlock({ label, value }) {
|
|||||||
function ShopDrawer() {
|
function ShopDrawer() {
|
||||||
const { closePanel, panelOpen, panelType, user } = useShop();
|
const { closePanel, panelOpen, panelType, user } = useShop();
|
||||||
|
|
||||||
|
const drawerLabel = !user ? "Account" : panelType === "cart" ? "Warenkorb" : "Profil";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className={`drawer-backdrop ${panelOpen ? "open" : ""}`}
|
className={`drawer-backdrop ${panelOpen ? "is-open" : ""}`}
|
||||||
onClick={closePanel}
|
onClick={closePanel}
|
||||||
/>
|
/>
|
||||||
<aside
|
<aside
|
||||||
className={`shop-drawer ${panelOpen ? "open" : ""}`}
|
className={`shop-drawer ${panelOpen ? "is-open" : ""}`}
|
||||||
aria-hidden={!panelOpen}
|
aria-hidden={!panelOpen}
|
||||||
data-lenis-prevent
|
data-lenis-prevent
|
||||||
>
|
>
|
||||||
<div className="drawer-top">
|
<header className="drawer-top">
|
||||||
<span>{!user ? "ACCOUNT" : panelType === "cart" ? "CART" : "PROFILE"}</span>
|
<span className="drawer-eyebrow">{drawerLabel}</span>
|
||||||
<button type="button" onClick={closePanel} aria-label="Close panel">
|
<button
|
||||||
x
|
type="button"
|
||||||
|
className="atmos-btn atmos-btn--outline atmos-btn--icon atmos-btn--sm"
|
||||||
|
onClick={closePanel}
|
||||||
|
aria-label="Panel schliessen"
|
||||||
|
>
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</header>
|
||||||
{!user ? <AuthPanel /> : panelType === "cart" ? <CartPanel /> : <ProfilePanel />}
|
{!user ? <AuthPanel /> : panelType === "cart" ? <CartPanel /> : <ProfilePanel />}
|
||||||
</aside>
|
</aside>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -76,10 +76,10 @@ function HeroSection({
|
|||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div className="hero-actions" ref={setActionsRef}>
|
<div className="hero-actions" ref={setActionsRef}>
|
||||||
<a href="#dufte" className="btn btn-primary">
|
<a href="#dufte" className="atmos-btn atmos-btn--primary">
|
||||||
{"Aktuelle D\u00FCfte"}
|
{"Aktuelle D\u00FCfte"}
|
||||||
</a>
|
</a>
|
||||||
<Link to="/discovery-set" className="btn btn-secondary">
|
<Link to="/discovery-set" className="atmos-btn atmos-btn--secondary">
|
||||||
Discovery Set
|
Discovery Set
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -170,39 +170,7 @@
|
|||||||
gap: var(--gap-xs);
|
gap: var(--gap-xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
.discovery-primary-btn {
|
/* Discovery CTAs use the global .atmos-btn system (see src/style/buttons.css). */
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 100%;
|
|
||||||
min-height: 48px;
|
|
||||||
padding: 0.8rem 1.35rem;
|
|
||||||
border: 1px solid var(--theme-accent-fill);
|
|
||||||
border-radius: 0;
|
|
||||||
background: var(--theme-accent-fill);
|
|
||||||
color: var(--theme-accent-contrast);
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: var(--text-sm);
|
|
||||||
letter-spacing: 0.12em;
|
|
||||||
line-height: 1.25;
|
|
||||||
text-align: center;
|
|
||||||
text-transform: uppercase;
|
|
||||||
transition:
|
|
||||||
transform var(--duration-med) var(--ease-out),
|
|
||||||
border-color var(--duration-med) var(--ease-out),
|
|
||||||
box-shadow var(--duration-med) var(--ease-out),
|
|
||||||
background-color var(--duration-med) var(--ease-out);
|
|
||||||
}
|
|
||||||
|
|
||||||
.discovery-primary-btn:hover,
|
|
||||||
.discovery-primary-btn:focus-visible {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: var(--theme-shadow-soft);
|
|
||||||
}
|
|
||||||
|
|
||||||
.discovery-primary-btn:active {
|
|
||||||
transform: translateY(0) scale(0.98);
|
|
||||||
}
|
|
||||||
|
|
||||||
.discovery-panel-actions p {
|
.discovery-panel-actions p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -556,10 +524,8 @@
|
|||||||
min-width: min(100%, 22rem);
|
min-width: min(100%, 22rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
.discovery-final-actions .discovery-primary-btn {
|
/* On the accent-filled final CTA, the `data-on-accent` wrapper auto-flips
|
||||||
border-color: rgba(255, 255, 255, 0.18);
|
.atmos-btn--primary to a white pill — no override needed here. */
|
||||||
background: #262626;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1180px) {
|
@media (max-width: 1180px) {
|
||||||
.discovery-story-grid,
|
.discovery-story-grid,
|
||||||
@ -743,20 +709,12 @@
|
|||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.discovery-primary-btn {
|
|
||||||
min-height: 48px;
|
|
||||||
padding-inline: 1rem;
|
|
||||||
font-size: 0.76rem;
|
|
||||||
white-space: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.discovery-product-card {
|
.discovery-product-card {
|
||||||
min-height: 360px;
|
min-height: 360px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
.discovery-primary-btn,
|
|
||||||
.discovery-product-card,
|
.discovery-product-card,
|
||||||
.discovery-product-image img {
|
.discovery-product-image img {
|
||||||
transition: none;
|
transition: none;
|
||||||
|
|||||||
@ -81,7 +81,11 @@ function DiscoveryOrderPanel({ onBuy }) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="discovery-panel-actions">
|
<div className="discovery-panel-actions">
|
||||||
<button type="button" className="discovery-primary-btn" onClick={onBuy}>
|
<button
|
||||||
|
type="button"
|
||||||
|
className="atmos-btn atmos-btn--primary atmos-btn--block atmos-btn--uppercase"
|
||||||
|
onClick={onBuy}
|
||||||
|
>
|
||||||
Kaufen
|
Kaufen
|
||||||
</button>
|
</button>
|
||||||
<p>Nur das erste Set erstellt einen einmaligen CHF 48 Full-Size-Rabatt.</p>
|
<p>Nur das erste Set erstellt einen einmaligen CHF 48 Full-Size-Rabatt.</p>
|
||||||
@ -265,7 +269,7 @@ function DiscoveryComparisonSection() {
|
|||||||
|
|
||||||
function DiscoveryFinalCta({ onBuy }) {
|
function DiscoveryFinalCta({ onBuy }) {
|
||||||
return (
|
return (
|
||||||
<section className="discovery-final-cta" data-reveal-group>
|
<section className="discovery-final-cta" data-reveal-group data-on-accent>
|
||||||
<div>
|
<div>
|
||||||
<span className="discovery-label" data-reveal="fade">
|
<span className="discovery-label" data-reveal="fade">
|
||||||
Discovery Set
|
Discovery Set
|
||||||
@ -277,8 +281,12 @@ function DiscoveryFinalCta({ onBuy }) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="discovery-final-actions" data-reveal="fade">
|
<div className="discovery-final-actions" data-reveal="fade">
|
||||||
<button type="button" className="discovery-primary-btn" onClick={onBuy}>
|
<button
|
||||||
Kaufen
|
type="button"
|
||||||
|
className="atmos-btn atmos-btn--primary atmos-btn--uppercase"
|
||||||
|
onClick={onBuy}
|
||||||
|
>
|
||||||
|
Discovery Set bestellen
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@ -161,47 +161,7 @@ body.theme-light .hero-wordmark__image {
|
|||||||
will-change: transform, opacity;
|
will-change: transform, opacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn,
|
/* Hero CTAs use the global .atmos-btn system (see src/style/buttons.css). */
|
||||||
.discovery-btn {
|
|
||||||
min-height: 48px;
|
|
||||||
border: none;
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
padding: 0 clamp(1rem, 2vw, 1.35rem);
|
|
||||||
font-size: var(--text-sm);
|
|
||||||
cursor: pointer;
|
|
||||||
transition:
|
|
||||||
transform var(--duration-med) var(--ease-out),
|
|
||||||
opacity var(--duration-med) var(--ease-out),
|
|
||||||
box-shadow var(--duration-med) var(--ease-out),
|
|
||||||
background-color var(--duration-med) var(--ease-out),
|
|
||||||
border-color var(--duration-med) var(--ease-out);
|
|
||||||
text-decoration: none;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:hover,
|
|
||||||
.discovery-btn:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:active,
|
|
||||||
.discovery-btn:active {
|
|
||||||
transform: translateY(0) scale(0.98);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary {
|
|
||||||
background: var(--theme-accent-fill);
|
|
||||||
color: var(--theme-accent-contrast);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-secondary {
|
|
||||||
background: color-mix(in srgb, var(--theme-surface) 72%, transparent);
|
|
||||||
color: var(--theme-text);
|
|
||||||
border: 1px solid var(--theme-border-strong);
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-scroll {
|
.hero-scroll {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -587,11 +547,8 @@ body.theme-light .hero-wordmark__image {
|
|||||||
line-height: 1.62;
|
line-height: 1.62;
|
||||||
}
|
}
|
||||||
|
|
||||||
.discovery-btn {
|
.discovery-copy .atmos-btn {
|
||||||
margin-top: clamp(1.3rem, 3vw, 2.1rem);
|
margin-top: clamp(1.3rem, 3vw, 2.1rem);
|
||||||
background: var(--theme-surface);
|
|
||||||
color: var(--theme-text);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.discovery-banner {
|
.discovery-banner {
|
||||||
@ -719,7 +676,7 @@ body.theme-light .hero-wordmark__image {
|
|||||||
margin-top: 1.1rem;
|
margin-top: 1.1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-actions .btn {
|
.hero-actions .atmos-btn {
|
||||||
min-height: 44px;
|
min-height: 44px;
|
||||||
padding-inline: 0.95rem;
|
padding-inline: 0.95rem;
|
||||||
font-size: 0.78rem;
|
font-size: 0.78rem;
|
||||||
|
|||||||
@ -554,7 +554,11 @@ function LandingPage() {
|
|||||||
<br />
|
<br />
|
||||||
Verstehen, was funktioniert.
|
Verstehen, was funktioniert.
|
||||||
</p>
|
</p>
|
||||||
<Link to="/discovery-set" className="discovery-btn" data-reveal="fade">
|
<Link
|
||||||
|
to="/discovery-set"
|
||||||
|
className="atmos-btn atmos-btn--invert"
|
||||||
|
data-reveal="fade"
|
||||||
|
>
|
||||||
Discovery Set bestellen
|
Discovery Set bestellen
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -136,7 +136,7 @@ function SmallBatchPage() {
|
|||||||
{state.error && <p className="small-message">{state.error}</p>}
|
{state.error && <p className="small-message">{state.error}</p>}
|
||||||
{state.loading && <p className="small-message">Loading access…</p>}
|
{state.loading && <p className="small-message">Loading access…</p>}
|
||||||
|
|
||||||
{loyalty.unlocked && state.releases.length > 0 && (
|
{loyalty.unlocked && (
|
||||||
<section
|
<section
|
||||||
className="release-grid"
|
className="release-grid"
|
||||||
data-reveal-group
|
data-reveal-group
|
||||||
|
|||||||
@ -273,6 +273,13 @@
|
|||||||
letter-spacing: 0.12em;
|
letter-spacing: 0.12em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Content distributed across the button (label left, icon right). */
|
||||||
|
.atmos-btn--split {
|
||||||
|
justify-content: space-between;
|
||||||
|
text-align: left;
|
||||||
|
padding-inline: clamp(0.85rem, 1.6vw, 1.1rem);
|
||||||
|
}
|
||||||
|
|
||||||
/* Group helper — consistent spacing wherever buttons cluster. */
|
/* Group helper — consistent spacing wherever buttons cluster. */
|
||||||
.atmos-btn-row {
|
.atmos-btn-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user