Remove tier numbering/labels and add generated metallic surface textures
Dropped the "01 · EINSTIEG" style number/category eyebrow from package cards per feedback. Added a real brushed-metal surface texture per theme (generated via Higgsfield/GPT Image 2 - graphite steel, blue-black titanium, warm bronze/copper), blended with the existing price-driven color gradient via background-blend-mode so the material grain reads as genuine surface detail while the escalation logic stays untouched. Textures are pre-optimized WebP, 7-21KB each. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
3868a258e4
commit
f50cc063d1
6 changed files with 22 additions and 29 deletions
BIN
hifi-src/src/assets/textures/metal-deep-blue.webp
Normal file
BIN
hifi-src/src/assets/textures/metal-deep-blue.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.4 KiB |
BIN
hifi-src/src/assets/textures/metal-graphite.webp
Normal file
BIN
hifi-src/src/assets/textures/metal-graphite.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8 KiB |
BIN
hifi-src/src/assets/textures/metal-warm-bronze.webp
Normal file
BIN
hifi-src/src/assets/textures/metal-warm-bronze.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
|
|
@ -155,7 +155,6 @@ export default {
|
||||||
productLoading: 'Produkt wird geladen…',
|
productLoading: 'Produkt wird geladen…',
|
||||||
requestContact: 'Kontakt anfragen',
|
requestContact: 'Kontakt anfragen',
|
||||||
featuredBadge: 'Empfohlen',
|
featuredBadge: 'Empfohlen',
|
||||||
tierLabels: ['Einstieg', 'Upgrade', 'Performance', 'Premium', 'High-End', 'Signature', 'Referenz'],
|
|
||||||
tiersHeading: (count) => `${count} Stufen. Ein Ziel: perfekter Klang.`,
|
tiersHeading: (count) => `${count} Stufen. Ein Ziel: perfekter Klang.`,
|
||||||
tiersSubheading: 'Von der soliden Basis bis zum kompromisslosen Meisterwerk – finde dein Paket.',
|
tiersSubheading: 'Von der soliden Basis bis zum kompromisslosen Meisterwerk – finde dein Paket.',
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -155,7 +155,6 @@ export default {
|
||||||
productLoading: 'Loading product…',
|
productLoading: 'Loading product…',
|
||||||
requestContact: 'Request contact',
|
requestContact: 'Request contact',
|
||||||
featuredBadge: 'Recommended',
|
featuredBadge: 'Recommended',
|
||||||
tierLabels: ['Entry', 'Upgrade', 'Performance', 'Premium', 'High-End', 'Signature', 'Reference'],
|
|
||||||
tiersHeading: (count) => `${count} tiers. One goal: perfect sound.`,
|
tiersHeading: (count) => `${count} tiers. One goal: perfect sound.`,
|
||||||
tiersSubheading: 'From a solid foundation to an uncompromising masterpiece – find your package.',
|
tiersSubheading: 'From a solid foundation to an uncompromising masterpiece – find your package.',
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,19 @@ import DynamicIcon from '../../components/DynamicIcon.jsx';
|
||||||
import { useMaintenance } from '../../context/MaintenanceContext.jsx';
|
import { useMaintenance } from '../../context/MaintenanceContext.jsx';
|
||||||
import { useLanguage } from '../../context/LanguageContext.jsx';
|
import { useLanguage } from '../../context/LanguageContext.jsx';
|
||||||
import { useSiteSettings } from '../../context/SiteSettingsContext.jsx';
|
import { useSiteSettings } from '../../context/SiteSettingsContext.jsx';
|
||||||
|
import textureGraphite from '../../assets/textures/metal-graphite.webp';
|
||||||
|
import textureDeepBlue from '../../assets/textures/metal-deep-blue.webp';
|
||||||
|
import textureWarmBronze from '../../assets/textures/metal-warm-bronze.webp';
|
||||||
|
|
||||||
|
// Eine generierte, gebuerstete Metall-Oberflaechenstruktur je Theme (statt Fotos pro
|
||||||
|
// Paket, die es fuer echte Kundenpakete nicht gibt) - wird per "overlay"-Blendmodus mit
|
||||||
|
// dem bestehenden Farbverlauf kombiniert, sodass die Preis-Staffelung weiterhin die
|
||||||
|
// Farbe/Helligkeit steuert, die Textur nur die Material-Oberflaeche liefert.
|
||||||
|
const PACKAGE_TEXTURES = {
|
||||||
|
graphite: textureGraphite,
|
||||||
|
'deep-blue': textureDeepBlue,
|
||||||
|
'warm-bronze': textureWarmBronze,
|
||||||
|
};
|
||||||
|
|
||||||
// Material-Stufen statt einfarbigem Verlauf: jede Preisstufe durchlaeuft eine eigene
|
// Material-Stufen statt einfarbigem Verlauf: jede Preisstufe durchlaeuft eine eigene
|
||||||
// "Wertigkeit" wie bei Kreditkarten-/Loyalty-Stufen. Die Paket-Sektion sitzt bewusst IMMER
|
// "Wertigkeit" wie bei Kreditkarten-/Loyalty-Stufen. Die Paket-Sektion sitzt bewusst IMMER
|
||||||
|
|
@ -89,6 +102,7 @@ export default function ModelPage() {
|
||||||
const { t, language } = useLanguage();
|
const { t, language } = useLanguage();
|
||||||
const { package_card_theme: packageCardTheme } = useSiteSettings();
|
const { package_card_theme: packageCardTheme } = useSiteSettings();
|
||||||
const MATERIALS = PACKAGE_THEMES[packageCardTheme] || PACKAGE_THEMES.graphite;
|
const MATERIALS = PACKAGE_THEMES[packageCardTheme] || PACKAGE_THEMES.graphite;
|
||||||
|
const cardTexture = PACKAGE_TEXTURES[packageCardTheme] || PACKAGE_TEXTURES.graphite;
|
||||||
const formatPrice = (value) =>
|
const formatPrice = (value) =>
|
||||||
new Intl.NumberFormat(language === 'de' ? 'de-DE' : 'en-US', { style: 'currency', currency: 'EUR' }).format(value);
|
new Intl.NumberFormat(language === 'de' ? 'de-DE' : 'en-US', { style: 'currency', currency: 'EUR' }).format(value);
|
||||||
|
|
||||||
|
|
@ -125,7 +139,6 @@ export default function ModelPage() {
|
||||||
// eine eigene Materialstufe (Bronze/Silber/Gold/Platin) bis zur Onyx-Kroenung ganz
|
// eine eigene Materialstufe (Bronze/Silber/Gold/Platin) bis zur Onyx-Kroenung ganz
|
||||||
// oben. Das braucht keine zusaetzliche Admin-Einstellung und passt sich automatisch
|
// oben. Das braucht keine zusaetzliche Admin-Einstellung und passt sich automatisch
|
||||||
// an jede Paketanzahl an.
|
// an jede Paketanzahl an.
|
||||||
const tierLabels = t('modelPage.tierLabels');
|
|
||||||
const rankById = new Map(
|
const rankById = new Map(
|
||||||
[...packages].sort((a, b) => a.total_price - b.total_price).map((p, i) => [p.id, i])
|
[...packages].sort((a, b) => a.total_price - b.total_price).map((p, i) => [p.id, i])
|
||||||
);
|
);
|
||||||
|
|
@ -133,8 +146,6 @@ export default function ModelPage() {
|
||||||
const n = packages.length;
|
const n = packages.length;
|
||||||
const rank = rankById.get(pkg.id) ?? 0;
|
const rank = rankById.get(pkg.id) ?? 0;
|
||||||
const tierT = n <= 1 ? 0 : rank / (n - 1);
|
const tierT = n <= 1 ? 0 : rank / (n - 1);
|
||||||
const tierNumber = String(rank + 1).padStart(2, '0');
|
|
||||||
const tierLabel = tierLabels[Math.min(Math.floor(tierT * tierLabels.length), tierLabels.length - 1)];
|
|
||||||
|
|
||||||
const bgRgb = materialRgbAt(MATERIALS, tierT, 'bg').map(Math.round);
|
const bgRgb = materialRgbAt(MATERIALS, tierT, 'bg').map(Math.round);
|
||||||
const accentRgbFloat = materialRgbAt(MATERIALS, tierT, 'accent');
|
const accentRgbFloat = materialRgbAt(MATERIALS, tierT, 'accent');
|
||||||
|
|
@ -171,8 +182,6 @@ export default function ModelPage() {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
mutedColor,
|
mutedColor,
|
||||||
tierNumber,
|
|
||||||
tierLabel,
|
|
||||||
accentColor: `rgb(${accentRgb.join(', ')})`,
|
accentColor: `rgb(${accentRgb.join(', ')})`,
|
||||||
priceColor: `rgb(${priceRgb.join(', ')})`,
|
priceColor: `rgb(${priceRgb.join(', ')})`,
|
||||||
roadOpacity,
|
roadOpacity,
|
||||||
|
|
@ -190,7 +199,12 @@ export default function ModelPage() {
|
||||||
},
|
},
|
||||||
style: {
|
style: {
|
||||||
backgroundColor: `rgb(${bgRgb.join(', ')})`,
|
backgroundColor: `rgb(${bgRgb.join(', ')})`,
|
||||||
backgroundImage: `radial-gradient(135% 160% at 88% -20%, rgb(${highlight.join(', ')}) 0%, rgb(${bgRgb.join(', ')}) 55%)`,
|
// Generierte Metall-Oberflaechenstruktur (oberste Ebene, per Blendmodus mit der
|
||||||
|
// Farbe darunter verschmolzen) + der bestehende Eck-Lichtschein-Verlauf darunter -
|
||||||
|
// die Preis-Farbstufe bleibt fuehrend, die Textur liefert nur die Materialoberflaeche.
|
||||||
|
backgroundImage: `url(${cardTexture}), radial-gradient(135% 160% at 88% -20%, rgb(${highlight.join(', ')}) 0%, rgb(${bgRgb.join(', ')}) 55%)`,
|
||||||
|
backgroundSize: '480px 480px, cover',
|
||||||
|
backgroundBlendMode: 'overlay, normal',
|
||||||
borderColor: `rgb(${accentRgb.join(', ')})`,
|
borderColor: `rgb(${accentRgb.join(', ')})`,
|
||||||
boxShadow:
|
boxShadow:
|
||||||
tierT > 0.04
|
tierT > 0.04
|
||||||
|
|
@ -241,18 +255,8 @@ export default function ModelPage() {
|
||||||
|
|
||||||
<div className="grid gap-6 sm:grid-cols-2 xl:grid-cols-3">
|
<div className="grid gap-6 sm:grid-cols-2 xl:grid-cols-3">
|
||||||
{packages.map((pkg) => {
|
{packages.map((pkg) => {
|
||||||
const {
|
const { style, priceColor, mutedColor, iconChipStyle, accentColor, glowLineStyle, roadOpacity, roadGlowOpacity } =
|
||||||
style,
|
styleOf(pkg);
|
||||||
priceColor,
|
|
||||||
mutedColor,
|
|
||||||
iconChipStyle,
|
|
||||||
tierNumber,
|
|
||||||
tierLabel,
|
|
||||||
accentColor,
|
|
||||||
glowLineStyle,
|
|
||||||
roadOpacity,
|
|
||||||
roadGlowOpacity,
|
|
||||||
} = styleOf(pkg);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={pkg.id} style={style} className="relative flex flex-col overflow-hidden rounded-xl border p-8">
|
<div key={pkg.id} style={style} className="relative flex flex-col overflow-hidden rounded-xl border p-8">
|
||||||
|
|
@ -292,15 +296,6 @@ export default function ModelPage() {
|
||||||
{/* Eigener Stapelkontext ueber dem Lichtweg-SVG, damit der Text IMMER lesbar
|
{/* Eigener Stapelkontext ueber dem Lichtweg-SVG, damit der Text IMMER lesbar
|
||||||
oben liegt statt vom absolut positionierten Hintergrund verdeckt zu werden. */}
|
oben liegt statt vom absolut positionierten Hintergrund verdeckt zu werden. */}
|
||||||
<div className="relative z-10 flex flex-1 flex-col">
|
<div className="relative z-10 flex flex-1 flex-col">
|
||||||
<div className="mb-4 flex items-baseline gap-3">
|
|
||||||
<span style={{ color: mutedColor }} className="text-3xl font-extralight leading-none">
|
|
||||||
{tierNumber}
|
|
||||||
</span>
|
|
||||||
<span style={{ color: accentColor }} className="text-xs font-semibold uppercase tracking-[0.2em]">
|
|
||||||
{tierLabel}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{pkg.icon_name && (
|
{pkg.icon_name && (
|
||||||
<div style={iconChipStyle} className="mb-4 flex h-11 w-11 items-center justify-center rounded-full">
|
<div style={iconChipStyle} className="mb-4 flex h-11 w-11 items-center justify-center rounded-full">
|
||||||
<DynamicIcon name={pkg.icon_name} className="h-6 w-6" />
|
<DynamicIcon name={pkg.icon_name} className="h-6 w-6" />
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue