From 017835b083e87cc7a54b35b3b0d93a8cb3ee05fd Mon Sep 17 00:00:00 2001 From: Benjamin Sutter Date: Wed, 10 Jun 2026 23:53:15 +0200 Subject: [PATCH] =?UTF-8?q?fix(typography):=20remove=20DM=20Serif=20Displa?= =?UTF-8?q?y=20=E2=80=94=20all=20UI=20text=20now=20uses=20Inter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PageHeader titles, MatchCardCompact score badge, and IntelligenceMatchCard score badge were the only 3 places using the serif override. All now use the theme-defined Inter stack (fontWeight 600/700, letterSpacing tight). PDF/print previews (BerichtDialog, LatentInquiryReportPreview) intentionally keep their serif fonts — those are document renderers, not UI. Co-Authored-By: Claude Sonnet 4.6 --- src/components/layout/PageHeader.tsx | 2 +- .../match-card/IntelligenceMatchCard.tsx | 60 ++++--- .../match-card/MatchCardCompact.tsx | 156 ++++++++---------- 3 files changed, 105 insertions(+), 113 deletions(-) diff --git a/src/components/layout/PageHeader.tsx b/src/components/layout/PageHeader.tsx index fa682d1..6c2d664 100644 --- a/src/components/layout/PageHeader.tsx +++ b/src/components/layout/PageHeader.tsx @@ -52,7 +52,7 @@ export function PageHeader({ - + {title} {badge} diff --git a/src/components/match-card/IntelligenceMatchCard.tsx b/src/components/match-card/IntelligenceMatchCard.tsx index edb90da..7cb0935 100644 --- a/src/components/match-card/IntelligenceMatchCard.tsx +++ b/src/components/match-card/IntelligenceMatchCard.tsx @@ -1,5 +1,5 @@ -import { Box, Button, Chip, Tooltip, Typography } from '@mui/material' -import { Building2, CheckCircle2 } from 'lucide-react' +import { Box, Button, Chip, Typography } from '@mui/material' +import { Building2, CheckCircle2, MapPin } from 'lucide-react' import { useSessionStore } from '../../stores/sessionStore' import { useInquiryStore } from '../../stores/inquiryStore' import { LocationPreview } from '../shared/LocationPreview' @@ -34,13 +34,8 @@ export function IntelligenceMatchCard({ vm, imageUrl, lat, lng, cityLabel, overl return } - // secondary chips — max 2 visible - const chips: Array<{ label: string; color?: string }> = [] - if (vm.availabilityLabel) chips.push({ label: vm.availabilityLabel }) - if (vm.fitOutLabel) chips.push({ label: vm.fitOutLabel }) - if (vm.isDivisible && vm.minDivisibleUnitSqm) chips.push({ label: `Teilbar ab ${vm.minDivisibleUnitSqm} m²` }) - const visibleChips = chips.slice(0, 2) - const hiddenChips = chips.slice(2) + // secondary chips — max 1 visible (availability only), no overflow chip + const secondaryChip = vm.availabilityLabel ?? vm.fitOutLabel ?? null return ( {/* ── Hero zone ── */} @@ -67,15 +69,29 @@ export function IntelligenceMatchCard({ vm, imageUrl, lat, lng, cityLabel, overl border: `1px solid ${theme.border}`, }}> {vm.matchScore}% - {/* Top-right badge strip */} + {/* Location pill — Ginesta-style, top-left */} + + + + {vm.locationLabel} + + + + {/* Top-right badge strip — result type + owner + heat */} {vm.resultType === 'VERIFIED_PORTFOLIO' && isPortfolioOwner && ( @@ -116,19 +132,13 @@ export function IntelligenceMatchCard({ vm, imageUrl, lat, lng, cityLabel, overl )} - {/* Confidence + secondary chips */} + {/* Confidence + one secondary chip max */} - {visibleChips.map((c, i) => ( - - ))} - {hiddenChips.length > 0 && ( - c.label).join(' · ')} arrow> - - )} @@ -145,7 +155,7 @@ export function IntelligenceMatchCard({ vm, imageUrl, lat, lng, cityLabel, overl {vm.reasons.length > 0 && ( - {vm.reasons.slice(0, 3).map((r, i) => ( + {vm.reasons.slice(0, 2).map((r, i) => ( diff --git a/src/components/match-card/MatchCardCompact.tsx b/src/components/match-card/MatchCardCompact.tsx index 08dee6e..6ea18b6 100644 --- a/src/components/match-card/MatchCardCompact.tsx +++ b/src/components/match-card/MatchCardCompact.tsx @@ -1,23 +1,21 @@ import { memo } from 'react' -import { Alert, Box, Button, Chip, Divider, Tooltip, Typography } from '@mui/material' -import { MapPin, Building2, CheckCircle2, AlertTriangle } from 'lucide-react' +import { Alert, Box, Button, Chip, Typography } from '@mui/material' +import { MapPin, AlertTriangle } from 'lucide-react' import { useSessionStore } from '../../stores/sessionStore' import { useInquiryStore } from '../../stores/inquiryStore' import { HeatBadge } from '../shared/HeatBadge' import { getScoreTier, SCORE_THEME } from '../shared/scoreTheme' import { RESULT_TYPE_META, DS_TEXT, DS_BORDER } from '../../lib/ds' import { confidenceHex } from '../../lib/utils' -import { ScoreInlineBreakdown } from './ScoreInlineBreakdown' import { MatchCardRestrictedState } from './MatchCardRestrictedState' import type { MatchCardViewModel } from './MatchCardViewModel' interface Props { vm: MatchCardViewModel imageUrl?: string - overlayTypeLabel?: string } -export const MatchCardCompact = memo(function MatchCardCompact({ vm, imageUrl, overlayTypeLabel }: Props) { +export const MatchCardCompact = memo(function MatchCardCompact({ vm, imageUrl }: Props) { if (vm.isRestricted) return const { currentUser } = useSessionStore() @@ -32,18 +30,20 @@ export const MatchCardCompact = memo(function MatchCardCompact({ vm, imageUrl, o const leftAccent = vm.isCompareSelected ? '#5b3f8a' : vm.isSelected ? '#152642' : 'transparent' - const extraChips: string[] = [] - if (vm.availabilityLabel) extraChips.push(vm.availabilityLabel) - if (vm.fitOutLabel) extraChips.push(vm.fitOutLabel) - if (vm.isDivisible && vm.minDivisibleUnitSqm) extraChips.push(`Teilbar ab ${vm.minDivisibleUnitSqm} m²`) - if (topRisk) extraChips.push(topRisk.level === 'CRITICAL' ? 'Kritisch' : 'Hohes Risiko') - const visibleExtra = extraChips.slice(0, 2) - const hiddenExtra = extraChips.slice(2) + // Compact metric line — area · rent · availability + const metricParts: string[] = [] + if (vm.areaSqm) metricParts.push(`${vm.areaSqm.toLocaleString('de-CH')} m²`) + if (vm.rentPerSqm) metricParts.push(`CHF ${vm.rentPerSqm}/m²/J`) + if (vm.availabilityLabel) metricParts.push(vm.availabilityLabel) + const metricLine = metricParts.join(' · ') + + // Detail action — prefer the "Details →" action if available, fallback to first action + const detailAction = vm.actions.find(a => a.actionType === 'OPEN_DETAIL' && a.label.includes('Detail')) + ?? vm.actions.find(a => a.actionType === 'OPEN_DETAIL') return ( - {/* ── Image strip ── */} - + {/* ── Image strip — 180px, Ginesta proportion ── */} + {imageUrl ? ( )} - {/* Score badge */} + {/* Score badge — top left */} {vm.matchScore}% - {overlayTypeLabel && ( - - - {overlayTypeLabel} - - - )} + {/* Location pill — Ginesta-style, top right */} + + + + {vm.locationLabel} + + {/* ── Content ── */} - + {vm.disclaimer && ( {vm.disclaimer} )} - {/* Badges row */} - - + {/* Chips — result type + heat + confidence */} + - {vm.resultType === 'VERIFIED_PORTFOLIO' && isPortfolioOwner && ( - } label="Ihr Objekt" size="small" - sx={{ bgcolor: 'rgba(21,38,66,0.10)', color: '#152642', fontWeight: 700, fontSize: 9, height: 18, '& .MuiChip-icon': { ml: 0.5 } }} /> - )} + - {visibleExtra.map((label, i) => ( - - ))} - {hiddenExtra.length > 0 && ( - - - + {vm.resultType === 'VERIFIED_PORTFOLIO' && isPortfolioOwner && ( + )} - {/* Title + location */} + {/* Title */} {vm.title} @@ -141,40 +140,25 @@ export const MatchCardCompact = memo(function MatchCardCompact({ vm, imageUrl, o {vm.propertySubtitle} )} - - - - {vm.locationLabel} - - + {/* Metric line — Ginesta-style */} + {metricLine && ( + + {metricLine} + + )} + + {/* Explainability summary — 1 line */} {vm.explainabilitySummary && ( - + {vm.explainabilitySummary} )} - {vm.reasons.length > 0 && ( - - {vm.reasons.slice(0, 2).map((r, i) => ( - - - - {r.label} - - - ))} - - )} - - {vm.scoreBreakdown && vm.allFactors && vm.allFactors.length > 0 && ( - <> - - - - )} - + {/* Critical risk only */} {topRisk && topRisk.level === 'CRITICAL' && ( } severity="error" sx={{ mt: 0.75, py: 0.25, fontSize: '0.72rem' }}> @@ -182,8 +166,8 @@ export const MatchCardCompact = memo(function MatchCardCompact({ vm, imageUrl, o )} - {/* Actions */} - + {/* 2 Buttons — Anfrage + Details */} + - {vm.actions.map(a => ( + {detailAction && ( - ))} + )}