Add a tier number and category label to package cards
Inspired by a reference mockup showing a numbered, categorized tier lineup (Einstieg/Upgrade/.../Referenz). Each card now shows its price rank (01, 02...) plus an auto-derived category eyebrow label, computed the same way as the material-tier color system so it needs no extra admin config and scales to any package count. Kept the existing responsive grid rather than switching to a fixed horizontal row, since the fixed layout looks broken for models with only 1-2 packages. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
dea2a936a1
commit
bac6d114a2
3 changed files with 12 additions and 1 deletions
|
|
@ -155,6 +155,7 @@ 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'],
|
||||||
},
|
},
|
||||||
leistungen: {
|
leistungen: {
|
||||||
metaTitle: 'Leistungen',
|
metaTitle: 'Leistungen',
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,7 @@ 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'],
|
||||||
},
|
},
|
||||||
leistungen: {
|
leistungen: {
|
||||||
metaTitle: 'Services',
|
metaTitle: 'Services',
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,7 @@ export default function ModelPage() {
|
||||||
// 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 materials = theme === 'dark' ? MATERIALS_DARK : MATERIALS_LIGHT;
|
const materials = theme === 'dark' ? MATERIALS_DARK : MATERIALS_LIGHT;
|
||||||
|
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])
|
||||||
);
|
);
|
||||||
|
|
@ -118,6 +119,8 @@ 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');
|
||||||
|
|
@ -151,6 +154,8 @@ export default function ModelPage() {
|
||||||
return {
|
return {
|
||||||
isDarkCard,
|
isDarkCard,
|
||||||
mutedColor,
|
mutedColor,
|
||||||
|
tierNumber,
|
||||||
|
tierLabel,
|
||||||
priceColor: `rgb(${priceRgb.join(', ')})`,
|
priceColor: `rgb(${priceRgb.join(', ')})`,
|
||||||
iconChipStyle: {
|
iconChipStyle: {
|
||||||
backgroundColor: `rgba(${accentRgb.join(', ')}, 0.16)`,
|
backgroundColor: `rgba(${accentRgb.join(', ')}, 0.16)`,
|
||||||
|
|
@ -198,7 +203,7 @@ 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 { isDarkCard, style, priceColor, mutedColor, iconChipStyle } = styleOf(pkg);
|
const { isDarkCard, style, priceColor, mutedColor, iconChipStyle, tierNumber, tierLabel } = styleOf(pkg);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={pkg.id} style={style} className="relative flex flex-col rounded-xl border p-6">
|
<div key={pkg.id} style={style} className="relative flex flex-col rounded-xl border p-6">
|
||||||
|
|
@ -208,6 +213,10 @@ export default function ModelPage() {
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<p style={{ color: mutedColor }} className="mb-2 text-xs font-semibold uppercase tracking-wider">
|
||||||
|
{tierNumber} · {tierLabel}
|
||||||
|
</p>
|
||||||
|
|
||||||
{pkg.icon_name && (
|
{pkg.icon_name && (
|
||||||
<div style={iconChipStyle} className="mb-3 flex h-11 w-11 items-center justify-center rounded-full">
|
<div style={iconChipStyle} className="mb-3 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