fix(appearance): clearer widget settings, density hint, solid surfaces with transparency off

Dashboard widget settings are grouped by where they sit on the dashboard (below the hero / right sidebar / bottom of page); the right-sidebar master toggle now nests its individual widgets and greys them out when the sidebar is off, instead of a confusing flat list mixing the master with its children. Density gains an explanatory hint plus a real compact spacing effect. Transparency-off also solidifies the Atlas glass panels and tooltip, Leaflet zoom controls and GL popups — class-based surfaces via CSS, the Atlas inline panels via a noTransparency flag.
This commit is contained in:
Maurice
2026-06-29 11:47:25 +02:00
committed by Maurice
parent a3f395e5ac
commit fac043c691
5 changed files with 148 additions and 77 deletions
@@ -38,22 +38,26 @@ const isHex = (v: string) => /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(v)
type DesktopWidgetKey = keyof AppearanceConfig['dashboard']['desktop']
type MobileWidgetKey = keyof AppearanceConfig['dashboard']['mobile']
const DESKTOP_WIDGETS: { key: DesktopWidgetKey; fallback: string }[] = [
{ key: 'sidebar', fallback: 'Right sidebar' },
{ key: 'currency', fallback: 'Currency' },
{ key: 'timezones', fallback: 'Timezones' },
{ key: 'upcomingReservations', fallback: 'Upcoming reservations' },
{ key: 'atlas', fallback: 'Atlas / countries' },
{ key: 'tripsTotal', fallback: 'Trips total' },
{ key: 'daysTraveled', fallback: 'Days traveled' },
{ key: 'distanceFlown', fallback: 'Distance flown' },
const WIDGET_LABELS: Record<string, string> = {
sidebar: 'Right sidebar',
currency: 'Currency',
timezones: 'Timezones',
upcomingReservations: 'Upcoming reservations',
atlas: 'Atlas / countries',
tripsTotal: 'Trips total',
daysTraveled: 'Days traveled',
distanceFlown: 'Distance flown',
}
// Grouped by where the widgets actually sit on the dashboard. The right sidebar
// has a master toggle (off → no sidebar, layout centers); its individual
// widgets only matter while the sidebar is shown.
const DESKTOP_GROUPS: { id: string; fallback: string; master?: DesktopWidgetKey; keys: DesktopWidgetKey[] }[] = [
{ id: 'belowHero', fallback: 'Below the hero', keys: ['atlas', 'tripsTotal', 'daysTraveled', 'distanceFlown'] },
{ id: 'rightSidebar', fallback: 'Right sidebar', master: 'sidebar', keys: ['currency', 'timezones', 'upcomingReservations'] },
]
const MOBILE_WIDGETS: { key: MobileWidgetKey; fallback: string }[] = [
{ key: 'tripsTotal', fallback: 'Trips total' },
{ key: 'daysTraveled', fallback: 'Days traveled' },
{ key: 'currency', fallback: 'Currency' },
{ key: 'timezones', fallback: 'Timezones' },
{ key: 'upcomingReservations', fallback: 'Upcoming reservations' },
const MOBILE_GROUPS: { id: string; fallback: string; keys: MobileWidgetKey[] }[] = [
{ id: 'belowHero', fallback: 'Below the hero', keys: ['tripsTotal', 'daysTraveled'] },
{ id: 'bottomOfPage', fallback: 'Bottom of page', keys: ['currency', 'timezones', 'upcomingReservations'] },
]
// shared segmented-button style (matches DisplaySettingsTab)
@@ -61,7 +65,7 @@ function segStyle(active: boolean): React.CSSProperties {
return {
display: 'flex', alignItems: 'center', gap: 6, justifyContent: 'center',
padding: '10px 14px', borderRadius: 10, cursor: 'pointer', flex: '1 1 0', minWidth: 0,
fontFamily: 'inherit', fontSize: 14, fontWeight: 500,
fontFamily: 'inherit', fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 500,
border: active ? '2px solid var(--text-primary)' : '2px solid var(--border-primary)',
background: active ? 'var(--bg-hover)' : 'var(--bg-card)',
color: 'var(--text-primary)', transition: 'all 0.15s',
@@ -174,7 +178,7 @@ export default function AppearanceSettingsTab(): React.ReactElement {
onClick={() => update({ schemeId: s.id })}
style={{
display: 'flex', alignItems: 'center', gap: 8, padding: '10px 12px',
borderRadius: 10, cursor: 'pointer', fontFamily: 'inherit', fontSize: 13, fontWeight: 500,
borderRadius: 10, cursor: 'pointer', fontFamily: 'inherit', fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500,
border: active ? '2px solid var(--text-primary)' : '2px solid var(--border-primary)',
background: active ? 'var(--bg-hover)' : 'var(--bg-card)', color: 'var(--text-primary)',
transition: 'all 0.15s',
@@ -192,7 +196,7 @@ export default function AppearanceSettingsTab(): React.ReactElement {
onClick={() => update({ schemeId: 'custom', accent: cfg.accent ?? { light: accentLight, dark: accentDark } })}
style={{
display: 'flex', alignItems: 'center', gap: 8, padding: '10px 12px', borderRadius: 10,
cursor: 'pointer', fontFamily: 'inherit', fontSize: 13, fontWeight: 500,
cursor: 'pointer', fontFamily: 'inherit', fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500,
border: cfg.schemeId === 'custom' ? '2px solid var(--text-primary)' : '2px solid var(--border-primary)',
background: cfg.schemeId === 'custom' ? 'var(--bg-hover)' : 'var(--bg-card)', color: 'var(--text-primary)',
transition: 'all 0.15s',
@@ -276,6 +280,9 @@ export default function AppearanceSettingsTab(): React.ReactElement {
</button>
))}
</div>
<p className="text-xs text-content-faint mt-2">
{tr('settings.appearance.densityHint', 'Compact tightens spacing and padding for a denser layout that fits more on screen.')}
</p>
</div>
{/* Text size (global) */}
@@ -304,30 +311,59 @@ export default function AppearanceSettingsTab(): React.ReactElement {
{/* ── Dashboard widgets ───────────────────────────────────── */}
<Section title={tr('settings.appearance.dashboardWidgets', 'Dashboard widgets')} icon={LayoutDashboard}>
<p className="text-xs text-content-faint -mt-1">
{tr('settings.appearance.dashboardWidgetsHint', 'Show or hide dashboard widgets independently on desktop and mobile.')}
{tr('settings.appearance.dashboardWidgetsHint', 'Choose which widgets appear on the dashboard independently for desktop and mobile.')}
</p>
<div>
<div className="text-sm font-medium mb-2 text-content-secondary">{tr('settings.appearance.desktop', 'Desktop')}</div>
{DESKTOP_WIDGETS.map((w) => (
<ToggleRow
key={w.key}
label={tr(`settings.appearance.widget.${w.key}`, w.fallback)}
on={cfg.dashboard.desktop[w.key]}
onToggle={() => setWidget('desktop', w.key, !cfg.dashboard.desktop[w.key])}
/>
))}
</div>
<div>
<div className="text-sm font-medium mb-2 text-content-secondary">{tr('settings.appearance.mobile', 'Mobile')}</div>
{MOBILE_WIDGETS.map((w) => (
<ToggleRow
key={w.key}
label={tr(`settings.appearance.widget.${w.key}`, w.fallback)}
on={cfg.dashboard.mobile[w.key]}
onToggle={() => setWidget('mobile', w.key, !cfg.dashboard.mobile[w.key])}
/>
))}
</div>
<div className="text-sm font-semibold text-content">{tr('settings.appearance.desktop', 'Desktop')}</div>
{DESKTOP_GROUPS.map((g) => {
const masterOn = g.master ? cfg.dashboard.desktop[g.master] : true
return (
<div key={g.id} className="rounded-lg border border-edge-secondary px-3 py-2">
{g.master ? (
<ToggleRow
label={tr(`settings.appearance.widget.${g.master}`, WIDGET_LABELS[g.master])}
hint={tr('settings.appearance.sidebarHint', 'The whole right column. Turn off and the dashboard centers.')}
on={masterOn}
onToggle={() => setWidget('desktop', g.master as string, !masterOn)}
/>
) : (
<div className="text-[11px] font-semibold uppercase tracking-wide text-content-faint mb-1">
{tr(`settings.appearance.group.${g.id}`, g.fallback)}
</div>
)}
<div
className={g.master ? 'mt-1 pl-3 border-l-2 border-edge-secondary' : ''}
style={g.master && !masterOn ? { opacity: 0.4, pointerEvents: 'none' } : undefined}
>
{g.keys.map((k) => (
<ToggleRow
key={k}
label={tr(`settings.appearance.widget.${k}`, WIDGET_LABELS[k])}
on={cfg.dashboard.desktop[k]}
onToggle={() => setWidget('desktop', k, !cfg.dashboard.desktop[k])}
/>
))}
</div>
</div>
)
})}
<div className="text-sm font-semibold text-content mt-3">{tr('settings.appearance.mobile', 'Mobile')}</div>
{MOBILE_GROUPS.map((g) => (
<div key={g.id} className="rounded-lg border border-edge-secondary px-3 py-2">
<div className="text-[11px] font-semibold uppercase tracking-wide text-content-faint mb-1">
{tr(`settings.appearance.group.${g.id}`, g.fallback)}
</div>
{g.keys.map((k) => (
<ToggleRow
key={k}
label={tr(`settings.appearance.widget.${k}`, WIDGET_LABELS[k])}
on={cfg.dashboard.mobile[k]}
onToggle={() => setWidget('mobile', k, !cfg.dashboard.mobile[k])}
/>
))}
</div>
))}
</Section>
<div className="flex justify-end mb-6">
+24
View File
@@ -759,6 +759,30 @@ html[data-no-transparency] *::after {
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
}
/* Class-based translucent surfaces (Atlas tooltip, Leaflet controls, GL popups)
forced solid. Inline rgba() is handled per-component (it can't be reached by CSS). */
html[data-no-transparency] .atlas-tooltip {
background: var(--bg-card) !important;
color: var(--text-primary) !important;
border-color: var(--border-primary) !important;
}
html[data-no-transparency] .atlas-tooltip::before { border-top-color: var(--bg-card) !important; }
html[data-no-transparency] .leaflet-control-zoom a { background: var(--bg-card) !important; }
html[data-no-transparency] .map-tooltip,
html[data-no-transparency] .leaflet-tooltip.map-tooltip { background: var(--bg-card) !important; }
html[data-no-transparency] .trek-map-popup .mapboxgl-popup-content,
html[data-no-transparency] .trek-map-popup .maplibregl-popup-content { background: var(--bg-card) !important; }
/* ── Compact density ──────────────────────────────────────────────
Tightens the spacing scale; reach grows as components adopt the tokens. */
html[data-density="compact"] {
--sp-1: 3px;
--sp-2: 6px;
--sp-3: 9px;
--sp-4: 12px;
--sp-6: 18px;
}
html[data-density="compact"] .form-input { padding: 5px 11px; }
body {
font-family: var(--font-system);
+39 -36
View File
@@ -85,6 +85,9 @@ export default function AtlasPage(): React.ReactElement {
bucketSearching, bucketSearch, setBucketSearch,
} = useAtlas()
const toast = useToast()
// Solid surfaces when the user disabled transparency (read at render — the
// attribute is already set by applyAppearance before navigating here).
const noTransparency = typeof document !== 'undefined' && document.documentElement.hasAttribute('data-no-transparency')
if (loading) {
return (
@@ -108,12 +111,12 @@ export default function AtlasPage(): React.ReactElement {
<div ref={regionTooltipRef} style={{
position: 'fixed', display: 'none',
zIndex: 9999, pointerEvents: 'none',
background: dark ? 'rgba(15,15,20,0.92)' : 'rgba(255,255,255,0.96)',
background: noTransparency ? (dark ? '#0f0f14' : '#ffffff') : (dark ? 'rgba(15,15,20,0.92)' : 'rgba(255,255,255,0.96)'),
color: dark ? '#fff' : '#111',
borderRadius: 10, padding: '10px 14px',
boxShadow: '0 4px 16px rgba(0,0,0,0.18)',
border: `1px solid ${dark ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.08)'}`,
fontSize: 12, minWidth: 120,
fontSize: 'calc(12px * var(--fs-scale-text, 1))', minWidth: 120,
}} />
<AtlasCountrySearch
dark={dark}
@@ -158,7 +161,7 @@ export default function AtlasPage(): React.ReactElement {
transform: 'translateX(-50%)',
width: 'fit-content',
maxWidth: 'calc(100vw - 40px)',
background: dark ? 'rgba(10,10,15,0.55)' : 'rgba(255,255,255,0.2)',
background: noTransparency ? (dark ? '#15151c' : '#ffffff') : (dark ? 'rgba(10,10,15,0.55)' : 'rgba(255,255,255,0.2)'),
backdropFilter: 'blur(24px) saturate(180%)',
WebkitBackdropFilter: 'blur(24px) saturate(180%)',
border: '1px solid ' + (dark ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.06)'),
@@ -202,9 +205,9 @@ export default function AtlasPage(): React.ReactElement {
{confirmAction.code.length === 2 ? (
<img src={`https://flagcdn.com/w80/${confirmAction.code.toLowerCase()}.png`} alt={confirmAction.code} style={{ width: 48, height: 34, borderRadius: 6, objectFit: 'cover', marginBottom: 12, display: 'inline-block' }} />
) : (
<div style={{ fontSize: 36, marginBottom: 12 }}>{countryCodeToFlag(confirmAction.code)}</div>
<div style={{ fontSize: 'calc(36px * var(--fs-scale-text, 1))', marginBottom: 12 }}>{countryCodeToFlag(confirmAction.code)}</div>
)}
<h3 className="text-content" style={{ margin: '0 0 16px', fontSize: 16, fontWeight: 700 }}>{confirmAction.name}</h3>
<h3 className="text-content" style={{ margin: '0 0 16px', fontSize: 'calc(16px * var(--fs-scale-text, 1))', fontWeight: 700 }}>{confirmAction.name}</h3>
{confirmAction.type === 'choose' && (
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
@@ -227,8 +230,8 @@ export default function AtlasPage(): React.ReactElement {
onMouseLeave={e => e.currentTarget.style.background = 'none'}>
<MapPin size={18} className="text-content" style={{ flexShrink: 0 }} />
<div>
<div className="text-content" style={{ fontSize: 13, fontWeight: 600 }}>{t('atlas.markVisited')}</div>
<div className="text-content-muted" style={{ fontSize: 11, marginTop: 1 }}>{t('atlas.markVisitedHint')}</div>
<div className="text-content" style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 600 }}>{t('atlas.markVisited')}</div>
<div className="text-content-muted" style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', marginTop: 1 }}>{t('atlas.markVisitedHint')}</div>
</div>
</button>
<button onClick={() => setConfirmAction({ ...confirmAction, type: 'bucket' as any })}
@@ -238,8 +241,8 @@ export default function AtlasPage(): React.ReactElement {
onMouseLeave={e => e.currentTarget.style.background = 'none'}>
<Star size={18} className="text-[#fbbf24]" style={{ flexShrink: 0 }} />
<div>
<div className="text-content" style={{ fontSize: 13, fontWeight: 600 }}>{t('atlas.addToBucket')}</div>
<div className="text-content-muted" style={{ fontSize: 11, marginTop: 1 }}>{t('atlas.addToBucketHint')}</div>
<div className="text-content" style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 600 }}>{t('atlas.addToBucket')}</div>
<div className="text-content-muted" style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', marginTop: 1 }}>{t('atlas.addToBucketHint')}</div>
</div>
</button>
</div>
@@ -248,7 +251,7 @@ export default function AtlasPage(): React.ReactElement {
{confirmAction.type === 'choose-region' && (
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
{confirmAction.countryName && (
<p className="text-content-muted" style={{ margin: '-8px 0 8px', fontSize: 12 }}>{confirmAction.countryName}</p>
<p className="text-content-muted" style={{ margin: '-8px 0 8px', fontSize: 'calc(12px * var(--fs-scale-text, 1))' }}>{confirmAction.countryName}</p>
)}
<button onClick={async () => {
const { code: countryCode, name: rName, regionCode: rCode } = confirmAction
@@ -276,8 +279,8 @@ export default function AtlasPage(): React.ReactElement {
onMouseLeave={e => e.currentTarget.style.background = 'none'}>
<MapPin size={18} className="text-content" style={{ flexShrink: 0 }} />
<div>
<div className="text-content" style={{ fontSize: 13, fontWeight: 600 }}>{t('atlas.markVisited')}</div>
<div className="text-content-muted" style={{ fontSize: 11, marginTop: 1 }}>{t('atlas.markRegionVisitedHint')}</div>
<div className="text-content" style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 600 }}>{t('atlas.markVisited')}</div>
<div className="text-content-muted" style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', marginTop: 1 }}>{t('atlas.markRegionVisitedHint')}</div>
</div>
</button>
<button onClick={() => setConfirmAction({ ...confirmAction, type: 'bucket' })}
@@ -287,8 +290,8 @@ export default function AtlasPage(): React.ReactElement {
onMouseLeave={e => e.currentTarget.style.background = 'none'}>
<Star size={18} className="text-[#fbbf24]" style={{ flexShrink: 0 }} />
<div>
<div className="text-content" style={{ fontSize: 13, fontWeight: 600 }}>{t('atlas.addToBucket')}</div>
<div className="text-content-muted" style={{ fontSize: 11, marginTop: 1 }}>{t('atlas.addToBucketHint')}</div>
<div className="text-content" style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 600 }}>{t('atlas.addToBucket')}</div>
<div className="text-content-muted" style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', marginTop: 1 }}>{t('atlas.addToBucketHint')}</div>
</div>
</button>
</div>
@@ -296,16 +299,16 @@ export default function AtlasPage(): React.ReactElement {
{confirmAction.type === 'unmark' && (
<>
<p className="text-content-muted" style={{ margin: '0 0 20px', fontSize: 13 }}>{t('atlas.confirmUnmark')}</p>
<p className="text-content-muted" style={{ margin: '0 0 20px', fontSize: 'calc(13px * var(--fs-scale-text, 1))' }}>{t('atlas.confirmUnmark')}</p>
<div style={{ display: 'flex', gap: 8, justifyContent: 'center' }}>
<button onClick={() => setConfirmAction(null)}
className="border border-edge text-content-muted"
style={{ padding: '8px 20px', borderRadius: 10, background: 'none', fontSize: 13, cursor: 'pointer', fontFamily: 'inherit' }}>
style={{ padding: '8px 20px', borderRadius: 10, background: 'none', fontSize: 'calc(13px * var(--fs-scale-text, 1))', cursor: 'pointer', fontFamily: 'inherit' }}>
{t('common.cancel')}
</button>
<button onClick={executeConfirmAction}
className="bg-[#ef4444] text-white"
style={{ padding: '8px 20px', borderRadius: 10, border: 'none', fontSize: 13, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit' }}>
style={{ padding: '8px 20px', borderRadius: 10, border: 'none', fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit' }}>
{t('atlas.unmark')}
</button>
</div>
@@ -315,13 +318,13 @@ export default function AtlasPage(): React.ReactElement {
{confirmAction.type === 'unmark-region' && (
<>
{confirmAction.countryName && (
<p className="text-content-muted" style={{ margin: '-8px 0 8px', fontSize: 12 }}>{confirmAction.countryName}</p>
<p className="text-content-muted" style={{ margin: '-8px 0 8px', fontSize: 'calc(12px * var(--fs-scale-text, 1))' }}>{confirmAction.countryName}</p>
)}
<p className="text-content-muted" style={{ margin: '0 0 20px', fontSize: 13 }}>{t('atlas.confirmUnmarkRegion')}</p>
<p className="text-content-muted" style={{ margin: '0 0 20px', fontSize: 'calc(13px * var(--fs-scale-text, 1))' }}>{t('atlas.confirmUnmarkRegion')}</p>
<div style={{ display: 'flex', gap: 8, justifyContent: 'center' }}>
<button onClick={() => setConfirmAction(null)}
className="border border-edge text-content-muted"
style={{ padding: '8px 20px', borderRadius: 10, background: 'none', fontSize: 13, cursor: 'pointer', fontFamily: 'inherit' }}>
style={{ padding: '8px 20px', borderRadius: 10, background: 'none', fontSize: 'calc(13px * var(--fs-scale-text, 1))', cursor: 'pointer', fontFamily: 'inherit' }}>
{t('common.cancel')}
</button>
<button onClick={async () => {
@@ -356,7 +359,7 @@ export default function AtlasPage(): React.ReactElement {
setConfirmAction(null)
}}
className="bg-[#ef4444] text-white"
style={{ padding: '8px 20px', borderRadius: 10, border: 'none', fontSize: 13, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit' }}>
style={{ padding: '8px 20px', borderRadius: 10, border: 'none', fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit' }}>
{t('atlas.unmark')}
</button>
</div>
@@ -365,7 +368,7 @@ export default function AtlasPage(): React.ReactElement {
{confirmAction.type === 'bucket' && (
<>
<p className="text-content-muted" style={{ margin: '0 0 14px', fontSize: 13 }}>{t('atlas.bucketWhen')}</p>
<p className="text-content-muted" style={{ margin: '0 0 14px', fontSize: 'calc(13px * var(--fs-scale-text, 1))' }}>{t('atlas.bucketWhen')}</p>
<div style={{ display: 'flex', gap: 8, justifyContent: 'center', marginBottom: 16 }}>
<div style={{ flex: 1 }}>
<CustomSelect
@@ -395,7 +398,7 @@ export default function AtlasPage(): React.ReactElement {
<div style={{ display: 'flex', gap: 8, justifyContent: 'center', flexWrap: 'wrap' }}>
<button onClick={() => setConfirmAction({ ...confirmAction, type: confirmAction.regionCode ? 'choose-region' : 'choose' })}
className="border border-edge text-content-muted"
style={{ padding: '8px 20px', borderRadius: 10, background: 'none', fontSize: 13, cursor: 'pointer', fontFamily: 'inherit' }}>
style={{ padding: '8px 20px', borderRadius: 10, background: 'none', fontSize: 'calc(13px * var(--fs-scale-text, 1))', cursor: 'pointer', fontFamily: 'inherit' }}>
{t('common.back')}
</button>
<button onClick={async () => {
@@ -410,7 +413,7 @@ export default function AtlasPage(): React.ReactElement {
setConfirmAction(null)
}}
className="bg-[#fbbf24] text-[#1a1a1a]"
style={{ padding: '8px 20px', borderRadius: 10, border: 'none', fontSize: 13, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit' }}>
style={{ padding: '8px 20px', borderRadius: 10, border: 'none', fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit' }}>
{t('atlas.addToBucket')}
</button>
</div>
@@ -419,16 +422,16 @@ export default function AtlasPage(): React.ReactElement {
{confirmAction.type === 'mark' && (
<>
<p className="text-content-muted" style={{ margin: '0 0 20px', fontSize: 13 }}>{t('atlas.confirmMark')}</p>
<p className="text-content-muted" style={{ margin: '0 0 20px', fontSize: 'calc(13px * var(--fs-scale-text, 1))' }}>{t('atlas.confirmMark')}</p>
<div style={{ display: 'flex', gap: 8, justifyContent: 'center' }}>
<button onClick={() => setConfirmAction(null)}
className="border border-edge text-content-muted"
style={{ padding: '8px 20px', borderRadius: 10, background: 'none', fontSize: 13, cursor: 'pointer', fontFamily: 'inherit' }}>
style={{ padding: '8px 20px', borderRadius: 10, background: 'none', fontSize: 'calc(13px * var(--fs-scale-text, 1))', cursor: 'pointer', fontFamily: 'inherit' }}>
{t('common.cancel')}
</button>
<button onClick={executeConfirmAction}
className="bg-content text-white"
style={{ padding: '8px 20px', borderRadius: 10, border: 'none', fontSize: 13, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit' }}>
style={{ padding: '8px 20px', borderRadius: 10, border: 'none', fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit' }}>
{t('atlas.markVisited')}
</button>
</div>
@@ -506,7 +509,7 @@ function SidebarContent({ data, stats, countries, selectedCountry, countryDetail
style={{
flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 5,
padding: '7px 0', borderRadius: 10, border: 'none', cursor: 'pointer', fontFamily: 'inherit',
fontSize: 12, fontWeight: 600, transition: 'all 0.15s',
fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 600, transition: 'all 0.15s',
background: bucketTab === tab.id ? bg(0.1) : 'transparent',
color: bucketTab === tab.id ? tp : tf,
}}>
@@ -560,7 +563,7 @@ function SidebarContent({ data, stats, countries, selectedCountry, countryDetail
</div>
))}
{bucketList.length === 0 && !showBucketAdd && (
<div className="flex items-center justify-center py-4 px-6" style={{ color: tf, fontSize: 12 }}>
<div className="flex items-center justify-center py-4 px-6" style={{ color: tf, fontSize: 'calc(12px * var(--fs-scale-text, 1))' }}>
{t('atlas.bucketEmptyHint')}
</div>
)}
@@ -576,7 +579,7 @@ function SidebarContent({ data, stats, countries, selectedCountry, countryDetail
placeholder={t('atlas.bucketNamePlaceholder')}
autoFocus
className="border border-edge text-content bg-surface-input"
style={{ flex: 1, padding: '6px 10px', borderRadius: 8, fontSize: 12, fontFamily: 'inherit', outline: 'none', boxSizing: 'border-box' }}
style={{ flex: 1, padding: '6px 10px', borderRadius: 8, fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontFamily: 'inherit', outline: 'none', boxSizing: 'border-box' }}
/>
{!bucketForm.name && (
<button onClick={onSearchBucket} disabled={bucketSearching}
@@ -597,8 +600,8 @@ function SidebarContent({ data, stats, countries, selectedCountry, countryDetail
<div className="bg-surface-card border border-edge" style={{ position: 'absolute', bottom: '100%', left: 0, right: 0, zIndex: 50, marginBottom: 4, borderRadius: 8, boxShadow: '0 4px 12px rgba(0,0,0,0.12)', maxHeight: 160, overflowY: 'auto' }}>
{bucketSearchResults.slice(0, 6).map((r, i) => (
<button key={i} onClick={() => onSelectBucketPoi(r)} className="border-b border-edge-faint" style={{ display: 'flex', flexDirection: 'column', gap: 1, width: '100%', padding: '6px 10px', borderTop: 'none', borderLeft: 'none', borderRight: 'none', background: 'none', cursor: 'pointer', textAlign: 'left', fontFamily: 'inherit' }}>
<span className="text-content" style={{ fontSize: 12, fontWeight: 500 }}>{r.name}</span>
{r.address && <span className="text-content-faint" style={{ fontSize: 10 }}>{r.address}</span>}
<span className="text-content" style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 500 }}>{r.name}</span>
{r.address && <span className="text-content-faint" style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))' }}>{r.address}</span>}
</button>
))}
</div>
@@ -606,7 +609,7 @@ function SidebarContent({ data, stats, countries, selectedCountry, countryDetail
</div>
{/* Selected place indicator */}
{bucketForm.lat && bucketForm.lng && (
<div className="text-content-faint" style={{ fontSize: 10, display: 'flex', alignItems: 'center', gap: 4 }}>
<div className="text-content-faint" style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', display: 'flex', alignItems: 'center', gap: 4 }}>
<MapPin size={10} /> {Number(bucketForm.lat).toFixed(4)}, {Number(bucketForm.lng).toFixed(4)}
</div>
)}
@@ -624,12 +627,12 @@ function SidebarContent({ data, stats, countries, selectedCountry, countryDetail
<div style={{ display: 'flex', gap: 6, justifyContent: 'flex-end' }}>
<button onClick={() => { setShowBucketAdd(false); setBucketForm({ name: '', notes: '', lat: '', lng: '', target_date: '' }); setBucketSearch(''); setBucketSearchResults([]); setBucketPoiMonth(0); setBucketPoiYear(0) }}
className="border border-edge text-content-muted"
style={{ fontSize: 11, padding: '4px 10px', borderRadius: 6, background: 'none', cursor: 'pointer', fontFamily: 'inherit' }}>
style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', padding: '4px 10px', borderRadius: 6, background: 'none', cursor: 'pointer', fontFamily: 'inherit' }}>
{t('common.cancel')}
</button>
<button onClick={onAddBucket} disabled={!bucketForm.name.trim()}
className="bg-[#fbbf24] text-[#1a1a1a]"
style={{ fontSize: 11, padding: '4px 12px', borderRadius: 6, border: 'none', fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit', opacity: bucketForm.name.trim() ? 1 : 0.5 }}>
style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', padding: '4px 12px', borderRadius: 6, border: 'none', fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit', opacity: bucketForm.name.trim() ? 1 : 0.5 }}>
{t('common.add')}
</button>
</div>
@@ -638,7 +641,7 @@ function SidebarContent({ data, stats, countries, selectedCountry, countryDetail
<div style={{ padding: '4px 16px 8px' }}>
<button onClick={() => setShowBucketAdd(true)}
className="border border-dashed border-edge"
style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 4, width: '100%', padding: '5px 0', borderRadius: 8, background: 'none', fontSize: 11, color: tf, cursor: 'pointer', fontFamily: 'inherit' }}>
style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 4, width: '100%', padding: '5px 0', borderRadius: 8, background: 'none', fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: tf, cursor: 'pointer', fontFamily: 'inherit' }}>
<Plus size={11} /> {t('atlas.addPoi')}
</button>
</div>
+4
View File
@@ -388,6 +388,10 @@ const settings: TranslationStrings = {
'settings.appearance.widget.daysTraveled': 'Reisetage',
'settings.appearance.widget.distanceFlown': 'Flugdistanz',
'settings.appearance.reset': 'Auf Standard zurücksetzen',
'settings.appearance.group.belowHero': 'Unter dem Hero',
'settings.appearance.group.bottomOfPage': 'Seitenende',
'settings.appearance.sidebarHint': 'Die ganze rechte Spalte. Aus → das Dashboard zentriert sich.',
'settings.appearance.densityHint': 'Kompakt verringert Abstände und Innenabstände für ein dichteres Layout.',
};
export default settings;
+4
View File
@@ -382,6 +382,10 @@ const settings: TranslationStrings = {
'settings.appearance.widget.daysTraveled': 'Days traveled',
'settings.appearance.widget.distanceFlown': 'Distance flown',
'settings.appearance.reset': 'Reset to defaults',
'settings.appearance.group.belowHero': 'Below the hero',
'settings.appearance.group.bottomOfPage': 'Bottom of page',
'settings.appearance.sidebarHint': 'The whole right column. Turn off and the dashboard centers.',
'settings.appearance.densityHint': 'Compact tightens spacing and padding for a denser layout that fits more on screen.',
};
export default settings;